【问题标题】:Same simple calculation, different results同样的简单计算,不同的结果
【发布时间】:2014-06-08 20:32:27
【问题描述】:

使用 gcc 4.8.2 (Ubuntu 14.04) 我得到不同的结果,而基本上以相同的方式计算值。根据我测试的系统上的架构(32 位/64 位),也存在差异。

#include <math.h>
#include <stdio.h>

int main()
{
    float h = 0.11f; 
    float y = 0.11f;
    float g = 1.37906f;
    float x = 2.916949f;

    float result1 = (h * y / fabs(g)) / x;

    float result2 = h * y / fabs(g);
    result2 /= x;

    float result3 = (h * y / g) / x;

    printf("%.20f \n", result1); //0.00300796888768672943 
    printf("%.20f \n", result2); //0.00300796912051737309 
    printf("%.20f \n", result3); //0.00300796912051737309 on x64
                                 //0.00300796888768672943 on x32 
}

这是什么原因,我该如何预测或避免这些差异?

编辑:铸造晶圆厂浮动不会改变结果,至少在我的系统上(参见 Oli Charlesworth 的 cmets)。

【问题讨论】:

  • 要求从单精度(23 位)浮点数输出 20 位数字几乎没有意义。
  • 是的,它对精度没有意义,但它显示了差异。请注意,要准确表示浮点数位的值,您可能需要 20 位以上的数字。
  • @Leushenko 任何有限的单精度浮点数精确地表示某个数字,并且有很多理由希望看到该数字的精确十进制表示(这通常意味着打印超过 20十进制数字)。

标签: c gcc floating-point gcc4.8


【解决方案1】:

C99 标准没有像早期 Java 标准那样强制所有 C 编译器为浮点计算实现严格的标准,而是允许在理想模式下进行一些变化,其中每个操作都按照 IEEE 的顺序完成并舍入754格式对应的浮点类型。

您可以询问 GCC 它遵循什么浮点计算模型,并且您可以使用命令行选项来更改其行为并使其更可预测。 有两种情况:

  1. 如果您要生成 387 代码,请使用最新的 GCC(4.8 应该没问题)和-std=c99 进行编译。没有它(特别是没有它所暗示的-fexcess-precision=standard),浮点计算的确切结果是不可预测的,并且您允许编译器为result1result2result3 生成不同的结果)。对于-std=c99result1result3 的值必须相同。 result2 的值可能不同,因为对 result2 的中间赋值会强制将计算点处的值四舍五入为 float
  2. 停止生成 387 代码,改为生成 SSE2 代码(选项-msse2 -mfpmath=sse)。在这种模式下,fabsfabsf 替换的所有三个计算都应该产生相同的结果。这样做的缺点是生成的代码仅与过去 12 年左右生产的处理器兼容(!)

更多信息:post1post2,是从打算为 C 程序编写静态分析器以精确预测浮点计算结果的人的角度编写的。

【讨论】:

    【解决方案2】:

    前两个不同,因为fabs 返回一个double。因此在第一个版本中,x 的除法是以双精度完成的。在第二个版本中,它以单精度完成。

    【讨论】:

    • 谢谢,这解释了第一个区别。有趣的是,当使用我的 GPU (CUDA) 执行第一种方法时,我得到了第二种方法的结果。
    • 嗯,我测试了你的解决方案。将所有晶圆厂的结果转换为浮动时,我得到了相同的结果。
    • @JerrodWilson:这也可能与平台有关(因为中间结果保存在更广泛的寄存器中);参见例如ideone.com/PjlRXC.
    • @Jerrod:演员只是说你知道你在做什么......你需要调用特定的浮点函数 fabsf。
    • @Rob11311:确实如此。但它也不会改变结果。
    【解决方案3】:

    原因是您使用了float 类型,其精度约为 6 位十进制数字。
    结果在前 6 位有效数字内一致。

    【讨论】:

    • 没有。 x + yy + x 其中 xy 是浮点数,不仅需要匹配 6 个十进制数字,它们还必须相同。同样,所有三个计算(一旦fabsfabsf 替换)在某些计算模型中是相同的,并且必须在这些模型中产生相同的结果。你的“6位小数”解释只是放弃理解。此外,导致第 7 位小数差异的机制可能会导致其他计算的第 3 位、第 2 位或第 1 位小数的差异,因此 C 程序员应该了解它们。
    • 对不起,我从未听说过结果必须相同。并且计算不完全相同。正如 Oli Charlesworth 所说,fabs 返回double,因此在计算result1 时,它会强制将所有浮点数提升为双倍。然而在result2 计算中存储了一个中间结果,因此被截断为浮点数,最后的除法是在浮点数中完成的。所以它显然与result1 不同。而result3 只能以浮点数计算 - 所以它也与其他人不同。对汇编语言进行编译并比较所有三个计算的代码。
    • 问题说要为float 添加转换以获取fabs 的结果。一旦进行此修改,对于正确定义 FLT_EVAL_METHOD 为 0 的编译器,所有三个结果都是相同的。对于正确定义 FLT_EVAL_METHOD 为 1 或 2 的编译器,result1 和 result3 的值保证是相同的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-11
    • 2012-09-26
    • 1970-01-01
    • 2013-05-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多