PDA

View Full Version : Weird problem with real variable



Christian Knudsen
29-12-2010, 03:35 PM
Sorry about the vague topic title, but I have no idea what's going on here and was therefor not able to give a more precise topic title. Anyway...

I'm programming in FreePascal 2.2.2 on Win32 and have run into a strange issue with a real variable. I have a procedure called PlaceLightsource like this:



PROCEDURE PlaceLightSource(Const LightSourceNumber : Byte; Const Radius, HotSpot, X, Y, Red, Green, Blue : Real; Const Life : SmallInt);
BEGIN
LightSource[LightSourceNumber].AbsoluteX := X;
LightSource[LightSourceNumber].AbsoluteY := Y;
LightSource[LightSourceNumber].Active := T;
LightSource[LightSourceNumber].HotSpot := HotSpot;
LightSource[LightSourceNumber].Life := Life;
LightSource[LightSourceNumber].Radius := Radius;
LightSource[LightSourceNumber].R := Red;
LightSource[LightSourceNumber].G := Green;
LightSource[LightSourceNumber].B := Blue;
PlaceLightSourceReferencesOnTiles(LightSourceNumbe r);
RecalculateTileLighting := T;
END;


I then call this procedure like this:



PlaceLightSource(I, 1.5, 0, X, Y, 0.5, 0.5, 0.3, -1);


So, LightSource[I].Radius should now be set to 1.5, LightSource[I].R to 0.5, LightSource[I].G to 0.5, and LightSource[I].B to 0.3. If I get the program to print the values of these variables to the screen, it checks out fine.

However, when I call this piece of code...



IF (LightSource[I].Radius = 1.5) AND (LightSource[I].R = 0.5) AND (LightSource[I].G = 0.5) AND (LightSource[I].B = 0.3) THEN BEGIN
Character[CharacterNumber].LightSource := 0;
LightSource[I].Active := F;
RemoveLightSourceReferencesOnTiles(I);
RecalculateTileLighting := T;
END;


...it never gets past the IF check. Weird. Even more weird, if I comment out the "AND (LightSource[I].B = 0.3)", it does everything it's supposed to do. So I double checked that LightSource[I].B = 0.3 by printing the value to the screen again, and it IS 0.3. Now, if I set it to 0.5, it also works fine!

I'm completely stumped by this. Is anybody aware of some things to look out for when checking the value of real variables?

Darthman
29-12-2010, 03:50 PM
Try to avoiding this kind of check. Because 0.3 can be not exactly 0.3. It can be 0.30000000001 or 0.29999999999.
So you can check like this: if round(b*10)=3 (dont like it) or like this: > 0.29 and < 0.31 (for example).

chronozphere
29-12-2010, 04:25 PM
Make a special equals() function, like this:



const
//A treshold for float comparison
EPSILON = 1e-5;

implementation

function Equals(const a, b: Single): Boolean;
begin
Result := (a - b < EPSILON);
end;


Make similar functions to compare vectors and colors. :)

KidPaddle
29-12-2010, 05:18 PM
Small changes, cause you forgot function call ABS(a - b)


const
//A treshold for float comparison
EPSILON = 1e-5;

implementation

function Equals(const a, b: Single): Boolean;
begin
Result := (abs(a - b) < EPSILON);
end;

Thomas

Christian Knudsen
29-12-2010, 05:48 PM
Interesting! And thanks for the replies!

So, when you assign the value 0.3 to a real, you can't be 100% sure that it actually gets the precise value? That seems a bit odd.


EDIT: Still doesn't work. I tried this:


IF (Trunc(LightSource[I].Radius * 10) = 15) AND (Trunc(LightSource[I].R * 10) = 5)
AND (Trunc(LightSource[I].G * 10) = 5) AND (Trunc(LightSource[I].B * 10) = 3) THEN BEGIN
Character[CharacterNumber].LightSource := 0;
LightSource[I].Active := F;
RemoveLightSourceReferencesOnTiles(I);
RecalculateTileLighting := T;
END;


And it will still only go into the IF block if I comment out the "AND (Trunc(LightSource[I].B * 10) = 3)" bit.


ANOTHER EDIT: It seems that Trunc(LightSource[I].B * 10) returns 2, so 0.3 must actually be something just below 0.3, like 0.299999999999, or something...

User137
29-12-2010, 06:39 PM
You can use Round() instead of Trunc() so 2.7 would round upwards to 3 instead.

wagenheimer
29-12-2010, 07:03 PM
Delphi has a native SameValue function in Math Unit that I use to compare Float Values.


function SameValue(const A, B: Double; Epsilon: Double): Boolean;
begin
if Epsilon = 0 then
Epsilon := Max(Min(Abs(A), Abs(B)) * DoubleResolution,
DoubleResolution);
if A > B then
Result := (A - B) <= Epsilon
else
Result := (B - A) <= Epsilon;
end;

Christian Knudsen
29-12-2010, 07:45 PM
You can use Round() instead of Trunc() so 2.7 would round upwards to 3 instead.

Yep, that's what I did instead. Everything's working now. :)

I'm still wondering from a purely computer hardware standpoint why an absolute float value isn't guaranteed. Anybody have some illuminating links on the subject?

pstudio
29-12-2010, 10:30 PM
Yep, that's what I did instead. Everything's working now. :)

I'm still wondering from a purely computer hardware standpoint why an absolute float value isn't guaranteed. Anybody have some illuminating links on the subject?

It's because in the math world a real number can have an infinite amount of decimals after the dot. However a computer has a finite amount of memory so we can't allow an infinite amount of decimals. It's most common to use 4 or 8 bytes to represent real values called floating points on a computer. Clearly 8 bytes can't provide you with infinite precision so the computer will have to approximate your real number to the closest floating point value it knows how to represent. This is why you can't be guranteed that a real variable holds the exact value you would expect.
The http://en.wikipedia.org/wiki/IEEE 754 (http://en.wikipedia.org/wiki/IEEE_754-2008) standard describes how floating points are represented in memory.

Christian Knudsen
30-12-2010, 12:15 AM
Thanks for the reply. I understand why the computer wouldn't be able to hold an infinite amount of decimal points, but when given the simple number of 0.3 with just one decimal, it just seems weird that it's not stored precisely. Why aren't all the decimals following the .3 just zero'ed or something, instead of storing the number as 2.9999999999?

Reading that Wikipedia link, it seems to me that 0.3 should easily be stored as:



(−1)s ?ó c ?ó bq

s = 1
c = 3
b = 10
q = -1


Is the issue then that it's base 2 instead of base 10?

code_glitch
30-12-2010, 01:55 AM
I once remeber reading an article on how CPUs make mathematical mistakes. You wouldnt happen to be encountering these issues on a Pentium chip would you? As far as I an awarem this is the Pentium specialty - although there was a time when google had this problem also ;). The inherent problem is how to debug stuff if its the CPU producing waked up results.

If I remember correctly, its specifically related to the floating point XOR gates for division in the ALU... (Someone please check this). Other than that I woud say your best bet is to either round or use an approximate...

chronozphere
30-12-2010, 09:28 AM
Small changes, cause you forgot function call ABS(a - b)


const
//A treshold for float comparison
EPSILON = 1e-5;

implementation

function Equals(const a, b: Single): Boolean;
begin
Result := (abs(a - b) < EPSILON);
end;

Thomas

Argh.. that's indeed a stupid mistake. Thanks for the heads up. :)

Christian Knudsen
30-12-2010, 10:55 AM
I once remeber reading an article on how CPUs make mathematical mistakes. You wouldnt happen to be encountering these issues on a Pentium chip would you? As far as I an awarem this is the Pentium specialty - although there was a time when google had this problem also ;). The inherent problem is how to debug stuff if its the CPU producing waked up results.

Nah, I'm on a Celeron.


If I remember correctly, its specifically related to the floating point XOR gates for division in the ALU... (Someone please check this). Other than that I woud say your best bet is to either round or use an approximate...

Yeah, I went the Round() way. It's not a problem at all, but it just made me curious as to the cause of it and the internal workings of the CPU.

code_glitch
30-12-2010, 02:28 PM
Just re-read the article. Turns out therevis a wiki on it here: http://en.wikipedia.org/wiki/Pentium_FDIV_bug which is a prime example of how serious it can get. Funny enough, the bug did not hit some of the celeron chips based on the architecture... Oh well, intel whatever. Whats next - a cpu that takes tea leaves and makes coffee of it? :D I cant wait to see the day that AMD rolls out a cheap chip that beats the i7 extreme edition whatever its called ;)

Christian Knudsen
30-12-2010, 03:36 PM
Ouch. That's a bad bug to have in your CPU! I'm sure somebody lost their job over this! :D

I believe my old computer was a Pentium 90MHz. Didn't really experience any problems with this, though, even though I did a lot of programming on that machine as well.

AthenaOfDelphi
30-12-2010, 05:41 PM
If reals work like they used to, it's because the bit values can end up being things like 1, 0.5, 0.25, 0.125 and so on, each time dividing by 2. I can't remember the exact details as it was a while since I actually cared about it, but thats why reals can vary slightly from what you think they are, because it's impossible to set a bit pattern that matches exactly the value you want. The error might be very small, but it is still an error and comparing 3 with 3.000000000000000000000000000000001 (you get the picture) will still give a 'does not equal' result.

Wikipedia has what looks to be a decent explanation of FP storage formats here (http://en.wikipedia.org/wiki/Floating_point).

code_glitch
30-12-2010, 06:54 PM
Which is why I literally never use Real variables... Except when I have to use some atrocious C/C++ code written with floating point variables. Or when I use OpenGl ;). I've had enough headaches with Reals to last a long time.

Christian Knudsen
30-12-2010, 07:13 PM
Or when I use OpenGl ;)

...and that would be my situation. :)

AthenaOfDelphi
30-12-2010, 08:11 PM
I think the key thing here is that what you're actually doing is using the display mechanisms of your engine/display solution (OpenGL) to store state. If you need to do that kind of comparison, then isn't it worthy of having it's own state variable somewhere along the line. An integer field somewhere for example, faster absolute comparisons that are guaranteed to work. There is also the issue that you are performing absolute comparisons with specific values in your code... what happens if you change the look of something? Comparisons that used this data will all stop working unless of course you start comparing and setting values with constants.

Just a few thoughts :)

Christian Knudsen
30-12-2010, 10:05 PM
Without getting into too many details of my program, the piece of code posted here was used to check whether or not that particular light source in the game belonged to the firing of a weapon (not OpenGL lighting; the game is 2D isometric tile based and the "lighting" is just how dark/light and with which colors each tile is to be drawn to the screen -- I just use OpenGL for fast sprite blitting). The "light" emitted from a firing weapon has a tile radius of 1.5 and an R, G, B value of 0.5, 0.5 and 0.3, which is why I was checking for those values. So the value I'm checking is not some OpenGL value, it is actually a state variable stored in the program (in the LightSource array).

code_glitch
30-12-2010, 10:41 PM
Hmm. then could you not use an Int64 and multiply by 1000 or 10000 or whatever and just divide it whenever you call a gl(real, pain, here) and then compare them headache free beacuse they are Integers? An Int64 gives plenty of working space. Thats −9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 or any 18 digit number either side the decimal point with 92% of 19 digit numbers covered ;) Tell me if you run wont you?

Christian Knudsen
30-12-2010, 11:06 PM
Doing the *10 and Round() when comparing the value is fine, since I'm just doing the compare once. And I don't really need the 100% correct float value when passing it in the gl call -- it doesn't really matter there if the number is 0.3 or 0.299999999.

Murmandamus
04-01-2011, 07:00 PM
Yes. Essentially, floating-point fractions are reciprocal multiples of 2: 1/2, 1/4, 1/8, 1/16, etc. There is no combination of any set of those fractions (at least within the range of the type, based on how many bits represent it) which will give you exactly 0.3 (or 0.1, for that matter). They end up dancing all around the value, but never really getting to it.

In general, however, if you set a float variable to a float constant, like 0.3, it should have the same value as the float constant when compared. I think the difference in this case is the accuracy of the types. Float constants are stored by default in the largest/most accurate type available, which is probably either Double or Extended, and when you compare the values of two different float types, the less precise one is converted to the same type as the more precise one.

So, for example, if the default float constant type is Double, and you store 0.3 in a Single, it is "truncated" to fit. If you then compare it to the literal float constant 0.3, the Single value is temporarily upconverted back into a Double, then compared. The problem is that there was a loss of information when it was converted to a Single for storage, so the values will not be exact, and the comparison will fail.

One way to be sure of comparing apples to apples is to value-cast your constants. Like so (assuming the record fields are defined as type Real):



IF (LightSource[I].Radius = Real(1.5)) AND (LightSource[I].R = Real(0.5)) AND (LightSource[I].G = Real(0.5)) AND (LightSource[I].B = Real(0.3)) THEN BEGIN
Character[CharacterNumber].LightSource := 0;
LightSource[I].Active := F;
RemoveLightSourceReferencesOnTiles(I);
RecalculateTileLighting := T;
END;


It's a bit of a pain to change all those casts if you wanted to change it use Singles (for example), and it really only works in this highly-specific situation. In other situations, the epsilon comparison function is more appropriate, and will work fine in this case, too.

Oh, one other thing, be careful using the Real type, unless you explicitly redeclare it as Single or Double; the actual implementation of it is platform-dependent, so it may cause additional headaches in practice.

AthenaOfDelphi
05-01-2011, 09:06 PM
I still stand by what I said.... do this....



If (lightsource[i].sourceType=GUNFIRE) then
begin
// Do something
end;


A single, integer comparison is going to be faster than the sample code. Four floating point comparisons (which as we've discussed are flakey) and the logic to combine their results. I'd go for the single integer comparison any day (in this instance).

Murmandamus
05-01-2011, 09:32 PM
Oh, definitely, if refactoring it to simplify will work, I'd do that, too. I just wanted to explain why floating point can be weird (since the question was asked by the OP), and how to get around / take advantage of the weirdness.

Knowledge is power. :)

AthenaOfDelphi
06-01-2011, 11:07 AM
This is true :)

Christian Knudsen
06-01-2011, 11:52 AM
I still stand by what I said.... do this....



If (lightsource[i].sourceType=GUNFIRE) then
begin
// Do something
end;


A single, integer comparison is going to be faster than the sample code. Four floating point comparisons (which as we've discussed are flakey) and the logic to combine their results. I'd go for the single integer comparison any day (in this instance).

I might end up doing this if the current code becomes a bottleneck. The code is only called very rarely during runtime, though, (once whenever a weapon has ended firing to check if the light source associated with the character holding the gun should be deactivated -- which probably only happens once every minute or so during play, if that (it's going to be a sneaking/assassination game, not a guns blazing action game)) so I currently prefer to use the existing variables instead of adding new variables that will only be used rarely. Maybe that's just my personal coding preference, or something. :)