<>

Archives Forums/BlitzMax Bug Reports/<>

REDi(Posted 2008) [#1]
<> strangeness...

Local f:Double = 0.01
Local v1:Double = 2^f
Local v2:Double = 2^f

'Print v1				' uncomment any one of the commented lines and it works as expected
'Print v2

'If v1=v2 Print "~nis the same!"
If v1<>v2 Print "~nis different?"	' should not be printed!

'If v1=v2 Print "~nis the same!"	' even uncommenting this one makes it work right!

outputs...

Building untitled2
Compiling:untitled2.bmx
flat assembler version 1.67.25 (384728 kilobytes memory)
3 passes, 2375 bytes.
Linking:untitled2.exe
Executing:untitled2.exe

is different?

Process complete




REDi(Posted 2008) [#2]
more strangeness...

Local f:Double, n:Int
Local a:Double, b:Double
For n=0 To 100

	a = 2^f     ' change them to divide and it gives the same problem
	b = 2^f 

	If a=b
		Print "same!~n"
	Else
		Print "a="+a+"~nb="+b+"~n"
	EndIf
	
	f:+0.01

Next

only seems to happen if using power or divide in the math. (plus and minus works fine)


H&K(Posted 2008) [#3]
Yes, it only "goes wrong" when you do anything that gets into the "Correct to X Decimal Place"


Who was John Galt?(Posted 2008) [#4]
I don't have Blitz here to test, but I would say there is a problem. If un-commenting the last line affects the outcome of the previous line, it's a problem, and although you're not supposed to compare floats for exact equality, if they are both the result of the exact same calculation they should compare absolutely equal.


tonyg(Posted 2008) [#5]
For me that code produces :
Building untitled1
Compiling:untitled1.bmx
flat assembler  version 1.66
3 passes, 2375 bytes.
Linking:untitled1.exe
Executing:untitled1.exe

Process complete

We do have different levels of FASM though.


Floyd(Posted 2008) [#6]
if they are both the result of the exact same calculation they should compare absolutely equal.

You shouldn't even count on that.

What happens is that float and double are 32-bit and 64-bit values. But calculations are done in 80-bit registers. Sometimes these values must be stored to memory, in 32 or 64 bit precision, which slightly changes their value. So even though the calculations look the same in source code the resulting values might be a little different depending on whether certain registers were available.

A command such as Print, which also has to call a function to convert numbers to strings, may need to temporarily use some registers. It does look odd that this can affect a calculation before the call to Print. But I suppose the compiler is simplifying things by dealing with blocks of code when deciding what values can stay in registers.


REDi(Posted 2008) [#7]
@tonyg, Just tried it with FASM 1.66 and I get the same result as with 1.67.25

@Floyd, I see your point that values might change if stored in an extended register or in memory, but IMHO that is kind of assuming that one of the variables has been poped out of a register and the other one is still there.

Local f:Double, n:Int
Local a:Double, b:Double
Local errors:Int
For n=0 To 100
	a = 2^f
	b = 2^f
	If a<>b	errors:+1
	f:+0.01
Next
Print errors

In the above code print isnt used until after the loop block, and I would expect all (if any) variables to stay in registers. (I admit I know nothing about asm, so I might be way off, just my opinion)

Might well be a FASM problem and out of Marks hands though.

*EDIT* Just tried the same thing in C++ and it works as expected.


Floyd(Posted 2008) [#8]
The version of FASM shouldn't matter.

I have BlitzMax 1.28 and don't see any errors in the three examples given above. But I have seen this sort of thing in past.

One particularly strange effect of keeping some values in registers while doing repeated store/load operations on others is that single precision values may be too accurate.


marksibly(Posted 2008) [#9]
Hi,

There is indeed a weird bug in there...investigating.


marksibly(Posted 2008) [#10]
Hi,

Ok, not a compiler bug - well, the compiler's doing what it should anyway - but a 64 vs 80 bit issue.

The '^' operator does in fact involve a function call, so the result of 'a=2^f' is first stored to 64 bit (as it's a double) memory. This is so it's not wiped out by the function call in the subsequent 'b=2^f'.

However, the result of 'b=2^f' is NOT being stored to memory - no need, as there are no function calls between it's definition and it's use. So 'b' is being 'stored' with 80 bit precision - thus the mismatch. In a way, I guess this is partially the optimizers fault!

Note this only happens in release mode. In debug mode, all locals are stored to memory, so are all 'forced' to 64 bit (although I can think of how to break it in debug mode too).

Not really sure what the fix is for this. Currently, float returning functions all really return 80 bit values (since they are returned in registers), so one fix could be to force float functions to 'chop' returned values by doing a store/load before returning them. However, built-in operators like +, * etc that don't require function calls would also really need to be chopped in some (which?) cases.

Another approach would be to 'chop' comparison parameters.

Or, all temps could be stored 80 bit - although I don't think this would work due to hidden 'guard bits' in the FPU.

All of which would result in slower code - I think I'll need to do a bit more research on this!


H&K(Posted 2008) [#11]
Another solution would be to introduce two new comparitors.
"Equal Enough", and "Quite different"


REDi(Posted 2008) [#12]
Floyd, My apologies mate, you're spot on! I'll add you to my "clever buggers not to argue with" list :)

Mark, do you think its worth fixing at all, chances of this cropping up in real apps must be pretty small?

I only found it because I was checking C++ functions against ones I had prototyped in Max.


marksibly(Posted 2008) [#13]
Hi,


"Equal Enough", and "Quite different"


This is actually really hard to do, as it all depends on how 'different' the parameters are.

I've seen some pretty complex and not so complex ways of doing it, but none I'd consider general enough for inclusion into the language.


Mark, do you think its worth fixing at all, chances of this cropping up in real apps must be pretty small?


Well, it crops up all the time really!

I probably don't notice it myself as I learned long ago never to use = or <> with floats unless I *know for sure* they're holding ints or 1/power-of-two values.

But none-the-less, c/c++ do appear to do better it better than Max so I'll look into it one of these days.


sswift(Posted 2008) [#14]
It seems to me that as the value you can store in ram is 64 bit, then the "true" value is the 64 bit value, and so that is what you should compare in a comparison operation.

But when in the process of doing math operations, I think you should do them with the greatest speed and precision possible, so you should not chop the values when doing that.

That said, it is my own personal practice never to try to compare two floats to see if they are equal, because of precision issues like this. I always check to see if they're less than/equal to or greater than/equal to some value, and never count on even decrementing 10 by .1 in a loop to ever result in a precise value like 0.


REDi(Posted 2008) [#15]
Yeah, I agree, its not the sort of thing I'd do normaly either.

Dirty solution... How about a "definitely equals" where any 80bit values are moved first, so no overhead for normal comparisons. ( == or something )