ARM64上浮点精度的奇怪问题

1 c++ floating-point arm floating-accuracy arm64

我在ARM64上遇到了一个非常奇怪的浮点精度问题.我有一个非常简单的C++代码,看起来像这样:

float sx = some_float_number_1;
float sy = some_float_number_2;
float ex = some_float_number_3;
float ey = some_float_number_4;
float px = ex;
float py = ey;

float d1 = (ex - sx) * (py - sy);
float d2 = (px - sx) * (ey - sy);

float d = d1 - d2;
float t = (ex - sx) * (py - sy) - (px - sx) * (ey - sy);

//32-bit output: d == t == 0
//64-bit output: d == 0, t != 0
Run Code Online (Sandbox Code Playgroud)

理论上,d应该等于t并且等于0,这正是32位ARM上发生的情况.但由于一些奇怪的原因,在64位ARM上t的输出不等于0,而d仍然是正确的.我从来没有见过这样的bug,所以我不知道是什么原因造成了这种问题.

编辑:更多信息

  • 如果你没有注意到,d和t的输出都应该是0,因为(ex-sx)*(py-sy)等于(px-sx)*(ey-sy)
  • 仅当输入的小数部分不等于0时才会发生此问题.
  • 我正在使用的编译器是包含在Android NDK r15c包中的Clang.

编辑2:这是拆卸

4c: 52933348    mov w8, #0x999a                 // #39322
50: 72a82828    movk    w8, #0x4141, lsl #16
54: b90683e8    str w8, [sp,#1664]
58: 52933348    mov w8, #0x999a                 // #39322
5c: 72a82728    movk    w8, #0x4139, lsl #16
60: b9067fe8    str w8, [sp,#1660]
64: 52933348    mov w8, #0x999a                 // #39322
68: 72a838a8    movk    w8, #0x41c5, lsl #16
6c: b9067be8    str w8, [sp,#1656]
70: 529999a8    mov w8, #0xcccd                 // #52429
74: 72a855e8    movk    w8, #0x42af, lsl #16
78: b90677e8    str w8, [sp,#1652]
7c: bd467be0    ldr s0, [sp,#1656]
80: bd0673e0    str s0, [sp,#1648]
84: bd4677e0    ldr s0, [sp,#1652]
88: bd066fe0    str s0, [sp,#1644]
8c: bd467be0    ldr s0, [sp,#1656]
90: bd4683e1    ldr s1, [sp,#1664]
94: 1e213800    fsub    s0, s0, s1
98: bd466fe1    ldr s1, [sp,#1644]
9c: bd467fe2    ldr s2, [sp,#1660]
a0: 1e223821    fsub    s1, s1, s2
a4: 1e210800    fmul    s0, s0, s1
a8: bd066be0    str s0, [sp,#1640]
ac: bd4673e0    ldr s0, [sp,#1648]
b0: bd4683e1    ldr s1, [sp,#1664]
b4: 1e213800    fsub    s0, s0, s1
b8: bd4677e1    ldr s1, [sp,#1652]
bc: bd467fe2    ldr s2, [sp,#1660]
c0: 1e223821    fsub    s1, s1, s2
c4: 1e210800    fmul    s0, s0, s1
c8: bd0667e0    str s0, [sp,#1636]
cc: bd466be0    ldr s0, [sp,#1640]
d0: bd4667e1    ldr s1, [sp,#1636]
d4: 1e213800    fsub    s0, s0, s1
d8: bd0663e0    str s0, [sp,#1632]
dc: bd467be0    ldr s0, [sp,#1656]
e0: bd4683e1    ldr s1, [sp,#1664]
e4: 1e213800    fsub    s0, s0, s1
e8: bd466fe2    ldr s2, [sp,#1644]
ec: bd467fe3    ldr s3, [sp,#1660]
f0: 1e233842    fsub    s2, s2, s3
f4: bd4673e4    ldr s4, [sp,#1648]
f8: 1e243821    fsub    s1, s1, s4
fc: bd4677e4    ldr s4, [sp,#1652]
100:    1e233883    fsub    s3, s4, s3
104:    1e230821    fmul    s1, s1, s3
108:    1f020400    fmadd   s0, s0, s2, s1
10c:    bd065fe0    str s0, [sp,#1628]
Run Code Online (Sandbox Code Playgroud)

Eri*_*hil 5

C++标准允许实现以比名义上具有的类型更精确的方式评估浮点表达式.它需要实现在将值分配给对象时丢弃多余的精度.

因此,在分配给d1d2,过度精度被丢弃并且没有贡献d1 - d2,但是,在(ex - sx) * (py - sy) - (px - sx) * (ey - sy)过度精度中参与评估.请注意,C++不仅允许在评估中使用过多的精度,而且允许它用于表达式的某些部分而不是其他部分.

特别是,评估表达式的常用方法a*b - c*d-c*d使用乘法指令(不使用过多精度)进行计算a*b - c*d,然后使用融合乘法 - 加法指令进行计算,该指令有效地使用无限精度进行乘法运算.

您的编译器可能有一个开关来禁用此行为,并始终使用标称精度.