Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does 1.0 not equal to 1.0 in tcl when using ==

In the following code, I've encountered a strange phenomena. On on the runs on the line if {$ma == $mb} both mb and ma were equal 1.0, but the if wasn't taken. When I've changed the == to eq or [cequal $ma $mb], the if was taken.

Also, when I tried changing one of the variables with 1.0 (if {$ma == 1.0} and if {$1.0 == $mb}) the if wasn't taken as well, however an if with the expression if {1.0 ==1.0} is taken.

Another thing that happened to me after changing == to eq is that 0.0 and -0.0 are not equal when using eq, but are equal when using ==.

What is the source for those differences?

I'm aware that in floating point number comparison it's better to use a small epsilon to check if two numbers are really close to each other, but in this case the comparison is done to avoid division by zero.

Code:

proc geometry_intersect_two_sections {xa1 ya1 xa2 ya2 xb1 yb1 xb2 yb2} {
    if {($xa1 == $xa2) && ($xb1 == $xb2)} {
        return {}
    }

    if {!($xa1 == $xa2)} {
        set ma [expr (($ya1-$ya2)*1.0)/($xa1-$xa2)]
        set na [expr $ya1 - ($ma * 1.0 * $xa1)]
    }

    if {!($xb1 == $xb2)} {
        set mb [expr (($yb1-$yb2)*1.0)/($xb1-$xb2)]
        set nb [expr $yb1 - ($mb * 1.0 * $xb1)]
    }

    if {$xa1 == $xa2} {
        set retx [expr $xa1 * 1.0]
        set rety [expr $retx * 1.0 * $mb + $nb]
        if {($rety <= [max $yb1 $yb2]) && ($rety >= [min $yb1 $yb2]) && ($rety <= [max $ya1 $ya2]) && ($rety >= [min $ya1 $ya2]) && \
            ($retx <= [max $xb1 $xb2]) && ($retx >= [min $xb1 $xb2]) && ($retx <= [max $xa1 $xa2]) && ($retx >= [min $xa1 $xa2])} {ety]
        } else {
            return {}
        }
    }

    if {$xb1 == $xb2} {
        set retx [expr $xb1 * 1.0]
        set rety [expr $retx * 1.0 * $ma + $na]
        if {($rety <= [max $yb1 $yb2]) && ($rety >= [min $yb1 $yb2]) && ($rety <= [max $ya1 $ya2]) && ($rety >= [min $ya1 $ya2]) && \
            ($retx <= [max $xb1 $xb2]) && ($retx >= [min $xb1 $xb2]) && ($retx <= [max $xa1 $xa2]) && ($retx >= [min $xa1 $xa2])} {
            return [list $retx $rety]
        } else {
            return {}
        }
    }

    if {$mb == $ma} {
        return {}
    }

    set retx [expr 1.0 * ($na - $nb)/($mb - $ma)]
    set rety [expr 1.0 * ($ma * $retx) + $na]
    if {($rety <= [max $yb1 $yb2]) && ($rety >= [min $yb1 $yb2]) && ($rety <= [max $ya1 $ya2]) && ($rety >= [min $ya1 $ya2]) && \
        ($retx <= [max $xb1 $xb2]) && ($retx >= [min $xb1 $xb2]) && ($retx <= [max $xa1 $xa2]) && ($retx >= [min $xa1 $xa2])} {
        return [list $retx $rety]
    } else {
        return {}
    }
like image 928
SIMEL Avatar asked Dec 16 '22 18:12

SIMEL


2 Answers

IEEE floating point values (which Tcl uses internally because they are supported by your CPU hardware) are very well known to have the feature that they don't exactly represent values. To a first approximation anyway; they do represent values exactly as they have a fixed number of bits (64 for double, which is what Tcl uses) but the value that they represent could be slightly different to what you think it is (because many values can't be represented exactly in fixed number of binary digits, just as 1/3 is almost but not exactly 0.333333333 in decimal; it's the exact same issue, but in another number base).

Tcl takes some limited steps to work around this issue for display purposes; since 8.5 it renders floating point numbers with the minimum number of digits required to get the exact value out again, and in 8.4 and before it simply uses a smaller number of digits when printing a number out (up to 15 decimal digits instead of the 17 that would be required for doing exact representation) where that's controllable via the magic tcl_precision variable. Do not set that variable though; it doesn't do what you need since it is all about the rendering of the value to a string, and not the value itself. Instead, you need to use a different (and very well known) strategy for equality: equal-within-epsilon.

# Magic value! This one is OK for values in the range of small integers
proc equal_float {a b {epsilon 1e-15}} {
    return [expr {abs($a - $b) < $epsilon}]
}

You'd then use it like this:

# Instead of: if {$x == 42.3} { ... }
if {[equal_float $x 42.3]} { ... }

Note that it is another consequence of this is that you should never use floating point numbers for iteration purposes, as that allows errors to accumulate and exceed the epsilon. Instead of going from 0 to 25 by steps of 0.1, go from 0 to 250 in integer steps and then derive the float value by multiplying by 0.1.

like image 148
Donal Fellows Avatar answered Feb 04 '23 15:02

Donal Fellows


To directly address your question, == will fail whenever the two numbers are not equal.

In your question you mention that eq gave a different result than ==. This is due to the fact that eq causes the values to be converted to a string before the comparison is done. When floating point values are converted to strings, they must be rounded up to some value. In the case of two floating point numbers that fall within a certain range, they will be rounded to the same value, yielding the exact same string.

like image 33
Bryan Oakley Avatar answered Feb 04 '23 15:02

Bryan Oakley