Java vs C 浮点:“x * x”与“pow(x,2)”不同?

2022-09-02 21:15:58

为什么会这样?与C甚至Java Math.pow方法相比,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中看到的结果。呸。


答案 1

正如 pst 和 trutheality 已经明智地指出的那样,C 正在将 a 提升到乘法之前。实际上,当它们被推送到堆栈上时,它们会被提升为80位扩展精度值。这是汇编程序输出(VS2005 x86 C89)floatdouble

    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,则使用该指令,并且您获得一个值作为结果,即您在java示例中看到的内容。试一试。movss0.779281497001648


答案 2

Java 的作用

double b = a * a;

首先乘以 (32 位),并在赋值给 时将结果转换为 (64 位)。a * afloatdoubleb

b = Math.pow(a,2);

首先转换为 (64 位) (因为 的参数是 ),然后将其平方。adoubleMath.powdouble, double

令人费解的是(对我来说)为什么C似乎把's投到第一位adouble

double b = a * a;

这在标准中吗?

编辑:我依稀记得C不需要特定的实现(就使用多少位而言)来表示数字......这是怎么回事?您的浮子是 64 位吗?(在Java中,浮点数始终为32位,双精度值始终为64位)。

编辑:Ed S.的答案和mark的评论,即不同的编译器给出不同的结果,这表明C结果是特定于实现和架构的。