C语言 在展开的for循环之间的汇编差异导致不同的float结果



考虑以下设置:

typedef struct
{
float d;
} InnerStruct;
typedef struct
{
InnerStruct **c;
} OuterStruct;

float TestFunc(OuterStruct *b)
{
float a = 0.0f;
for (int i = 0; i < 8; i++)
a += b->c[i]->d;
return a;
}
TestFunc中的for循环完全复制了我正在测试的另一个函数中的一个。这两个循环都是由gcc(4.9.2)展开的,但在这样做之后产生的汇编略有不同。

我的测试循环的汇编:

lwz       r9,-0x725C(r13)                   lwz       r9,0x4(r3)    
lwz       r8,0x4(r9)                        lwz       r8,0x8(r9)    
lwz       r10,0x0(r9)                       lwz       r10,0x4(r9)   
lwz       r11,0x8(r9)                       lwz       r11,0x0C(r9)  
lwz       r4,0x4(r8)                        lwz       r3,0x4(r8)    
lwz       r10,0x4(r10)                      lwz       r10,0x4(r10)  
lwz       r8,0x4(r11)                       lwz       r0,0x4(r11)   
lwz       r11,0x0C(r9)                      lwz       r11,0x10(r9)  
efsadd    r4,r4,r10                         efsadd    r3,r3,r10
lwz       r10,0x10(r9)                      lwz       r8,0x14(r9)   
lwz       r7,0x4(r11)                       lwz       r10,0x4(r11)  
lwz       r11,0x14(r9)                      lwz       r11,0x18(r9)  
efsadd    r4,r4,r8                          efsadd    r3,r3,r0
lwz       r8,0x4(r10)                       lwz       r0,0x4(r8)    
lwz       r10,0x4(r11)                      lwz       r8,0x0(r9)    
lwz       r11,0x18(r9)                      lwz       r11,0x4(r11)  
efsadd    r4,r4,r7                          efsadd    r3,r3,r10
lwz       r9,0x1C(r9)                       lwz       r10,0x1C(r9)  
lwz       r11,0x4(r11)                      lwz       r9,0x4(r8)    
lwz       r9,0x4(r9)                        efsadd    r3,r3,r0
efsadd    r4,r4,r8                          lwz       r0,0x4(r10)   
efsadd    r4,r4,r10                         efsadd    r3,r3,r11
efsadd    r4,r4,r11                         efsadd    r3,r3,r9
efsadd    r4,r4,r9                          efsadd    r3,r3,r0

问题是这些指令返回的浮点值不完全相同。我不能改变原来的循环。我需要以某种方式修改测试循环以返回相同的值。我相信测试的组装等同于一个接一个地添加每个元素。我不太熟悉汇编,所以我不确定上面的差异是如何转化为c的。我知道这是问题,因为如果我在循环中添加一个print,它们不会展开,结果与预期的完全匹配。

我认为这是为了用一个函数对另一个函数进行单元测试。

一般来说,在C或c++中,浮点计算从来都不是精确的,期望它们是精确的通常被认为是不合理的。

Java语言标准要求精确的浮点结果。这样做一直是对Java的憎恨之源,有各种指责说,使结果可重现通常会降低结果的准确性,有时还会使代码变得更慢。

如果你正在用C或c++进行测试,那么我建议使用这种方法:

尽可能地计算结果,同时具有高精度和高精度。在这种情况下,输入数据是32位浮点数,因此在计算预期结果之前将它们全部转换为64位浮点数。

如果输入是double类型(并且没有更大的long double类型),那么将值按顺序排序并将它们从最小到最大相加。这将导致精度损失最小。

一旦你得到了预期的结果,然后测试函数输出是否在一定范围内匹配。

有两种方法可以设置测试通过所需的准确度:

一种方法是检查数字的实际物理含义是什么,以及你实际需要的精度是多少。

另一种方法是只要求结果精确到理想结果的几个最低有效位之内,即:误差小于理想结果乘以FLT_EPSILON的几倍。

禁用快速数学似乎解决了这个问题。感谢@njuffa的建议。我希望能够围绕这个优化设计测试函数,但这似乎是不可能的。至少我现在知道问题是什么了。感谢大家对这个问题的帮助!

最新更新