【问题标题】:Can integer division in C/C++ run into loss of precision issues?C/C++ 中的整数除法会遇到精度损失问题吗?
【发布时间】:2019-07-29 14:21:26
【问题描述】:

假设我们有三个整数(int、long、long long、unsigned int 等)变量a, b, c。通常,执行

c = a / b;

会导致分数被截断。但是,c 是否可能以错误的值结束?

我不是在说 a / b 可能超出c's type. 的范围,而是我说的是在 C 中如何实现整数除法。执行a / b 是否首先生成一个浮点类型的中间结果,然后中间值被截断?

如果是这样,我想知道中间值的精度损失是否会导致 c 的值不正确。例如,假设 a / b 的精确值是 2,但不知何故中间结果是 1.9999...,因此 c 最终会得到不正确的值 1。这种情况会发生吗,还是整数除法总是导致正确的如果期望值在 c 的类型范围内,则取值?

【问题讨论】:

  • 这不是 Commodore 基本的,整数运算是作为整数完成的。整数运算通常比浮点数更有效。
  • 整数数学是严格使用整数完成的。事实上,一些处理器只能进行浮点数学运算,而附加的浮点数学协处理器确实可以处理浮点数。
  • @NathanOliver 那么我可以安全地假设整数除法总是会导致正确的结果吗?
  • 你必须担心上溢和下溢,但除此之外,整数数学是精确的。

标签: c++ c


【解决方案1】:

执行a/b是否先产生float类型的中间结果

就语言而言,没有中间结果。

如果期望值在 c 的类型范围内,整数除法是否总是得到正确的值?

是的。

【讨论】:

  • “整数除法中没有分数”,好吧,我认为硅上使用的任何合理的二进制除法算法也会产生分数(或余数,这与产生分数部分基本相同), CPU 上的“基本”除法指令也会将余数作为结果的一部分(SIMD 指令可能会丢弃它)。
  • @hyde 并不总是需要生成分数(或使用一些 idiv 等价物):godbolt.org/z/aRaDWM - homepage.divms.uiowa.edu/~jones/bcd/divide.html
  • @hyde 分数与余数不同,即使它们是相关概念。无论如何,我的回答是关于语言的。硬件可以在底层为所欲为,语言实现必须处理它。
  • 是的,当然,对于任何有常量的操作(取决于 CPU,可能对于任何编译时间常量除数),都有特殊情况和优化。我不确定在这种情况下称这种一般的“整数除法”是否公平。
  • Reminder 直接是分数的分子部分,具有已知的固定分母(至少对于正面结果,CBA 考虑到负面结果),所以它几乎是一回事。无论如何,我相信当前的C++标准至少确实使用分数来定义整数除法的结果,因此C++整数除法存在分数的概念(不能丢弃不存在的东西)。
【解决方案2】:

C11 标准第 6.5.5 节规定

当整数被除法时,/ 运算符的结果是代数商,其中任何小数部分被丢弃。 如果商a/b 是可表示的,则表达式(a/b)*b + a%b 应等于a;

这意味着从数学上讲,您不会得到错误的结果。

【讨论】:

  • 实际上并不是这样说的。 :)
  • @RobertHarvey 好吧,如果结果像 OP 所要求的那样不精确,假设没有余数,(a / b) * b 将永远不会导致 a(因为 1.99999... 不可表示,因此不准确)。如果有提醒也是一样。或者,也许我只是读得太多了。
  • 这意味着错误的结果必须是可逆的;)
  • @LightnessRacesinOrbit 好的,但在这种情况下“不正确”意味着“不精确”。这个问题最初是关于精度损失的。如果是这样的话,(a/b) * b 的结果不会和a 不同吗?当其他两个用户告诉我我误解了这一点时,我确定我错过了一些东西,但我似乎看不到它。
  • @FedericoklezCulloca 问题是关于不精确的中间阶段(不存在)导致的不正确。您所证明的只是,如果存在所述不精确性,它将是对称的,这并不能证明一个方向的结果在数学上是“正确的”。会的,但这不是原因。
【解决方案3】:

假设我们有三个整数(int、long、long long、unsigned int 等)变量 a、b、c。正常情况下,执行

c = a / b;

会导致分数被截断。但是,c 是否有可能最终得到不正确的值? 我不是在说 a / b 可能超出 c 的类型范围。

如果所有规则都被遵循,那么例如除法的最后一位数字是错误的是不可能的。 C11 6.5.5p6:

当整数相除时,/ 运算符的结果是去掉任何小数部分的代数商。

即结果不是“接近”但与 a / b 在代数上完全相同,只是丢弃该点之后的任何内容。

这并不意味着不会有任何问题:a / b 的除法可能在数学上没有超出 c 的类型的范围,但超出了除法中使用的类型的范围本身可能导致在 c 中设置错误的值。

考虑这个例子:

#include <stdio.h>
#include <inttypes.h>   

int main(void) {
    int32_t a = INT32_MIN;
    int32_t b = -1;
    int64_t c = a / b;
    printf("%" PRId64, c);
}

INT32_MIN / -1的除法结果可以用c表示,就是INT32_MAX + 1,是。然而,在 32 位平台上,算术运算发生在 32 位中,并且这种除法会产生整数溢出,从而导致行为未定义。在我的计算机上发生的情况是,如果我在没有优化的情况下编译它中止程序。如果我在启用优化的情况下进行编译 (-O3),编译器将在编译时解决此计算,并以一种特殊的方式处理溢出并生成结果 -2147483648,它是 negative。 p>

同样,如果你这样做:

uint16_t a = 16;
int16_t b = -1;
int32_t result = a / b;
printf("%" PRId32 "\n", result);

在 32 位 int 机器上的结果是 -16。如果您将a 的类型更改为uint32_t,则数学以无符号形式发生:

uint32_t a = 16;
int16_t b = -1;
int32_t result = a / b;
printf("%" PRId32 "\n", result);

结果当然是0。在 16 位机器上,您也会从 之前的计算中得到0

【讨论】:

    猜你喜欢
    • 2020-07-08
    • 2014-05-12
    • 1970-01-01
    • 1970-01-01
    • 2012-05-13
    • 2016-12-19
    • 1970-01-01
    • 1970-01-01
    • 2010-10-16
    相关资源
    最近更新 更多