Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Bug/Tip with floats

Ruadeil Zabelin
Registered User
Join date: 11 Apr 2007
Posts: 5
04-15-2007 05:13
I had 2 floats.

float current=0;
float max=2.0;

I was slowly increasing the current value with 0.1 with a timer and kept checking if current==max. This NEVER happened! Why? I am not fully sure, but I think it's a float rounding error. The way I got around it was

if((string) current == (string) max)

And then it worked. It's nasty coding like that, but it seemed the only thing that worked. Any other suggestions? or people experiencing the same?
Newgate Ludd
Out of Chesse Error
Join date: 8 Apr 2005
Posts: 2,103
04-15-2007 05:17
Due to the way in which floating point numbers are represented a straight equality check is rarely ever going to work.
Pale Spectre
Registered User
Join date: 2 Sep 2005
Posts: 586
04-15-2007 05:27
Maybe a more 'agnotistic' approach:

CODE
if (current >= max)
{
llSetTimerEvent(0);
current = max;
}
There was a recent discussion about the inaccuracy of incrementing floats.

I think there is a kind of philosophy you can apply when programming, which is difficult to explain, but essentially it is about making as few assumptions as possible. In this case lets assume that current might never == max. The lagginess of Second Life alone probably makes this a good practice. :)
Senuka Harbinger
A-Life, one bit at a time
Join date: 24 Oct 2005
Posts: 491
04-15-2007 10:42
From: Newgate Ludd
Due to the way in which floating point numbers are represented a straight equality check is rarely ever going to work.



or as the more famous mathematics programming "joke" goes

1+1=3 for sufficiently large values of 1

[theory]

The nitty gritty of why an incremental system rarely sums up to what you expect is because of how it's translated into binary. some values have infinitely repeating representations which the system rounds off at it's max bit value (I think .1 is one of those), rending it's calculated value different than what you really want. Depending on the size of the rounding errors, the point at which you will see noticable effects in your equations' results will vary.

[/theory]
_____________________
My SLExchange shop

Typos are forgiven; desecrating the english language with reckless abandon and necrophilic acts is not.


The function is working perfectly fine. It's just not working the way you wanted it to work.
Twisted Pharaoh
if ("hello") {"hey hey";}
Join date: 24 Mar 2007
Posts: 315
04-15-2007 12:10
It's because of the way float are represented physically, 0.1 is rounded up or down therefore your value never reaches 2.0. I would recommend using the integer instead comparing from 0 to 20 with increments of 1. If you prefer stick to float you can use this :
abs(max- current) < epsilon for a comparison, with float epsilon = 0.01 for instance.
Ruadeil Zabelin
Registered User
Join date: 11 Apr 2007
Posts: 5
04-15-2007 13:27
ah that's what i thought. Thanks for the suggestions guys. I'll try them :)

The strange thing is that it does work when converted to string. You would think that the converted string would have the same value representation, for example 2.000014 or whatever. But it doesnt seem to do that.
Kenn Nilsson
AeonVox
Join date: 24 May 2005
Posts: 897
04-15-2007 13:50
WILD speculation, but it might be that there's a 'rounding' back to the string-type that fixes this spread between the two floats.
_____________________
--AeonVox--

Computer games don't affect kids; I mean if Pac-Man affected us as kids, we'd all be running around in darkened rooms chasing ghosts, eating magic pills, and listening to repetitive, addictive, electronic music.
Deanna Trollop
BZ Enterprises
Join date: 30 Jan 2006
Posts: 671
04-15-2007 14:53
From: Ruadeil Zabelin
You would think that the converted string would have the same value representation, for example 2.000014 or whatever. But it doesnt seem to do that.
It's probably something more like 2.0000002384. Casting a float to string truncates everything after the 6th decimal digit. So, if the error is one ten-millionth or less, it gets lost in the typecast, and the strings evaluate as equal.
Stavros Augustus
Registered User
Join date: 14 Nov 2005
Posts: 38
04-15-2007 17:48
Wouldn't it be better to just use large integers?
Gearsawe Stonecutter
Over there
Join date: 14 Sep 2005
Posts: 614
04-15-2007 19:42
From: Stavros Augustus
Wouldn't it be better to just use large integers?


Yes agreed. I have a system where it increments by 0.1 or -0.1 but I uses Integers and just divide every thing by 10 for the final output. So my increment values are really 1 and -1. It works pretty well
Ruadeil Zabelin
Registered User
Join date: 11 Apr 2007
Posts: 5
04-16-2007 04:40
From: Stavros Augustus
Wouldn't it be better to just use large integers?


i was using the float as a direct input to llSetAlpha. That's where my original float choise came from. I suppose i could integers and then devide the input.
Twisted Pharaoh
if ("hello") {"hey hey";}
Join date: 24 Mar 2007
Posts: 315
04-16-2007 05:03
The other benefit of using integers is the speed, as computations on integers are much much much faster than floats.
Learjeff Innis
musician & coder
Join date: 27 Nov 2006
Posts: 817
04-16-2007 18:21
Welcome to floating point 101.

0.1 is a repeating fraction in binary, because it's 1 / (2 * 5).

Any fraction with any number other than 2 in the prime-factored denominator is a repeating fraction in binary. Just as if you tried to add 1/3 in decimal, using any set number of digits (say, two):

0.33 + 0.33 + 0.33 = 0.99

0.1 is not repeating in decimal because 1/10 = 1/(2 * 5). The rule in decimal is the denominator can contain only 2's and 5's in the prime-factored denominator.

So, the first rule of floating point is, never compare for equality unless you REALLY know what you're doing computationally. I generally know what I'm doing and I avoid it anyway.

Rather than convert to float, just add some "epsilon" value to the sum before comparing (when incrementing as you are doing -- subtract it when decrementing). This epsilon value can be small, say 1/1000 of the value you're comparing to (or less).

Another option (as mentioned above) is to do the counting using integers and convert to floating point for any values you need floating point numbers for. This is usually more efficient, though not necessarily because the conversion from int to float is not trivial.
Newgate Ludd
Out of Chesse Error
Join date: 8 Apr 2005
Posts: 2,103
04-16-2007 23:53
Another way around it is to use fixed point i.e. always assume that you have n decimal places in your integer value. Obviously it only works if you know your values will alwasy be constrained within the avaialable numeric range you ar eleft with.

Or if you want to get creative use B-scaling but as Learjeff said the coversion process can cost more than itgains.
Twisted Pharaoh
if ("hello") {"hey hey";}
Join date: 24 Mar 2007
Posts: 315
04-17-2007 01:46
Actually I've checked the floating point format and it is *very* inaccurate, so using an epsilon could break your code. On the other hand it seems to be pretty fast, I guess it is a variant of FFP.

CODE

compute_float()
{
float f ;
for (f=0.0; llFabs(f - fmax)> epsilon; f+=0.1)
{
do_something(f) ;
}
}

This would break for epsilon as high as 0.01 and fmax = 400.0

From: someone

[1:29] Object: f=399.715179fmax=400.000000
[1:29] Object: f=399.815186fmax=400.000000
[1:29] Object: f=399.915192fmax=400.000000
[1:29] Object: f=400.115204fmax=400.000000
[1:29] Object: f=400.015198fmax=400.000000
[1:29] Object: f=400.215210fmax=400.000000


so here the correct test would be f < fmax, but as you can imagine on very high values of fmax you would lose steps. I strongly recommend using integers here.


From: Learjeff Innis
This is usually more efficient, though not necessarily because the conversion from int to float is not trivial.


The best way if you have any doubt is to check by yourself using timers. Timers aren't accurate in LSL but give you a rough idea about what's going on and help you decide if a solution is better than another.

Here is an example of such a test based on your problem, note that the results aren't reliable at all but if you had a ratio of 1:10 then you could conclude something.
CODE


vector v ;

integer imax ;
float fmax ;

do_something(float f)
{
v.x = f ;
}

compute_integer()
{
integer i ;
float f ;

for (i=0; i<=imax; i++)
{
f = ((float)i)/10.0 ;
do_something(f) ;
}
}

compute_float()
{
float f ;
for (f=0.0; f <= fmax; f+=0.1)
{
do_something(f) ;
}
}

default
{
state_entry()
{
}

touch_start(integer total_number)
{
float timer_1 ;
float timer_2 ;
float timer_3 ;

imax = 20 ;
fmax = 2.00 ;

llSay(0, "timing for "+(string)imax+ " loops") ;

timer_1 = llGetTime() ;
compute_integer() ;
timer_2 = llGetTime() ;
compute_float() ;
timer_3 = llGetTime() ;
llSay(0, "Result: First:"+(string)(timer_2-timer_1)+" Second:"+(string)(timer_3-timer_2)) ;

imax = 4000 ;
fmax = 400.00 ;

llSay(0, "timing for "+(string)imax+ " loops") ;

timer_1 = llGetTime() ;
compute_integer() ;
timer_2 = llGetTime() ;
compute_float() ;
timer_3 = llGetTime() ;
llSay(0, "Result: First:"+(string)(timer_2-timer_1)+" Second:"+(string)(timer_3-timer_2));

}
}


The results:

From: someone

[1:42] Object: timing for 20 loops
[1:42] Object: Result: First:0.023086 Second:0.021628
[1:42] Object: timing for 4000 loops
[1:42] Object: Result: First:4.625366 Second:3.510604


The fast floating point format makes it even with integers, you cant really decide toward one or the other, but the accuracy loss is too big to use floats imho.