从float到double的转换产生不同的结果 - 相同的代码,相同的编译器,相同的OS

Chr*_*cho 4 c x86 assembly visual-c++-6 disassembly

编辑:有关答案的更新,请参阅问题的结尾.

我花了几周的时间来追踪我维护的一个软件中的一个非常奇怪的错误.简而言之,有一个旧的软件正在分发,还有一个新的软件需要匹配旧的输出.这两个人(在理论上)依赖于一个共同的图书馆.[1] 但是,我无法复制原始版本库生成的结果,即使两个版本的库的源匹配.有问题的实际代码非常简单.原始版本看起来像这样("voodoo"评论不是我的):[2]

// float rstr[101] declared and initialized elsewhere as a global

void my_function() {
    // I have elided several declarations not used until later in the function
    double tt, p1, p2, t2;
    char *ptr;

    ptr = NULL;
    p2 = 0.0;
    t2 = 0.0; /* voooooodoooooooooo */

    tt = (double) rstr[20];
    p1 = (double) rstr[8];

    // The code goes on and does lots of other things ...
}
Run Code Online (Sandbox Code Playgroud)

我所包含的最后一个陈述是不同行为的出现.在原始程序中,rstr[8]具有值101325.,并在将其转换为 double[3]并赋值后,p1具有该值101324.65625.同样,tt 最终得到价值373.149999999996.我已经使用调试打印确认了这些值并检查了调试器中的值(包括检查十六进制值).这在任何意义上都不足为奇,它与浮点值一样可以预期.

在围绕相同版本库的测试包装器中(以及对库的重构版本的任何调用),第一个赋值(to tt)产生相同的结果.但是,p1最终101325.0匹配原始值rstr[8].这种差异虽然很小,但有时会产生很大的变化,这取决于价值p1.

我的测试包装很简单,并且与原始包含模式完全匹配,但是消除了所有其他上下文:

#include "the_header.h"

float rstr[101];
int main() {
    rstr[8] = 101325.;
    rstr[20] = 373.15;

    my_function();
}
Run Code Online (Sandbox Code Playgroud)

出于绝望,我甚至煞费苦心地看着VC6生成的反汇编.

4550:   tt = (double) rstr[20];
0042973F   fld         dword ptr [rstr+50h (006390a8)]
00429745   fstp        qword ptr [ebp-0Ch]
4551:   p1 = (double) rstr[8];
00429748   fld         dword ptr [rstr+20h (00639078)]
0042974E   fstp        qword ptr [ebp-14h]
Run Code Online (Sandbox Code Playgroud)

VC6为测试代码包装器调用的相同库函数生成的版本(与VC6为我的重构版本库生成的版本匹配):

60:       tt = (double) rstr[20];
00408BC8   fld         dword ptr [_rstr+50h (0045bc88)]
00408BCE   fstp        qword ptr [ebp-0Ch]
61:       p1 = (double) rstr[8];
00408BD1   fld         dword ptr [_rstr+20h (0045bc58)]
00408BD7   fstp        qword ptr [ebp-14h]
Run Code Online (Sandbox Code Playgroud)

我能看到的唯一区别是,除了内存中存储数组的位置以及程序发生的程度,这是第二个_引用rstr.一般来说,VC6使用一个前导下划线来对函数进行名称修改,但我找不到任何有关使用数组指针进行名称修改的文档.在任何情况下,我都无法理解为什么这些会产生不同的结果,除非涉及以不同方式读取从指针访问的数据的名称修改.

我可以在两者之间识别的唯一区别(除了调用上下文)是原始的是基于MFC的Win32应用程序,而后者是非MFC控制台应用程序.否则两者以相同的方式配置,并且它们使用相同的编译标志并针对相同的C运行时构建.

任何建议将不胜感激.


编辑:解决方案,正如几个 答案非常有用地指出,是检查二进制/十六进制值并比较它们以确保我认为完全相同的事实上相同的.事实证明并非如此 - 尽管如此,我的强烈抗议却是相反的.

在这里,我可以吃一些不起眼的馅饼,并承认,虽然我认为我已经检查了这些值,但我实际上已经检查了一些其他密切相关的值 - 这一点我发现只有当我再次回顾数据时才发现.事实证明,所设置的值rstr[8]非常略有不同,所以转换为双强调了非常细微的差异,而这些差异然后在刚刚我提到的方式在整个程序中传播.

初始化的差异我可以根据两个程序的工作方式来解释.具体来说,在一种情况下rstr[8],基于用户对GUI的输入(在这种情况下也是转换计算的结果)指定,而在另一种情况下,从存储它的文件中读取它,但有一些损失.精确.有趣的是,在任何一种情况下,它实际上都不是确切的 101325.0,甚至是从存储它的文件中读取它的情况1.01325e5.

这将教会我仔细检查我对这些事情的双重检查.非常感谢埃里克Postpischil放松为促使我再次检查,并为及时反馈.这非常有帮助.


脚注

  1. 实际上,原始的"库"是一个头文件,所有实现都是内联完成的.标头是#include通过extern语句引入的,函数是通过语句引用的.我已经修复了这个实际上是库的库的重构版本,但是请看其余的问题.
  2. 请注意,变量名称不是我的,并且非常糟糕.同样使用全局变量,这在这个软件中很猖獗.我留下了/* voooooodoooooooooo */评论,因为它说明了我前任的......不寻常......编程实践.我认为该元素存在是因为它最初是从Fortran翻译而且开发人员已经将它用作处理某种内存错误的方法.该行对代码的实际行为没有任何影响.
  3. 我很清楚,实际上并不需要在这里进行演员表,但这是原始库的工作方式,我无法修改它.

unw*_*ind 6

这个:

在原始程序中,rstr[8]值为101325.,在将其转换为double[3]并赋值之后,p1具有该值101324.65625

意味着该float值实际上并非正好是101325.0,所以当你转换为double你时,你会看到更多的精度.我会(高度)怀疑你检查float值的方法,自动(隐式和静默)舍入时,浮点数很常见.检查位模式并使用系统中浮动的已知格式对其进行解码,以确保您不会被欺骗.