I just tried to do a simple comparison of the two values, one was
user-defined value, and the other one was a result of a math expression
of other two user-defined values. It seems the result of the comparison
check was correct only when the values to be compare weren't fractions.
I have narrowed it down to the following simple script.
====
proc test { max min exp_diff } {
set diff [expr $max - $min]
puts "max=$max,min=$min,diff=$diff => want exp_diff=$exp_diff"
if { $diff <= $exp_diff } {
puts TRUE
} else {
puts FALSE
}
}
puts "trying when all values are integer"
test 2 1 1
puts "trying when all values are float"
test 1.1 1.0 0.1
====
When I ran the above script, I got the following output. The 2nd case
when all the values were floating point, the condition check came out
FALSE when it should have been TRUE. Is this a bug with ActiveState TCL
code or was I doing something totally wrong? I tried different TCL
versions from ATS4.0, 4.2, and 4.3, and they all seemed to behave the
same.
A modified function however gives the correct results:
proc test { max min exp_diff } {
set diff [expr $max - $min]
puts "max=$max,min=$min,diff=$diff => want exp_diff=$exp_diff"
if [ expr $diff <= $exp_diff ] {
puts TRUE
} else {
puts FALSE
}
}
Any ideas?
Regards
Sharad
Hmm it does appear that (1.1 - 1.0) is indeed larger than 0.1. The
following simplified code have been tested on 3 different CPUs:
Pentium4, Athlon64 and ARM7 (Xscale). All generate the same result:
% expr {(1.1 - 1.0) <= (0.1)}
0
However it is the following seems to work on all 3 platforms:
% expr {(1.1 - 1.0) <= (0.1)}
1
It should be noted that 0.1 is not representable as a binary floating
point number of a finite size (you'd need an infinite number of bits).
So it is possible that (1.1 - 1.0) is represented by a larger binary
number than (0.1 + 0).
> % expr {(1.1 - 1.0) <= (0.1)}
> 0
>
> However it is the following seems to work on all 3 platforms:
>
> % expr {(1.1 - 1.0) <= (0.1)}
> 1
Am I blind or are the two lines equal...?
Regards
Stephan
this is a problem in every language dealing with fractional values. See
below what happens when we set the highest precision for displaying
fractional values:
(bin) 60 % set tcl_precision 17
17
(bin) 61 % test 1.1 1.0 0.1
max=1.1,min=1.0,diff=0.10000000000000009 => want exp_diff=0.1
FALSE
(bin) 62 %
The problem comes up when a string is converted into a value (for
fractional values a internal type 'double' is used). Usually a so-called
epsilon value (a very small value, e.g. 1e-6) is used for fractional
comparison, e.g.:
if { abs($diff - $exp_diff) <= $epsilon } ....
Again, this is NOT a problem of Tcl but a problem of converting numbers
from strings (which is of base 10) to double's (which uses base 2) -
this conversion looses precision.
I hope I was able to help ;-)
Regards - Leo
rounding errors, floating point numbers are not exactly representable,
the difference between the number is very minute, but not exact. always
leave an epsilon for checking 2 floats.
in the first case expr was directly comparing numbers, and diff is just a
little bit bigger than exp_diff. in the second case, the interp substitutes
values for the numbers as strings and then expr re-parses them and the come
out the same.
Bruce
You can read more about this on the Wiki: http://wiki.tcl.tk/11969
A solution is to use the fuzzy comparisons defined in the Tcllib
library
(the math::fuzzy package). This uses a, well, fuzzy comparison
technique
to get around these floating-point issues. And as others have pointed
out,
this is a very general problem.
Regards,
Arjen
Thanks again.
Sharad
Ah sorry, second line (the one that works) should have been:
expr {(1.1 - 1.0) <= (0.1 + 1 - 1)}
So in effect the test can be re-coded as:
if {$diff <= ($exp_diff + 1 - 1)} ...
Just posting my 2 cents: the [math::bigfloat] package adresses
floating-point precision issues with arbitrary-precision.
(With the overcost of adding a layer upon your computations)
Regards,
Stéphane