views:

299

answers:

7

If I do something like this:

float a = 1.5f;
float b = a;

void func(float arg)
{
  if (arg == 1.5f) printf("You are teh awresome!");
}

func(b);

Will the text print every time (and on every machine)?

EDIT

I mean, I'm not really sure if the value will pass through the FPU at some point even if I'm not doing any calculations, and if so, whether the FPU will change the binary representation of the value. I've read somewhere that the (approximate) same floating point values can have multiple binary representations in IEEE 754.

A: 

It should print every time and on every machine. You're not using the output of any calculation, so there's no reason to assume that the values would change.

One caveat: certain "magic" numbers don't necessarily come from memory. In x86 we have the following instructions:

  • FLDZ - load 0.0 into ST(0)
  • FLD1 - load 1.0 into ST(0)
  • FLD2T - load log2(10) into ST(0)
  • FLD2E - load log2(e) into ST(0)
  • FLDPI - load pi into ST(0)
  • FLDLG2 - load log10(2) into ST(0)
  • FLDLN2 - load ln(2) into ST(0)

So if you happen to be comparing with one of these values you may possibly not get the exact same value as the const literal you referenced.

Nathan Fellman
-1: `bool operator==( float, float )` _is_ a calculation, probably compiled as FPU instructions.
xtofl
Right, but the inputs to `operator==` are themselves not the outputs of any calculation. They are raw data from the memory. As such they should always be equal, and the comparison should also return `true`
Nathan Fellman
They aren't from memory. The second argument is a floating point literal.
xtofl
Which comes from the code, which resides in memory. In x86, for instance, I'd expect the constant to be in memory, and have it loaded in with an `fld` instruction from that address.
Nathan Fellman
Before expecting any more, take a look at the code in my latest answer.
xtofl
Your code doesn't show the relevant assembly (see my comments there)
Nathan Fellman
oh... sorry.. missed that one
Nathan Fellman
+4  A: 

First of all 1.5 can be stored accurately in memory so for this specific value, yes it will always be true.

More generally I think that the inaccuracies only pop up when you're doing computations, if you just store a value even if it doesn't have an accurate IEEE representation it will always be mapped to the same value (so 0.3 == 0.3 even though 0.3 * 3 != 0.9).

Motti
+1, I think the same thing. Beware though (since the questioner was using float literals and you use double literals), that `0.3f != 0.3`
Steve Jessop
-1: storage of the variable may well differ from the fpu-representation of the float literal value.
xtofl
"More generally I think that the inaccuracies only pop up when you're doing computations". On an x86 FPU, this is -- strangely enough -- not always true. See http://stackoverflow.com/questions/1338045/gcc-precision-bug/1338089#1338089 for an example.
Martin B
To be honest, if operator== for floats has no defined situations where it will return true, I'm not entirely convinced it should even exist...
Steve Jessop
It is not that '==' will never return true; the problem is that it does not return true reliably.
Jonathan Leffler
Right, but "not reliably" means "not defined to". Obviously I'm exaggerating a bit. There are a few situations where the standard does require it to be true, such as `a = 0; a == a;`, and maybe `volatile a = 0; volatile b = 0; a == b;`. I'm just wondering if there are any *useful* situations where operator== has standard-defined behaviour to return true. There are plenty of situations where it's defined to be false, but it can almost (not quite) be implemented just to always be false, which clearly would not be helpful ;-)
Steve Jessop
+1  A: 

Here's a (not quite comprehensive) proof that (at least in GCC) you are guaranteed equality for floating literals.

Python code to generate file:

print """
#include <stdio.h>

int main()
{
"""
import random
chars = "abcdefghijklmnopqrstuvwxyz"
randoms = [str(random.random()) for _ in xrange(26)]
for c, r in zip(chars, randoms):
    print "float %s = %sf;" % (c, r)

for c, r in zip(chars, randoms):
    print r'if (%s != %sf) { printf("Error!\n"); }' % (c,r)

print """
    return 0;
}
"""

Snippet of generated file:

#include <stdio.h>

int main()
{

float a = 0.199698325654f;
float b = 0.402517512357f;
float c = 0.700489844438f;
float d = 0.699640984356f;
if (a != 0.199698325654f) { printf("Error!\n"); }
if (b != 0.402517512357f) { printf("Error!\n"); }
if (c != 0.700489844438f) { printf("Error!\n"); }
if (d != 0.699640984356f) { printf("Error!\n"); }

    return 0;
}

And running it correctly does not print anything to the screen:

$ ./a.out 
$

But here's the catch: if you don't put the literal f after the floats in the check for equality, it will fail every time! You can leave the literal f out of the assignment, though, without problems.

Mark Rushakoff
+2  A: 

If it passes the FPU on one point in time it could be due to optimizations and register handling by the compiler that you end up comparing a FPU register with a value from the memory. The first one may have a higher precision as the latter one and so the comparison gives you a false. This may vary depending on compiler, compiler options and the platform you run on.

rstevens
+1: FPU-awareness.
xtofl
But I would add an example to make it more clear.
xtofl
A: 

I don't think it will behave equal:

float f1 = .1; 
// compiled as
//// 64-bits literal into EAX:EBX


if( f1 == .1 ) 
//// load .1 into 80 bits of FPU register 1
//// copy EAX:EBX into FPU register 2 (leaving alone the last 16 bits)
//// call FPU compare instruction
////

When the compiler generates code as mentioned above, the condition will never be true: 16 bits will be different when comparing the 64bits version of .1 with it's 80bits version.

Concluding: no: you cannot guarantee equality on each and every machine with this code.

xtofl
Can you show the assembly that's generated? I wonder why it would go through eax:ebx. What instruction copies eax:ebx to FPU registers?
Nathan Fellman
...and how do you load 0.1 directly into an FPU register?
Nathan Fellman
Your literal valuables should be `0.1F` to be float, not double.
Nathan Fellman
+3  A: 

In the example, the value 1.5F has an exact representation in IEEE 754 (and pretty much any other conceivable binary or decimal floating point representation), so the answer is almost certainly going to be yes. However, there is no guarantee, and there could be compilers which do not manage to achieve the result.

If you change the value to one without an exact binary representation, such as 5.1F, the result is far from guaranteed.

Way, way, way back in their excellent classic book "The Elements of Programming Style", Kernighan & Plauger said:

A wise programmer once said, "Floating point numbers are like sand piles; every time you move one, you lose a little sand and you pick up a little dirt". And after a few computations, things can get pretty dirty.

(It's one of two phrases in the book that I highlighted many years ago1.)

They also observe:

  • 10.0 times 0.1 is hardly ever 1.0.
  • Don't compare floating point numbers just for equality

Those observations were made in 1978 (for the second edition), but are still fundamentally valid today.

If the question is viewed at its most extremely restricted scope, you may be OK. If the question is varied very much, you are more likely to be bitten than not, and you'll probably be bitten sooner rather later.


1 The other highlighted phrase is (minus bullets):

  • the subroutine call permits us to summarize the irregularities in the argument list [...]
  • [t]he subroutine itself summarizes the regularities of the code [...]
Jonathan Leffler
+1  A: 

I took a look at the disassembly from the VS2005 compiler. When running this simple program, I found that after float f=.1;, the condition f==.1 resulted in .... FALSE.

EDIT - this was due to the comparand being a double. When using a float literal (i.e. .1f), the comparison resulted in TRUE. This also holds when comparing double variables with double literals.

I added the source code and disassembly here:

  float f=.1f;
0041363E  fld         dword ptr [__real@3dcccccd (415764h)] 
00413644  fstp        dword ptr [f] 

  double d=.1;
00413647  fld         qword ptr [__real@3fb999999999999a (415748h)] 
0041364D  fstp        qword ptr [d] 

  bool ffequal = f == .1f;
00413650  fld         dword ptr [f] 
00413653  fcomp       qword ptr [__real@3fb99999a0000000 (415758h)] 
00413659  fnstsw      ax   
0041365B  test        ah,44h 
0041365E  jp          main+4Ch (41366Ch) 
00413660  mov         dword ptr [ebp-0F8h],1 
0041366A  jmp         main+56h (413676h) 
0041366C  mov         dword ptr [ebp-0F8h],0 
00413676  mov         al,byte ptr [ebp-0F8h] 
0041367C  mov         byte ptr [fequal],al 
// here, ffequal is true

  bool dfequal = d == .1f;
0041367F  fld         qword ptr [__real@3fb99999a0000000 (415758h)] 
00413685  fcomp       qword ptr [d] 
00413688  fnstsw      ax   
0041368A  test        ah,44h 
0041368D  jp          main+7Bh (41369Bh) 
0041368F  mov         dword ptr [ebp-0F8h],1 
00413699  jmp         main+85h (4136A5h) 
0041369B  mov         dword ptr [ebp-0F8h],0 
004136A5  mov         al,byte ptr [ebp-0F8h] 
004136AB  mov         byte ptr [dequal],al 
// here, dfequal is false.

  bool ddequal = d == .1;
004136AE  fld         qword ptr [__real@3fb999999999999a (415748h)] 
004136B4  fcomp       qword ptr [d] 
004136B7  fnstsw      ax   
004136B9  test        ah,44h 
004136BC  jp          main+0AAh (4136CAh) 
004136BE  mov         dword ptr [ebp-104h],1 
004136C8  jmp         main+0B4h (4136D4h) 
004136CA  mov         dword ptr [ebp-104h],0 
004136D4  mov         al,byte ptr [ebp-104h] 
004136DA  mov         byte ptr [ddequal],al 
// ddequal is true
xtofl
Interesting! I stand corrected!
Nathan Fellman
I wonder, could you post the machine code as well? This is odd, because both `FLD` and `FCOMP` can accept the same memory operands. I wonder, if you use `0.1f` instead of `0.1` you'll get correct results. In C `0.1` is a double literal, while `f` is a float. That probably explains why the comparison fails.
Nathan Fellman
Either way, both `fld` and `fcomp` in your example above take the literals from memory, because x87 instructions don't accept immediate arguments.
Nathan Fellman
I take back my "stand corrected" comment unless the comparison returns false after fixing the double to float.
Nathan Fellman
@Nathan Fellan: you're right: for `0.1f`, the literal value `__real@3fb99999a0000000` is used for the `fcomp` function!
xtofl
And what is used for the `fld` that initializes f?
Nathan Fellman
`fload f=.1f` was translated as `__real@3dcccccd`.
xtofl
I wonder why there was a difference.
Nathan Fellman
ah... it's because for `float f` it's using a single-precision operand, and for the comparison in `fcomp` it's using a double-precision operand, with the bits in the mantissa beyond the "single precision" bits are all 0. This is great! I really learned something from this thread.
Nathan Fellman