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


103

我只是想知道,如果我想将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);

要么

也许有一个神奇的功能可以一次给出两者?


5
以下所有答案似乎都是合理的,我想补充一点,double对我(您的最后一项)的任何污蔑在我看来都是一个坏主意,您最终会得到不符合要求的数字,并且可能会浪费您的性能和可执行文件的大小(在某些嵌入式系统上,这始终是我的问题)。
2011年

2
第三个是BAD选项:如果tmp = 54.999999999999943157,该怎么办?这就是说,老式的铸造绝不是一件聪明的事。
2014年

Answers:


98

在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. */

9
我认为许多人从小学就知道,做一个除法运算时,您会免费获得其余的。真正的问题是:我们的编译器是否足够聪明以利用这一优势?

1
@jdv:我不会感到惊讶。这是一个非常简单的优化。
乔恩·普迪

68
我尝试了快速测试。在使用-O2的g ++ 4.3.2中,汇编器输出使用一条idivl指令并使用eax和edx中的结果清楚地显示了它。如果没有的话,我会感到震惊。
Fred Larson

2
@EuriPinhollow同意这不是关于x86的问题。我只是举一个例子,高度怀疑其他架构可能会做类似的事情。
cnicutar

3
但是,您确实需要告诉编译器进行优化,至少是针对g ++。在x86_64上使用g ++ 6.3.0进行的一个简单实验显示,完全没有优化,您将获得两条idivl指令,而具有-O1或更高版本,您将获得一条指令。正如手册所说:“没有任何优化选项……语句是独立的”
汤姆·齐奇

80

std::div 返回具有结果和余数的结构。


5
我很好奇这是否实际上比现代编译器上的选项1更有效。
格雷格·豪威尔

2
很好,我不知道。它更快吗?

真好 您是否会知道某个地方是否长期实施?
Cookie的

@Cookie:C ++ 03没有的概念long long,但是您的编译器很可能具有作为扩展的long long重载std::div
ildjarn 2011年

@Greg:我不这么认为,因为它很可能必须将结果写入内存中的结构并返回它(除非将其编译为内联并且优化了结构访问),否则这样做的代价更大。额外的除法指令。
pezcode 2011年

28

至少在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那时没关系,如今也没关系。编译器很聪明,可以重新排序。
Sahsahae,

10

示例代码测试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



3

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

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

是您提出的三个选项中最好的一个。但是,如其他答案所述,该div方法将立即为您计算两个值。


3

您不能在32位Intel平台上使用64位整数信任g ++ 4.6.3。a / b是通过调用divdi3来计算的,而%b是通过调用moddi3来计算的。我什至可以提出一个使用这些调用来计算a / b和ab *(a / b)的示例。所以我使用c = a / b和ab * c。

div方法调用了一个计算div结构的函数,但是在具有对整数类型的硬件支持的平台上(例如,在64位intel / amd平台上为64位整数),该函数调用效率似乎较低。


-4

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


2
是的,原始海报使用了模运算符,问题是如何使其高效。
Mark Lakata 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.