双精度变量存储整数值的算术运算是否精确?

16

假设我有两个整数值存储在 double 变量中,例如:

double x = 100.0;
double y = 7.0;

我可以安全地假设,在这两个双精度变量上执行任何会产生整数结果的算术运算,都将返回一个精确的整数值(作为 double)吗?也就是说,例如以下所有内容是否都是正确的:

x + y = 107.0
x - y = 93.0
x * y = 700.0

返回的是确切的整数值,还是会存在精度问题?例如 x*y 得到的结果为 699.99995 等。

一般性问题:对于两个保存整数值的双精度变量进行任何算术运算得到整数结果时,是否将返回确切的整数值(作为双精度)?

我在Java环境下提出这个问题,但我认为其他语言也类似。

7个回答

13
只要你的操作的整数结果可以被准确地表示为双精度浮点数,就会得到准确的结果,但一旦整数结果超过尾数中可用的位数(即52+1 = 53位),它就会被舍入。

4

3

大家讨论得很好。

你的问题是:

如果对两个包含整数值的双精度变量进行任何算术运算,以产生一个整数结果,那么这个结果将作为一个双精度浮点数返回一个确切的整数值吗?

我选择了一个边界情况,即两个数字恰好为53位。54位的和超过了double的容量,因此没有返回一个精确的整数结果。正如预期的一样,低位比特被截断,你会得到一个奇怪但预料之中的结果。

一个奇数加一个偶数不会得到一个奇数和(就像数学告诉你的那样);Java报告的是一个偶数(就像IEEE标准告诉你的那样)。

试试这个样例:

private static void doubleCalc() {
  double x = 4503599627370497.0d; // binary 10000000000000000000000000000000000000000000000000001
  double y = 4503599627370496.0d; // binary 10000000000000000000000000000000000000000000000000000

  double sum = x + y;
  System.out.println("sum=" + sum + "; should be 9007199254740993.0d");
}

它将打印出以下内容:
sum=9.007199254740992E15; should be 9007199254740993.0d

所以这个精心选择的反例会回答你精心措辞的问题,是否定的。

3
如果结果数字的位数太多,无法适应double类型。例如,1234567890.0 * 1234567890.0得到的结果是1,52415787501905E+18而不是1524157875019052100。如果结果可以适应,我不知道它是否总是准确的,但是@Sven Marnach已经回答了这个问题。我假设截断的数字将会是一个精确的整数,就像@Douglas Leeder所说的那样,因为曼蒂斯被指数(大于曼蒂斯中数字的数量)移位后将变成一个整数。

好的,这是真的,我没有明确地说出来。但是如果适用的话呢? - MicSim

2

所有的 int 值都可以通过 double 值精确地表示,而且 +*- 运算在这里也是一样的(只要不超过 int 范围)。但是,/% 运算则有所不同。

由于 double 只有 52 位尾数,因此你也无法精确地表示所有的 long 值。


1
只要数字之间不相差太远(例如2^1024和0.005),结果应该是精确的。双精度浮点数的工作原理如下:1位用于表示符号,11位用于表示指数,52位用于表示尾数。最终的数字是((-1)*(符号))(1.尾数 << (指数 - 1 << 10)),因此当两个数字进行加法运算时,就会发生这种情况:
x = number with greatest exponent
y = number with smallest exponent

(in case of same sign)
z.mantissa = x.mantissa + (y.mantissa >> (x.exponent - y.exponent) )
sign = either_one.sign

(in case of opposite sign)
z.mantissa = x.mantissa - (y.mantissa >> (x.exponent - y.exponent) )
sign = x.sign

对于乘法/除法而言,它要简单一些:

z.exponent = x.exponent + y.exponent
z.mantissa = 1.(x.mantissa) (operand) (y.mantissa)
z.sign = x.sign != y.sign
while (z.mantissa is not in format 1.x)
   z.mantissa << 1 (division)
   z.exponent--
   z.mantissa >> 1 (multiplication)
   z.exponent++

如果指数相差太大,移位时会丢失数据,这意味着对于双精度浮点数(一般而言)的精度不是100%准确的(特别是因为一些数字变成周期性小数)。但是对于完美的整数数字和结果,只要数字长度不超过52位(尾数的大小),就应该没问题,因为CPU可以将其移位为整数(例如1.111 << 3是1111)。

0
在一个相关问题中,有人指出双精度浮点数大约有15位数字的精度,而它可以容纳10^(300+)大的数字。所以我想只要你使用较小的整数就不应该是个大问题。
话虽如此,以下是来自Oracle教程的一些内容:

double:双精度数据类型是双精度64位IEEE 754浮点数。它的值范围超出了本讨论的范围,但在Java语言规范的第4.2.3节中有详细说明。对于十进制值,这种数据类型通常是默认选择。如上所述,此数据类型不应用于精确值,例如货币。

如需进一步参考,请查看上面提到的第4.2.3节链接。

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