C++ fpu中的小数值计算错误?

C++ fpu中的小数值计算错误?,c++,assembly,x86,fpu,x87,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

嗨,我想用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无法计算吗?

更新:,您在整个输入范围内都得到了错误的数字,因此您可能只是在实现公式时有一个常规错误,而不是FP特定的舍入错误或数值精度/稳定性类型的问题。在调试器中单步执行函数,查找给出错误答案的输入,并查看寄存器值

或者更好的是,用标量AVX指令重写它,因为标量AVX比x87更容易,所以工作标量实现是更好的起点。对于
sin()
,调用向量化的
sin()
实现,或者让gcc使用
-O3-ffast math
自动向量化函数。 (请参阅:glibc具有矢量化的数学库函数。)

如果您最终想要快速运行,那么使用slow
fsin
指令从标量x87实现开始可能是最不有用的起点。对于您甚至不打算使用的指令集,好的干净的C语言比草率的asm实现要好。(对于最终的优化版本,在大多数情况下,带有intrinsic的C比手写asm更有意义)。请参阅,以及中的其他链接


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

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

如果您以数组结构格式存储xy对,它将是SIMD友好型的,并且您可以轻松使用8个浮点
x
值和8个浮点匹配
y
值。将方向向量压缩到单个SIMD向量中通常不是最优的;不要被“向量”这个词所愚弄

另请参见,尤其是。这将帮助您了解如何设计程序,以便以后可以在值得的地方使用SIMD对其进行优化。(在开始的时候,你不必矢量化所有的东西,但是改变你的数据布局通常是很多工作,所以首先考虑让你的数据SIMD友好。)< /P>
数值误差的可能来源(事实证明这不是真正的问题)

一个可能的原因:。大多数现代数学库都不使用
fsin
指令来计算
sin
函数,因为它速度不快,而且某些输入的精度很差

此外,根据您构建代码的方式,还有一些(如MSVCRT启动,如果您在Windows上使用的是旧版本)(64位尾数)


你为什么用asm写这个?您是否需要有关如何提高效率的建议?您应该在
st0
中返回
float
作为返回值,而不是通过指针arg存储。另外,不要使用
finit
。我认为您这样做只是因为在加载内容之后,您没有平衡x87堆栈与POP,因此在重复调用之后,您可能会从x87堆栈溢出中得到NAN。在返回
void
的函数中,您仍然返回非空的x87堆栈,因此您仍然做得不对,可能会中断调用方

使用
fstp
fmulp
使堆栈保持平衡。使用
fld st0
代替另一个加载。使用
fmul qword[grav_zrychleni]
代替单独的
fld

或者更好,使用SSE2或AVX进行标量双精度数学。除非您真的想要80位
长双精度

更新:,否则您会在整个输入范围内得到错误的数字,因此您可能只是在实现公式时有一个常规错误,而不是FP特定的舍入错误或数值精度/稳定性类型的问题。在调试器中单步执行函数,查找给出错误答案的输入,并查看寄存器值

或者更好的是,用标量AVX指令重写它,因为标量AVX比x87更容易,所以工作标量实现是更好的起点。对于
sin()
,调用向量化的
sin()
实现,或者让gcc使用
-O3-ffast math
自动向量化函数。 (请参阅:glibc具有矢量化的数学库函数。)

如果您最终想要快速运行,那么使用slow
fsin
指令从标量x87实现开始可能是最不有用的起点。对于您甚至不打算使用的指令集,好的干净的C语言比草率的asm实现要好。(对于最终的优化版本,在大多数情况下,带有intrinsic的C比手写asm更有意义)。请参阅,以及中的其他链接


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