fpu中的小数字计算错误?

时间:2017-12-02 10:49:36

标签: c++ assembly x86 fpu x87

嗨,我想在fpu中做这个公式。

(y = v*t*sin(a) - 0.5*g*t^2) 

我在c ++中的代码是:

typedef void(*Ipa_algorithm2)(double t, double alfa, double *return_value);
Ipa_algorithm2 count_y;
count_y = (Ipa_algorithm2)GetProcAddress(hInstLibrary, "ipa_algorithm");
t = t + 0.01; //going from cas = 0
(*count_y)(t,camera.angleY, &y); //t = cas; 

我在asm中的代码是:

section .data
help_var dq 0 
speed dq 40.0 ;v = rychlost
number dq 180.0
grav dq 4.906865 ;grav= 0,5*g

ipa_algorithm2:
push ebp
mov ebp, esp
finit
fld qword [speed]
fld qword [ebp+8]
fmul st1
fstp qword [help_var] ;v pomocny je v*t
fldpi               
fld qword [ebp+16]  ;na st0 je uhel a na st1 3,14
fmul st1 ;na st0 je uhel * 3,14
fld qword [number]
fxch st1 ;na st0 je uhel*3,14 na st1 je 180
fdiv st1 ;na st0 je uhel v radianech
fsin
fld qword [help_var]
fmul st1 ;na st0 je v*t*sin uhlu
fst qword [help_var]
finit
fld qword [ebp+8]
fld qword [ebp+8]
fmul st1
fld qword [grav]
fmul st1
fld qword [help_var]
fxch st1
fsub st1


mov eax,[ebp+24]
fstp qword [eax]    

mov esp, ebp
pop ebp
ret 0

问题是,函数ipa_algorithm2从一开始就给我正确的数字(与在C中做同样的程序的输出相比),但经过几个步骤后,结果开始变得越来越糟。我正在检查代码3个小时,我没有发现任何错误。我指望的数字是否可能很小,以至于fpu不能用它们计算?

1 个答案:

答案 0 :(得分:3)

更新:according to a comment,您输入的所有输入数据都是错误的,因此您可能只是在执行公式时遇到常规错误,而不是FP特定的舍入错误或数值准确性/稳定型问题。在调试器中将您的函数单步执行,以获得给出错误答案的输入,并查看寄存器值。

或者更好的是,使用标量AVX指令重写它,因为标量AVX比x87和you eventually want a vectorized AVX implementation anyway更容易,因此工作标量实现是一个更好的起点。对于sin(),请调用矢量化sin()实现,或者让gcc使用-O3 -ffast-math自动向量化您的函数。  (参见https://sourceware.org/glibc/wiki/libmvec:glibc具有矢量化数学库函数。)

如果您最终想要快速运行的东西,那么使用慢速fsin指令从标量x87实现开始可能是最不实用的起点。对于你甚至不会使用的指令集,良好的干净C会比草率的asm实现更好。 (对于最终的优化版本,在大多数情况下,带有内在函数的C比手写的asm更有意义)。请参阅http://agner.org/optimize/中的x86 tag wiki和其他链接。

在游戏中存储角度

将方向存储为[x,y]个向量,而不是以弧度为单位的角度。 (或学位)。使用归一化的xy向量,添加两个角度成为2x2矩阵乘法(通过旋转矩阵)。但是sin变得微不足道:如果你将矢量标准化(x^2 + y^2 = 1.0),那么sin(angle) = angle.y

尽可能避免使用实际角度,而是使用标准化向量。您有时需要atan2,但通常很少,您只能使用普通库版本。

如果以数组结构格式存储xy对,它将是SIMD友好的,并且您可以轻松地执行8个浮点x值和8个浮点匹配y值的内容。使用打包到单个SIMD向量中的方向向量进行操作通常 NOT 最优;不要被“#34; vector"”这个词所迷惑。

另请参阅 https://stackoverflow.com/tags/sse/info,尤其是SIMD at Insomniac Games (GDC 2015)。这将有助于您了解如何设计程序,以便以后可以在有价值的地方使用SIMD对其进行优化。 (您不必 在开始时对所有内容进行矢量化,但更改数据布局通常需要做很多工作,因此请考虑首先使数据与SIMD保持友好关系。)

数字错误的可能来源(原来这不是真正的问题)

一个可能的原因:The worst-case error for the fsin instruction for small inputs is actually about 1.37 quintillion units in the last place, leaving fewer than four bits correct.。大多数现代数学图书馆都没有使用fsin指令来计算sin函数,因为它不快并且对某些输入的准确性较差。

此外,根据您构建代码的方式,某些内容(如MSVCRT启动,如果您使用的是Windows并使用旧版本)may have set the x87 FPU to less than 80-bit precision(64位尾数)。

你为什么要在asm中写这个?您是否想要如何提高效率的建议?您应该在float中返回st0作为返回值,而不是通过指针arg存储。另外,请勿使用finit。我认为你只是这样做,因为你在加载东西后没有平衡x87堆栈和pop,所以在反复调用之后你会从x87堆栈溢出中获得NaNs。您仍然在返回void的函数中返回x87堆栈非空,因此您仍然做错了并且可能会破坏调用者。

使用fstpfmulp让堆栈保持平衡。使用fld st0代替其他负载。使用fmul qword [grav_zrychleni]代替单独的fld

或者更好,使用SSE2或AVX进行标量双精度数学运算。除非你真的想要80位long double