tags:

views:

394

answers:

6

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.

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

I am using Xcode latest version as of todays date

+3  A: 

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

Try calling cosf( 1.57079632679489661923 ) to test this.

Troubadour
+8  A: 

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.

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.

Tim
`float`s are NOT always inaccurate! Don't mislead the poor programmer. The reason they *are* considered inaccurate for comparison is because of the 80bit to 32bit truncate the *x87 co-processor* does. Today, I will **HIGHLY** advise people to turn on SSE code generation in the code to take advantage of **native float processing** without being afraid of consequences. Unlike the aging x87, SSE/SSE2 are very accurate - not to mention faster. Again, float inaccuracies only happen on x86+x87. (x86_64 does not suffer from that problem due to native SSE adoption).
LiraNuna
Floats are certainly not *always* inaccurate, but they are, as I said, *inherently* inaccurate. Although some values can be represented with perfect accuracy, the sort of values for which they are usually used tend not to be in this set, and some accuracy will be lost in each calculation *irrespective of the type of floating point being used*. Certainly, there are *more* accurate and *less* accurate approaches, but they simply differ in their degree of inaccuracy.
Tim
This is a classic x87 conversion case, try compiling `#include <math.h> #include <stdio.h> int main() { printf("%f\n", cosf(M_PI_2)); return 0; }` **without optimization** (because GCC optimizes cos[f] and sin[f] with `-mfpmath=sse -msse3` and see how you get a **perfect** `0.00000`. Switch that to `-mfpmath=x87` and this breaks. I'm not saying floats are awesomely accurate, but they are NOT inaccurate as people think.
LiraNuna
I'm not convinced here. SSE3 has no cosine instruction. So how is it calculating that 0.00000? Lira, have you checked the ACTUAL value returned is it really 0x00000000? That would seem surprising as SSEx has far less precision in its calculations than the x87. In fact if you look at my edit to my post you will see that using the fcos x87 instruction actually gives pretty bloody good precision.
Goz
@LiraNuna: single and double precision arithmetic is done with SSE/SSE2 by default with the OS X compilers. There is nothing to turn on. @Goz: for performance reasons, the OS X math library does not use the x87 fcos instruction to compute cosf.
Stephen Canon
+5  A: 

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

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.

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

    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"

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.

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.

Goz
watch your damn tongue!
Jason S
what exactly did i say that requires tongue watching???
Goz
or ar eyou just taking the mickey absed on my overuse of the word damned? Cos, personally, I like it .. gives good emphasis :P
Goz
yup, just being facetious about "damn". the editor/proofreader in me says it's unnecessary, there's other ways to provide emphasis. ("mickey absed" what's that??!)
Jason S
"taking the mickey" can be looked up. absed = based based typo ... but, alas ... cmments can't be edited. Anyway ... i like damned. You gonna start on my excessive use of ellipses next? :P
Goz
Mark Twain: “Substitute "damn" every time you're inclined to write "very"; your editor will delete it and the writing will be just as it should be.” He doesn't actually say what to do when you're inclined to write "damned", but one suspects his point about emphasis is a general one.
ShreevatsaR
+1  A: 

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!

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

Pavel Shved
A: 

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.

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.

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.

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).

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)).

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

LiraNuna
I don't think I understand why a 32-bit float from x87 would be less precise than a 32-bit float from SSE. Could you elaborate?
avakar
x87 has nothing to do with this. `float`s are codegen'd to SSE by default with the OS X compilers.
Stephen Canon
+1  A: 

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).

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:

  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. Round the converted double precision value to single precision.
  3. Call the math library function cosf on the single precision value.

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.

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

M_PI_2 is defined to be

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).

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

1.57079637050628662109375

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

-0.00000004371138828673792886547744274139404296875

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

-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.

Stephen Canon
thanks a lot.. ur answer is very detailed.. my doubt is why does sinf return exactly 1 ? Can u please explain that.. please excuse me if its a dumb question.. but i cant think of why that is happening..
Quakeboy
sinf returns exactly 1 because sin(π/2 + 4.37*10^(-8)) is approximately 1 - (4.37*10^(-8))^2. The closest floating-point value to that is 1.0f, and that is what's returned. More fundamentally, what you're really seeing here is that there is far greater density of floating-point numbers close to zero than there is close to one.
Stephen Canon