修改:有关答案的更新,请参阅问题的结尾。
我花了几周时间跟踪一个软件中的一个非常奇怪的错误 保持。长话短说,有一个旧的软件 分发,以及需要匹配输出的新软件 旧。这两个人(在理论上)依赖于一个共同的图书馆。[1]但是,我不能 复制原始版本库生成的结果, 即使两个版本的库的源匹配。实际上 有问题的代码非常简单。原始版本看起来像这样( "伏都"评论不是我的:[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 ...
}
我所包含的最后一个陈述是不同行为的出现。在里面
原始程序rstr[8]
具有值101325.
,并在将其转换为之后
double
[3]并对其进行分配,p1
的值为101324.65625
。同样,tt
最终得到值373.149999999996
。我用这个确认了这些值
调试打印和检查调试器中的值(包括检查)
十六进制值)。这在任何意义上都不足为奇,它与预期的一样
浮点值。
在围绕同一版本库(以及任何调用)的测试包装器中
到重构版本的库),第一个任务(到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();
}
出于绝望,我甚至煞费苦心地看着 由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]
由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]
我能看到的唯一区别,除了内存中存储数组的位置和
这个程序到底发生了多远,是领先的_
在第二个中引用rstr
。通常,VC6使用前导下划线
使用函数进行名称修改,但我找不到它的任何文档
使用数组指针进行名称修改。我也无法理解为什么这些会产生
在任何情况下都会有不同的结果,除非涉及到名称错误
以不同的方式读取从指针访问的数据。
我可以在两者之间找出唯一的其他区别(除了打电话 context)是原来是一个基于MFC的Win32应用程序,而 后者是非MFC控制台应用程序。另外两个配置了 同样的方式,它们是用相同的编译标志构建的 相同的C运行时。
我们非常感谢任何建议。
编辑:正如several answers非常有帮助地指出的那样,解决方案是检查二进制/十六进制值并对它们进行比较,以确保我认为的是事实上完全相同相同。事实证明并非如此 - 尽管如此,我仍然采取强烈抗议。
在这里,我可以吃一些不起眼的馅饼,并承认,当我思考我已经检查了这些值时,我实际上已经检查了一些其他的,密切相关的值 - 这一点我发现只有当我去的时候回来再看看数据。事实证明,rstr[8]
中设置的值非常略有不同,因此转换为double会突出显示非常小的差异,然后这些差异会在整个程序中传播我注意到的方式。
初始化的差异我可以根据两个程序的工作方式来解释。具体来说,在一种情况下rstr[8]
是基于GUI的用户输入指定的(在这种情况下也是转换计算的结果),而在另一种情况下,它是从已经存在的文件中读入的存储有一些精度损失。有趣的是,在任何一种情况下,它实际上都不是完全 101325.0
,即使是从文件中读取它的情况,它也被存储为1.01325e5
。
这将教会我仔细检查我对这些事情的双重检查。非常感谢Eric Postpischil和unwind提示我再次检查并及时提供反馈。它非常很有帮助。
#include
和。{
通过extern
语句引用的函数。我把它修好了
重构版本的库实际上是一个库,但请参阅
其余的问题。/* voooooodoooooooooo */
评论中,因为它说明了......
不寻常......我的前任的编程实践。我认为那个元素是
因为这最初是从Fortran和开发人员翻译而来的
曾经用它来处理某种内存错误。这条线有
对代码的实际行为没有任何影响。答案 0 :(得分:6)
此:
在原始程序中,
rstr[8]
的值为101325.,在将其转换为double[3]
并分配后,p1的值为101324.65625
暗示float
值实际上并非完全是101325.0,因此当您转换为double
时,您会看到更多精度。我会(高度)怀疑你在检查float
值,自动(隐式和静默)舍入时使用浮点数非常常见的方法。检查位模式并使用系统中已知的浮点格式对其进行解码,以确保您不会被欺骗。
答案 1 :(得分:4)
可能性是:
rstr[8]
在分配到p1
之前的原始程序中的值为101324.65625,而不是报告的101325. p1
在作业后立即没有值101324.65625。double
)。要测试1,仔细在作业之前立即检查rstr[8]
的值。我建议:
rstr[8]
的字节,然后解释IEEE-754 64位二进制格式的字节,或此外,我建议通过将值101324.65625注入rstr[8]
(通过赋值或调试器)并以与上面使用的相同方式显示来测试是否足够好地显示浮点值。
要测试2,仔细在分配后立即检查p1
的值。我建议将上述内容应用于p1
,而不是rstr[8]
。
问题中显示的反汇编代码似乎反驳3.但是,我会考虑这些测试:
答案 2 :(得分:1)
你需要做的(调试明智的)是在旧的和重构的版本之间获得rstr [20]和rstr [8]的二进制值。 tt和p1的二进制值也不会受到影响。这将证明阵列初始化相同。将double赋值给float数组,然后将其转换回double,并不是无损。
我能想到的唯一奇怪的情况是FPU的舍入模式在旧程序和重构程序之间设置不同。检查源代码“_control_fp(”,“fesetround(”或“fenv.h”。
答案 3 :(得分:-2)
浮点的第一个规则是结果是近似值,不应该假设是精确的。
编译器和CPU都能够进行大量优化,优化中的微小差异(包括缺乏操作)可能导致产生的“近似”的微小差异。这包括各种各样的事情,例如执行操作的顺序(例如,不要假设“(x + y)+ z”与“x +(y + z)”相同),如果有任何事情是由编译器完成(例如,常量折叠),如果内联或不内联,等等。
例如,(内部)80x86使用80位“扩展精度”浮点,它比双精度更精确;所以简单地将结果存储为double并再次加载会导致不同的结果重新使用FPU寄存器中已有的(更高精度)值。
我所说的大部分内容是,如果您获得的确切价值非常重要,那么您根本不应该使用浮点数(考虑“大理性”或其他内容)。