【问题标题】:C++ Best way to get integer division and remainderC++ 获得整数除法和余数的最佳方法
【发布时间】:2011-08-15 20:21:16
【问题描述】:

我只是想知道,如果我想将 a 除以 b,并且对结果 c 和余数都感兴趣(例如,假设我有秒数并想将其分成分钟和秒),那么最好的方法?

会不会

int c = (int)a / b;
int d = a % b;

int c = (int)a / b;
int d = a - b * c;

double tmp = a / b;
int c = (int)tmp;
int d = (int)(0.5+(tmp-c)*b);

也许有一种神奇的功能可以同时提供两者?

【问题讨论】:

  • 以下所有答案似乎都是合理的,我想补充一点,在我看来,任何与 double(你的最后一项)的混搭似乎都是个坏主意,你最终会得到不t 排队,并且可能会在性能和可执行文件大小方面付出代价(在某些嵌入式系统上对我来说一直是个问题)。
  • 第三个是坏选项:如果 tmp = 54.999999999999943157 怎么办?这就是说,老式的铸造从来都不是一件聪明的事情。

标签: c++ division


【解决方案1】:

在 x86 上,余数是除法本身的副产品,因此任何半体面的编译器都应该能够只使用它(而不是再次执行 div)。这可能也在其他架构上完成。

指令:DIVsrc

注意:无符号除法。将累加器 (AX) 除以“src”。如果除数 是一个字节值,结果放在 AL ,余数放在 AH。如果除数 是一个字值,然后DX:AX除以“src”并存储结果 在 AX 中,余数存储在 DX 中

int c = (int)a / b;
int d = a % b; /* Likely uses the result of the division. */

【讨论】:

  • 我想很多人从小学就知道,做除法时,余数是免费的。真正的问题是:我们的编译器是否足够聪明,可以利用这一点?
  • @jdv:我不会感到惊讶。这是一个非常简单的优化。
  • 我尝试了一个快速测试。对于使用 -O2 的 g++ 4.3.2,汇编器输出清楚地显示了它使用一条 idivl 指令并使用 eax 和 edx 中的结果。如果没有,我会感到震惊。
  • @EuriPinhollow 同意这不是关于 x86 的问题。我只是把它作为一个例子,假设其他架构可能做类似的事情是非常可疑的。
  • 您确实需要告诉编译器进行优化,至少对于 g++。在 x86_64 上使用 g++ 6.3.0 进行的一个简单实验表明,在完全没有优化的情况下,你会得到两条 idivl 指令,但使用 -O1 或更大的指令,你会得到一条。正如手册所说,“Without any optimization option … Statements are independent”.
【解决方案2】:

std::div 返回一个包含结果和余数的结构。

【讨论】:

  • 我很想知道这是否真的比现代编译器上的选项 1 更有效。
  • 很好,我不知道。它更快吗?
  • 不错。您是否会碰巧知道某个地方是否已长期实施?
  • 看起来很有效率。 cppreference 表示“在许多平台上,一条 CPU 指令同时获得商和余数,这个函数可以利用这一点,尽管编译器通常能够在合适的地方合并附近的 / 和 %。”
  • 我不在乎它是否更快。 它更具可读性。同时使用/%,您将有代码重复或除数和除数的额外变量,没有经验的读者可能会问:这会优化吗? std::div 更清晰,减少歧义和冗余。使用结构化绑定,使用起来非常优雅:const auto [q, r] = std::div(divisor, dividend);
【解决方案3】:

至少在 x86 上,g++ 4.6.1 只使用 IDIVL 并从那条指令中获取两者。

C++ 代码:

void foo(int a, int b, int* c, int* d)
{
  *c = a / b;
  *d = a % b;
}

x86 代码:

__Z3fooiiPiS_:
LFB4:
    movq    %rdx, %r8
    movl    %edi, %edx
    movl    %edi, %eax
    sarl    $31, %edx
    idivl   %esi
    movl    %eax, (%r8)
    movl    %edx, (%rcx)
    ret

【讨论】:

  • 顺序重要吗?例如,如果您正在迭代 /= - 您可能需要先使用临时变量来保持除法。
  • @Annan 那时候没关系,现在也没关系。编译器足够聪明,可以重新排序。
【解决方案4】:

测试 div() 和组合除法和 mod 的示例代码。我用 gcc -O3 编译了这些,我必须添加对 doNothing 的调用以阻止编译器优化所有内容(除法 + mod 解决方案的输出将为 0)。

持保留态度:

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    div_t result;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        result = div(i,3);
        doNothing(result.quot,result.rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

输出:150

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    int dividend;
    int rem;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        dividend = i / 3;
        rem = i % 3;
        doNothing(dividend,rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

输出:25

【讨论】:

    【解决方案5】:

    除了前面提到的std::div系列函数,还有std::remquo系列函数,返回rem -ainder 并通过传入的指针获取 quo-tient。

    [编辑:] 毕竟看起来像 std::remquo doesn't really return the quotient

    【讨论】:

      【解决方案6】:

      在其他条件相同的情况下,最好的解决方案是明确表达您的意图。所以:

      int totalSeconds = 453;
      int minutes = totalSeconds / 60;
      int remainingSeconds = totalSeconds % 60;
      

      可能是您提出的三个选项中最好的一个。然而,正如其他答案中所述,div 方法将同时为您计算这两个值。

      【讨论】:

        【解决方案7】:

        您不能相信 g++ 4.6.3 在 32 位英特尔平台上使用 64 位整数。 a/b 通过调用 divdi3 计算,a%b 通过调用 moddi3 计算。我什至可以想出一个用这些调用计算 a/b 和 a-b*(a/b) 的例子。所以我使用 c=a/b 和 a-b*c。

        div 方法调用计算 div 结构的函数,但函数调用在硬件支持整数类型的平台上似乎效率低下(即 64 位 intel/amd 平台上的 64 位整数)。

        【讨论】:

          【解决方案8】:

          许多答案建议使用以下代码:

          int div = a / b;
          int mod = a % b;
          

          但是,值得记住的是,除法与乘法不同,加法的执行时间要长得多(据我所知,时钟周期是一个时钟周期的几十倍)。并且,如果需要反复计算mod和div,那么值得将二除换成一除一乘一加如下:

          int div = a / b;
          int mod = a - div * b;
          

          【讨论】:

          • 我使用gcc -S -O3检查了这两个建议,编译器能够将两者优化为相同的汇编代码
          • 编译器很棒:)
          • 没有。真棒谁写的:)
          • @AlexeyIsmagilov 你的函数体已经过优化(因为它什么都不做所以被移除),看这里,result are different
          • 但是@MichaelKotzjan 是对的,看看result
          【解决方案9】:

          您可以使用模数来获得余数。尽管@cnicutar 的回答似乎更干净/更直接。

          【讨论】:

          • 是的,原贴使用了模数运算符,问题是如何使其高效。
          猜你喜欢
          • 1970-01-01
          • 2011-08-01
          • 2018-08-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-23
          • 2010-09-15
          • 2011-09-24
          相关资源
          最近更新 更多