cosf(M_PI_2)不返回零

时间:2022-07-06 07:22:42

This started suddenly today morning.

这是今天早上突然发生的。

Original lines were this

原来的线是这样的

float angle = (x+90)*(M_PI/180.0);
float xx = cosf(angle);
float yy = sinf(angle);

After putting a breakpoint and hovering cursor.. I get the correct answer for yy as 1. but xx is NOT zero.

在放置断点并悬停游标之后。我得到了yy = 1的正确答案。但是xx不是零。

I tried with cosf(M_PI_2); still no luck.. it was working fine till yesterday.. I did not change any compiler setting etc..

我试着与cosf(M_PI_2);仍然没有运气. .到昨天为止一切都很好。我没有改变任何编译器设置等。

I am using Xcode latest version as of todays date

我正在使用Xcode最新版的todays日期。

6 个解决方案

#1


9  

Contrary to what others have said, this is not an x87 co-processor issue. XCode uses SSE for floating-point computation on Intel by default (except for long double arithmetic).

与其他人所说的相反,这不是x87共同处理器的问题。XCode默认使用SSE在Intel上进行浮点运算(长双算术除外)。

The "problem" is: when you write cosf(M_PI_2), you are actually telling the XCode compiler (gcc or llvm-gcc or clang) to do the following:

“问题”是:当您编写cosf(M_PI_2)时,实际上是在告诉XCode编译器(gcc或llvm-gcc或clang)执行以下操作:

  1. Look up the expansion of M_PI_2 in <math.h>. Per the POSIX standard, it is a double precision literal that converts to the correctly rounded value of π/2.
  2. 中查找M_PI_2的展开。/ POSIX标准,这是一个双精度文字转换到正确的π/ 2的价值。
  3. Round the converted double precision value to single precision.
  4. 将转换后的双精度值改为单精度。
  5. Call the math library function cosf on the single precision value.
  6. 在单个精度值上调用数学库函数cosf。

Note that, throughout this process, you are not operating on the actual value of π/2. You are instead operating on that value rounded to a representable floating-point number. While cos(π/2) is exactly zero, you are not telling the compiler to do that computation. You are instead telling the compiler to do cos(π/2 + tiny), where tiny is the difference between the rounded value (float)M_PI_2 and the (unrepresentable) exact value of π/2. If cos is computed with no error at all, the result of cos(π/2 + tiny) is approximately -tiny. If it returned zero, that would be an error.

注意,在整个过程中,你没有操作π/ 2的实际价值。相反,您将对该值进行操作,该值四舍五入为一个可表示的浮点数。虽然因为(π/ 2)就是零,你不是告诉编译器做计算。你不是告诉编译器做cos(π/ 2 +小),在小圆之间的差异值(浮动)M_PI_2和(unrepresentable)π/ 2的精确值。如果因为没有计算错误,结果因为(π/ 2 +小)大约是微型。如果它返回0,那将是一个错误。

edit: a step-by-step expansion of the computation on an Intel mac with the current XCode compiler:

编辑:使用当前的XCode编译器在Intel mac上逐步扩展计算:

M_PI_2 is defined to be

M_PI_2定义为。

1.57079632679489661923132169163975144

but that's not actually a representable double precision number. When the compiler converts it to a double precision value it becomes exactly

但这并不是一个可表示的双精度数。当编译器将它转换为双精度值时,它就变成了精确值

1.5707963267948965579989817342720925807952880859375

This is the closest double-precision number to π/2, but it differs from the actual mathematical value of π/2 by about 6.12*10^(-17).

这是最近的双精度数π/ 2,但它不同于π/ 2的实际数学价值约为6.12 * 10 ^(-17)。

Step (2) rounds this number to single-precision, which changes the value to exactly

步骤(2)将该数字四舍五入为单精度,从而将值更改为准确

1.57079637050628662109375

Which is approximately π/2 + 4.37*10^(-8). When we compute cosf of this number then, we get:

这大约是π/ 2 + 4.37 * 10 ^(8)。当我们计算这个数的cosf时,我们得到:

-0.00000004371138828673792886547744274139404296875

which is very nearly the exact value of cosine evaluated at that point:

这几乎是cos在这一点上的值

-0.00000004371139000186241438857289400265215231661...

In fact, it is the correctly rounded result; there is no value that the computation could have returned that would be more accurate. The only error here is that the computation that you asked the compiler to perform is different from the computation that you thought you were asking it to do.

事实上,它是正确的全面的结果;计算结果不会返回更准确的值。这里唯一的错误是您要求编译器执行的计算与您认为您要求它执行的计算是不同的。

#2


10  

The first thing to notice is that you're using floats. These are inherently inaccurate, and for most calculations give you only a close approximation of the mathematically-correct answer. Assuming that x in your code has value 0, angle will have a close approximation to π/2. xx will therefore have an approximation to cos(π/2). However, this is unlikely to be exactly zero due to approximation and rounding issues.

首先要注意的是你使用的是浮点数。这些问题本质上是不准确的,而且对于大多数计算,你只能得到数学上正确答案的近似。假设x代码值0,角有近似的π/ 2。xx将因此有近似cos(π/ 2)。然而,由于近似和舍入问题,这个值不太可能是零。

If you were able to change your code to us doubles rather than floats you're likely to get more accuracy, and an answer nearer zero. However, if it is important for your code to produce a value of exactly zero at this point, you're going to have to rethink how you're doing the calculations.

如果你能将你的代码转换成我们的双打而不是浮点数,你很可能会得到更精确的结果,而且答案接近零。但是,如果您的代码在此时生成的值恰好为零是很重要的,那么您将不得不重新考虑如何进行计算。

If this doesn't answer your particular problem, give us some more details and we'll have another think.

如果这不能解决你的问题,给我们更多的细节,我们会有另一个想法。

#3


6  

I suspect the answer is as near as damnit to 0 as not to be worth worrying about.

我怀疑答案几乎是0,不值得担心。

If i run the same thing through I get the answer "-4.3711388e-008" which can also be written as "-0.000000043711388". Which is pretty damned close to 0. Definitely near enough to not worry about it being out at the 8th decimal place.

如果我运行同样的程序,我得到的答案是“-4.3711388e-008”,也可以写成“-0.000000043711388”。非常接近0。很接近,不用担心它在小数点后8位。

Edit: Further to what LiraLuna is saying I wrote the following piece of x87 assembler under visual studio

编辑:进一步说明LiraLuna所说的,我在visual studio下编写了以下x87汇编程序

    float fRes;
_asm
{
    fld1
    fld1
    fadd st, st(1)
    fldpi
    fdiv st, st(1)
    fcos
    fstp [fRes]
}
char str[16];
sprintf( str, "%f", fRes );

Basically this uses the x87's fcos instruction to do a cosine of pi/2. the value held in str is "0.000000"

基本上这个用x87的fcos指令做cos /2。str中的值为“0.000000”

This, however, is not actually what fcos returned. It ACTUALLY returned 6.1230318e-017. This implies that the error occurs at the 17th decimal place and, lets be honest, thats far less significant than the standard debug cosf above.

然而,这并不是fcos返回的结果。它实际上返回6.1230318 e - 017。这意味着错误出现在第17位小数,老实说,这远没有上面标准的debug cosf那么重要。

As SSE3 has no specific cosine instruction I suspect (though i cannot confirm without seeing the assembler generated) that it is either using its own taylor series expansion or it is using the fcos instruction anyway. Either way you are still unlikely to get better precision than the error occurring at the 17th decimal place, in my opinion.

由于SSE3没有特定的余弦指令,我怀疑(尽管我无法确认没有看到汇编程序生成),它要么是使用自己的泰勒级数展开,要么就是使用fcos指令。无论哪种方法,在我看来,您仍然不太可能获得比17位小数出现的错误更好的精度。

#4


3  

The only thing I can think of is a malicious macro substituion i.e. M_PI_2 is no longer 1.57079632679489661923.

我唯一能想到的就是恶意的宏替换,即M_PI_2不再是1.57079632679489661923。

Try calling cosf( 1.57079632679489661923 ) to test this.

试着打电话给cosf(1.57079632679489661923)来测试一下。

#5


1  

The real thing you should be careful about is the sign of cosine. Make sure it is the same as you expected. E.g. if you operate with angles between 0 and pi/2. make sure that what you use as PI_2 is less that actual value of pi/2!

需要注意的是cos的符号。确保它和你期望的一样。例如,如果你的角度是0到/2。确保作为PI_2使用的值小于/2的实际值!

And the difference between 0.000001 and 0.0 is less than you think.

0。000001和0。0之间的差异比你想象的要小。

#6


0  

The reason

What you are experiencing is the infamous x87 math co-processor float truncate 'bug' - or rather - a feature. IEEE floats have an amazing range of numbers, but at a cost. They sacrifice precession for high range.

您正在体验的是臭名昭著的x87数学协同处理器,它截断了“bug”——或者更确切地说——一个特性。IEEE浮动有一个惊人的数字范围,但这是有代价的。他们为了高射程而牺牲岁差。

They are not inaccurate as you think, though - this is a semi-myth generate by Intel's x87 chip design, that internally uses 80bit internal representation for floats - they have far superior precession though a bit slower.

它们并不像你想的那样不准确——这是因特尔x87芯片设计所产生的半神话,它在内部使用80位的内部表示法来表示浮点数——它们的进动比你想象的要好得多,尽管速度稍慢一些。

When you perform a float comparison, x87 caches the float as an 80bit float, then when it's stack is full, it saves the 32bit representation in RAM, decreasing accuracy by a large degree.

当执行浮点比较时,x87将浮点缓存为80bit浮点数,当栈满时,它将在RAM中保存32位的表示,从而大大降低精度。

The solution

x87 is old, really old. It's replacement is SSE. SSE computes 32bit floats and 64bit floats natively, leading to minimal precession lost on math. Please note that precession issues with floats still exist, but printf("%f\n", cosf(M_PI_2)); should be zero. Heck - even float comparison with SSE is accurate again! (unlike x87).

x87很古老,很古老。替换是上交所。SSE计算32位浮点数和64位浮点数,导致数学上最小的岁差损失。请注意,浮点数的进动问题仍然存在,但是printf(“%f\n”、cosf(M_PI_2));应该是零。见鬼——甚至浮动与SSE的比较也是准确的!(不像x87)。

Since latest Xcode is actually GCC 4.2.1, use the compiler switch -msse3 -mfpmath=sse and see how you get a perfectly round 0.00000 (Note: if you get -0.00000, do not worry, it's perfectly fine and still equals 0.00000 under the IEEE spec (read more at this wikipedia article)).

由于最新的Xcode实际上是GCC 4.2.1,使用编译器开关-msse3 -mfpmath=sse,看看如何得到一个完美的圆形0.00000(注意:如果得到-0.00000,不要担心,它非常好,在IEEE规范下仍然等于0.00000。

All Intel macs are guaranteed to have SSE3 support (OSx86 Macs excluded, if you want to support those, use -msse2).

所有的英特尔mac电脑都保证有SSE3支持(如果你想支持这些,使用-msse2的话,OSx86 mac除外)。

#1


9  

Contrary to what others have said, this is not an x87 co-processor issue. XCode uses SSE for floating-point computation on Intel by default (except for long double arithmetic).

与其他人所说的相反,这不是x87共同处理器的问题。XCode默认使用SSE在Intel上进行浮点运算(长双算术除外)。

The "problem" is: when you write cosf(M_PI_2), you are actually telling the XCode compiler (gcc or llvm-gcc or clang) to do the following:

“问题”是:当您编写cosf(M_PI_2)时,实际上是在告诉XCode编译器(gcc或llvm-gcc或clang)执行以下操作:

  1. Look up the expansion of M_PI_2 in <math.h>. Per the POSIX standard, it is a double precision literal that converts to the correctly rounded value of π/2.
  2. 中查找M_PI_2的展开。/ POSIX标准,这是一个双精度文字转换到正确的π/ 2的价值。
  3. Round the converted double precision value to single precision.
  4. 将转换后的双精度值改为单精度。
  5. Call the math library function cosf on the single precision value.
  6. 在单个精度值上调用数学库函数cosf。

Note that, throughout this process, you are not operating on the actual value of π/2. You are instead operating on that value rounded to a representable floating-point number. While cos(π/2) is exactly zero, you are not telling the compiler to do that computation. You are instead telling the compiler to do cos(π/2 + tiny), where tiny is the difference between the rounded value (float)M_PI_2 and the (unrepresentable) exact value of π/2. If cos is computed with no error at all, the result of cos(π/2 + tiny) is approximately -tiny. If it returned zero, that would be an error.

注意,在整个过程中,你没有操作π/ 2的实际价值。相反,您将对该值进行操作,该值四舍五入为一个可表示的浮点数。虽然因为(π/ 2)就是零,你不是告诉编译器做计算。你不是告诉编译器做cos(π/ 2 +小),在小圆之间的差异值(浮动)M_PI_2和(unrepresentable)π/ 2的精确值。如果因为没有计算错误,结果因为(π/ 2 +小)大约是微型。如果它返回0,那将是一个错误。

edit: a step-by-step expansion of the computation on an Intel mac with the current XCode compiler:

编辑:使用当前的XCode编译器在Intel mac上逐步扩展计算:

M_PI_2 is defined to be

M_PI_2定义为。

1.57079632679489661923132169163975144

but that's not actually a representable double precision number. When the compiler converts it to a double precision value it becomes exactly

但这并不是一个可表示的双精度数。当编译器将它转换为双精度值时,它就变成了精确值

1.5707963267948965579989817342720925807952880859375

This is the closest double-precision number to π/2, but it differs from the actual mathematical value of π/2 by about 6.12*10^(-17).

这是最近的双精度数π/ 2,但它不同于π/ 2的实际数学价值约为6.12 * 10 ^(-17)。

Step (2) rounds this number to single-precision, which changes the value to exactly

步骤(2)将该数字四舍五入为单精度,从而将值更改为准确

1.57079637050628662109375

Which is approximately π/2 + 4.37*10^(-8). When we compute cosf of this number then, we get:

这大约是π/ 2 + 4.37 * 10 ^(8)。当我们计算这个数的cosf时,我们得到:

-0.00000004371138828673792886547744274139404296875

which is very nearly the exact value of cosine evaluated at that point:

这几乎是cos在这一点上的值

-0.00000004371139000186241438857289400265215231661...

In fact, it is the correctly rounded result; there is no value that the computation could have returned that would be more accurate. The only error here is that the computation that you asked the compiler to perform is different from the computation that you thought you were asking it to do.

事实上,它是正确的全面的结果;计算结果不会返回更准确的值。这里唯一的错误是您要求编译器执行的计算与您认为您要求它执行的计算是不同的。

#2


10  

The first thing to notice is that you're using floats. These are inherently inaccurate, and for most calculations give you only a close approximation of the mathematically-correct answer. Assuming that x in your code has value 0, angle will have a close approximation to π/2. xx will therefore have an approximation to cos(π/2). However, this is unlikely to be exactly zero due to approximation and rounding issues.

首先要注意的是你使用的是浮点数。这些问题本质上是不准确的,而且对于大多数计算,你只能得到数学上正确答案的近似。假设x代码值0,角有近似的π/ 2。xx将因此有近似cos(π/ 2)。然而,由于近似和舍入问题,这个值不太可能是零。

If you were able to change your code to us doubles rather than floats you're likely to get more accuracy, and an answer nearer zero. However, if it is important for your code to produce a value of exactly zero at this point, you're going to have to rethink how you're doing the calculations.

如果你能将你的代码转换成我们的双打而不是浮点数,你很可能会得到更精确的结果,而且答案接近零。但是,如果您的代码在此时生成的值恰好为零是很重要的,那么您将不得不重新考虑如何进行计算。

If this doesn't answer your particular problem, give us some more details and we'll have another think.

如果这不能解决你的问题,给我们更多的细节,我们会有另一个想法。

#3


6  

I suspect the answer is as near as damnit to 0 as not to be worth worrying about.

我怀疑答案几乎是0,不值得担心。

If i run the same thing through I get the answer "-4.3711388e-008" which can also be written as "-0.000000043711388". Which is pretty damned close to 0. Definitely near enough to not worry about it being out at the 8th decimal place.

如果我运行同样的程序,我得到的答案是“-4.3711388e-008”,也可以写成“-0.000000043711388”。非常接近0。很接近,不用担心它在小数点后8位。

Edit: Further to what LiraLuna is saying I wrote the following piece of x87 assembler under visual studio

编辑:进一步说明LiraLuna所说的,我在visual studio下编写了以下x87汇编程序

    float fRes;
_asm
{
    fld1
    fld1
    fadd st, st(1)
    fldpi
    fdiv st, st(1)
    fcos
    fstp [fRes]
}
char str[16];
sprintf( str, "%f", fRes );

Basically this uses the x87's fcos instruction to do a cosine of pi/2. the value held in str is "0.000000"

基本上这个用x87的fcos指令做cos /2。str中的值为“0.000000”

This, however, is not actually what fcos returned. It ACTUALLY returned 6.1230318e-017. This implies that the error occurs at the 17th decimal place and, lets be honest, thats far less significant than the standard debug cosf above.

然而,这并不是fcos返回的结果。它实际上返回6.1230318 e - 017。这意味着错误出现在第17位小数,老实说,这远没有上面标准的debug cosf那么重要。

As SSE3 has no specific cosine instruction I suspect (though i cannot confirm without seeing the assembler generated) that it is either using its own taylor series expansion or it is using the fcos instruction anyway. Either way you are still unlikely to get better precision than the error occurring at the 17th decimal place, in my opinion.

由于SSE3没有特定的余弦指令,我怀疑(尽管我无法确认没有看到汇编程序生成),它要么是使用自己的泰勒级数展开,要么就是使用fcos指令。无论哪种方法,在我看来,您仍然不太可能获得比17位小数出现的错误更好的精度。

#4


3  

The only thing I can think of is a malicious macro substituion i.e. M_PI_2 is no longer 1.57079632679489661923.

我唯一能想到的就是恶意的宏替换,即M_PI_2不再是1.57079632679489661923。

Try calling cosf( 1.57079632679489661923 ) to test this.

试着打电话给cosf(1.57079632679489661923)来测试一下。

#5


1  

The real thing you should be careful about is the sign of cosine. Make sure it is the same as you expected. E.g. if you operate with angles between 0 and pi/2. make sure that what you use as PI_2 is less that actual value of pi/2!

需要注意的是cos的符号。确保它和你期望的一样。例如,如果你的角度是0到/2。确保作为PI_2使用的值小于/2的实际值!

And the difference between 0.000001 and 0.0 is less than you think.

0。000001和0。0之间的差异比你想象的要小。

#6


0  

The reason

What you are experiencing is the infamous x87 math co-processor float truncate 'bug' - or rather - a feature. IEEE floats have an amazing range of numbers, but at a cost. They sacrifice precession for high range.

您正在体验的是臭名昭著的x87数学协同处理器,它截断了“bug”——或者更确切地说——一个特性。IEEE浮动有一个惊人的数字范围,但这是有代价的。他们为了高射程而牺牲岁差。

They are not inaccurate as you think, though - this is a semi-myth generate by Intel's x87 chip design, that internally uses 80bit internal representation for floats - they have far superior precession though a bit slower.

它们并不像你想的那样不准确——这是因特尔x87芯片设计所产生的半神话,它在内部使用80位的内部表示法来表示浮点数——它们的进动比你想象的要好得多,尽管速度稍慢一些。

When you perform a float comparison, x87 caches the float as an 80bit float, then when it's stack is full, it saves the 32bit representation in RAM, decreasing accuracy by a large degree.

当执行浮点比较时,x87将浮点缓存为80bit浮点数,当栈满时,它将在RAM中保存32位的表示,从而大大降低精度。

The solution

x87 is old, really old. It's replacement is SSE. SSE computes 32bit floats and 64bit floats natively, leading to minimal precession lost on math. Please note that precession issues with floats still exist, but printf("%f\n", cosf(M_PI_2)); should be zero. Heck - even float comparison with SSE is accurate again! (unlike x87).

x87很古老,很古老。替换是上交所。SSE计算32位浮点数和64位浮点数,导致数学上最小的岁差损失。请注意,浮点数的进动问题仍然存在,但是printf(“%f\n”、cosf(M_PI_2));应该是零。见鬼——甚至浮动与SSE的比较也是准确的!(不像x87)。

Since latest Xcode is actually GCC 4.2.1, use the compiler switch -msse3 -mfpmath=sse and see how you get a perfectly round 0.00000 (Note: if you get -0.00000, do not worry, it's perfectly fine and still equals 0.00000 under the IEEE spec (read more at this wikipedia article)).

由于最新的Xcode实际上是GCC 4.2.1,使用编译器开关-msse3 -mfpmath=sse,看看如何得到一个完美的圆形0.00000(注意:如果得到-0.00000,不要担心,它非常好,在IEEE规范下仍然等于0.00000。

All Intel macs are guaranteed to have SSE3 support (OSx86 Macs excluded, if you want to support those, use -msse2).

所有的英特尔mac电脑都保证有SSE3支持(如果你想支持这些,使用-msse2的话,OSx86 mac除外)。