C++获取整数除法和余数的最佳方法

132

我在想,如果我想将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);

或者

也许有一个神奇的函数可以同时给出两者?


7
下面的所有答案都看起来很合理,我只想补充一点,任何对“double”进行修改的操作(你提到的最后一项)在我看来似乎是个坏主意,你最终会得到不对齐的数字,这可能会影响性能和可执行文件大小(在某些嵌入式系统上一直是我的问题)。 - nhed
2
第三个选项是不好的选择:如果tmp = 54.999999999999943157怎么办? 因此,旧式转换永远不是明智的做法。 - jimifiki
9个回答

130

在x86架构中,余数是除法本身的副产品,因此任何像样的编译器都应该能够直接使用它(而不进行

div
)。其他架构可能也会这样做。

指令:DIV src

注意:无符号除法。将累加器(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. */

14
我认为很多人从小学就知道,在做除法时可以免费得到余数。真正的问题是:我们的编译器是否聪明到足以利用这一点? - user180326
1
@jdv: 我不会感到惊讶。这是一个非常简单的优化。 - Jon Purdy
86
我尝试了一项快速测试。使用 g++ 4.3.2 和 -O2 编译选项,汇编输出明确显示它使用了一个 idivl 指令,并将结果存储在 eax 和 edx 中。如果它没有这样做,我会感到震惊的。 - Fred Larson
2
@EuriPinhollow 同意这不是有关 x86 的问题。我只是用它作为一个例子,假设其他架构可能会做类似的事情,这种假设非常可疑。 - cnicutar
4
然而,您确实需要告诉编译器进行优化,至少对于g++来说是这样的。一项在x86_64上使用g++ 6.3.0进行的简单实验表明,在没有任何优化的情况下,您将获得两个idivl指令,但是通过使用-O1或更高级别的优化,您将获得一个指令。正如手册所说,“没有任何优化选项…语句是独立的” - Tom Zych
显示剩余6条评论

119

std::div 返回一个结构体,其中包含商和余数。


5
我很好奇这个方法是否比现代编译器的选项1更有效率。 - Greg Howell
2
很好,我不知道。它更快吗? - user180326
4
看起来很高效。cppreference 上说,“在许多平台上,一个单独的 CPU 指令可以同时获得商和余数,该函数可能会利用这一点,尽管编译器通常能够合并适当的相邻的 / 和 %。” - Dilawar
6
我不关心它是否更快。更易读才是关键。同时使用/%运算符,你将会有代码重复或需要额外的变量来存放被除数和除数,并且缺乏经验的读者可能会问:这段代码会被优化吗?使用std::div能够使代码更加清晰,减少歧义和冗余。通过使用结构化绑定,可以非常优雅地实现:const auto [q, r] = std::div(divisor, dividend); - pasbi
2
@pasbi,文档明确规定不要假设商和余数的特定内存顺序。 - William Cushing
显示剩余5条评论

36
在至少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

顺序重要吗?例如,如果您正在迭代 /=,您可能需要使用临时变量来保持除法优先。 - AnnanFay
@Annan 那时候不重要,现在也不重要。编译器足够聪明,可以重新排序。 - user11877195

12

测试div()和组合除法与取模的示例代码。我使用gcc -O3编译了这些代码,必须添加对doNothing的调用以防止编译器将所有内容优化掉(使用除法+取模解决方案的输出将为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


6

4
你不能在32位英特尔平台上使用g++ 4.6.3对64位整数进行信任。 a/b是通过调用divdi3来计算的,而a%b是通过调用moddi3来计算的。 我甚至可以举出一个例子,使用这些调用计算a/b和a-b*(a/b)。 因此,我使用c=a/b和a-b*c。
在64位intel / amd平台上有硬件支持的情况下,div方法会调用一个函数来计算div结构,但是在这些平台上调用函数似乎不太有效率。

4

其他条件相同的情况下,最好的解决方案是能够清晰表达您意图的方案。因此:

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

你提供的三个选项中,div 方法可能是最好的选择。但需要注意的是,div 方法会同时计算这两个值,这一点在其他回答中也有提到。


-1
许多答案建议使用以下代码:
int div = a / b;
int mod = a % b;

然而,值得记住的是,除法与乘法、加法相比需要更长时间来执行(据我所知,相当于数十个时钟周期与一个时钟周期)。如果您需要重复计算模和除法,则应该使用以下方法将两个除法替换为一个除法、一个乘法和一个加法:
int div = a / b;
int mod = a - div * b;

3
我使用 gcc -S -O3 检查了两个建议,并且编译器能够将它们优化为相同的汇编代码。 - Michael Kotzjan
编译器很棒 :) - Michael Kotzjan
2
没有。谁写的很棒 :) - Alexey Ismagilov
1
@AlexeyIsmagilov,你的函数体已经被优化了(因为它什么也没做),看这里,结果是不同的 - 0dminnimda
1
但是@MichaelKotzjan是正确的,请看结果 - 0dminnimda

-5

你可以使用模数来获取余数。虽然@cnicutar的答案似乎更简洁/直接。


2
是的,原帖作者使用了模数运算符,问题在于如何使其更高效。 - Mark Lakata

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接