Java常数乘除的双精度计算

5

可能有重复:
在Java中保留Double的精度

您知道Java中这两个操作的区别吗?

final double m1 = 11d / 1e9; // gives 1.1e-8
final double m2 = 11d * 1e-9; // gives 1.1000000000000001e-8

我在生成的字节码中看到m2的预编译结果已经不是我所期望的。
javap -verbose -c 的输出中,我可以看到以下值:

const #3 = double   1.1E-8d;
[...]
const #6 = double   1.1000000000000001E-8d;

当我在其他表达式中使用m1或m2时,结果并不相同。

当我尝试在C中进行相同的操作时,m1和m2严格为1.1e-8。

我认为我的问题在于Java处理双精度计算的方式,但我无法解释自己错过了什么。

3个回答

3

Java 和 C 没有区别,两者都遵循 IEEE 浮点数标准。唯一的区别在于打印方式。精确值 1.1e-8 无法被表示为一个 double 类型。输出结果的差异可能归因于如何积累表示误差的细节以及用于打印结果的舍入方式。


我认为区别只在于累积了多少错误,而第二行仅仅超出了打印的阈值。 - Marko Topolnik

1
您的区别在于1e9是可以准确表示的,而1e-9则不能准确表示。一个小的表示误差会导致算术误差,这一点您可以看到。
System.out.println(new BigDecimal(1e9));
System.out.println(new BigDecimal(1e-9));

打印

1000000000
1.0000000000000000622815914577798564188970686927859787829220294952392578125E-9

区别不在于您使用了 * 还是 /,而在于您在一个情况下使用了整数,在另一个情况下使用了分数。

System.out.println(11 * 1e9);
System.out.println(11 * 1e-9);

打印

1.1E10
1.1000000000000001E-8

好的,这是Java的解释,但是为什么当我在C中执行相同的代码时,我拥有了所需的所有精度呢? - Joker
C编译器将比javac更进一步地优化代码。这意味着它可以将/ 1e-9转换为* 1e9,因为a)它更快,b)更准确。Java的编译器很少进行优化,这留给JIT来完成,这也意味着未优化和优化的代码必须做相同的事情,否则在程序运行时会看到行为变化。在C和Java中,将double转换为String的方式也不同,但这不太可能是差异的原因。在两种情况下,它们使用相同的FPU。 - Peter Lawrey
1
此外,C编译器可以利用计算机上的80位浮点寄存器/计算。这取决于平台,并且仅适用于x86 / x64处理器。Java在每个平台上的行为都相同,因此即使可用80位浮点,也只会使用64位浮点。 - Peter Lawrey

0
如果您使用如下的BigDecimal:
BigDecimal a=new BigDecimal(1e-9);
BigDecimal b=new BigDecimal(11);
System.out.println(a.multiply(b).toString());

你会看到你的数字是

1.10000000000000006850975060355784206078677556206457666121423244476318359375E-8

JVM会将您的数字四舍五入并更改为:

1.1000000000000001e-8

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