Java与C的浮点数: "x * x" 与 "pow(x,2)" 有什么区别?

18

为什么会这样呢?与C以及Java的Math.pow方法相比,在乘以两个浮点数时,Java似乎会产生一个小差异的结果。

Java:

float a = 0.88276923;

double b = a * a;   // b becomes 0.779281497001648  <---- what???
b = Math.pow(a,2);  // b becomes 0.7792815081874238

C:

float a = 0.88276923;

double b = a * a;   // b becomes 0.7792815081874238
pow(a,2);           // b becomes 0.7792815081874238

更新:根据Ed S.的评论,我还发现C的行为会随着编译器而改变。使用gcc似乎与Java的行为相匹配。使用Visual Studio(取决于目标平台),它可以产生上述结果或与Java中看到的结果相同。糟糕。


2
http://floating-point-gui.de/ - Carl Norum
3
啊,浮点数算术。一块纯粹的准确性和可靠性的堡垒。 - Perception
1
我知道浮点数不是精确的。然而,我期望它们的不精确性是一致的。 - mark
3
你没有使用C++编译器来编译C代码,因此你不会使用重载版本的pow函数,即float pow(float base, float exponent)吧? - AusCBloke
2
我使用VS2005进行了测试,C的结果是相同的(实际上使用的是C89编译器)……直到我针对x64目标,此时会使用movss代替fld指令,然后你就会得到Java的行为。编程的乐趣所在。 - Ed S.
显示剩余4条评论
2个回答

16

正如pst和trutheality已经明智地指出的那样,在乘法之前,C将float提升为double。实际上,它们在推入堆栈时被提升为80位扩展精度值。以下是汇编输出(VS2005 x86 C89)。

    double b = a * a;
00411397  fld         dword ptr [a] 
0041139A  fmul        dword ptr [a] 
0041139D  fstp        qword ptr [b] 

FLD指令

FLD指令将32位、64位或80位浮点值加载到堆栈上。在将值推入浮点堆栈之前,此指令将32位和64位操作数转换为80位扩展精度值


有趣的是,如果我以x64为目标构建,将会使用movss指令,并且您将得到一个值为0.779281497001648的结果,即在java示例中看到的内容。试一下吧。


2
现在,在C标准中(不要忘记指定哪个草案!)有一个模糊的声明,可以被解释为对两个实现定义行为的规定。;-) - user166390
@pst:出于好奇尝试将其定向为x64,您会看到Java行为,因为fld指令不再使用(至少在我的设置中)。 - Ed S.
1
我想象了一些狡猾的事情,但不是这么狡猾。我希望我能再次点赞。 - user166390

10

Java为什么而存在

double b = a * a;

a * a 作为 (32位) float 进行乘法运算,然后在赋值给 b 时将结果转换为 (64位) double

b = Math.pow(a,2);

首先将a转换为(64位)double,然后对其进行平方(因为Math.pow的参数是double,double)。

令人困惑的是(对我而言),为什么C语言好像在第一时间就将a转换为double

double b = a * a;

这在标准中吗?

编辑: 我模糊地记得C语言并不要求对于数字使用多少位来实现(具体到位数),这就是这里发生的事情吗?你的float是64位吗?(在Java中,float始终为32位,double始终为64位)。

编辑: Ed S.的回答和mark的评论表明,C语言的结果取决于实现和架构。


我在想是否还会发生编译时评估(优化)呢? - user166390
尽管我怀疑在现代机器/编译器上sizeof(float) == sizeof(double) ;-) - user166390
在我的机器上,当它们被推送到堆栈上时就会被提升。请参见:FLD指令 - Ed S.

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