4

在下面的代码中,我遇到了一个奇怪的现象。在线上运行时,if {$ma == $mb}两者mbma相等1.0,但未采用 if。当我将 to 更改==eqor[cequal $ma $mb]时, if 被采用。

此外,当我尝试使用1.0(if {$ma == 1.0}if {$1.0 == $mb}) 更改其中一个变量时,if 也没有被采用,但是带有表达式的 ifif {1.0 ==1.0}被采用。

==换成之后发生在我身上的另一件事eq0.0and-0.0使用时不相等eq,但使用时相等==

这些差异的根源是什么?

我知道在浮点数比较中,最好使用一个小的 epsilon 来检查两个数字是否真的彼此接近,但在这种情况下,比较是为了避免被零除。

代码:

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 {}
    }
4

3 回答 3

6

众所周知,IEEE 浮点值(Tcl 在内部使用,因为它们受 CPU 硬件支持)具有它们不能准确表示值的特性。无论如何都是第一个近似值;它们确实代表了值,因为它们具有固定数量的位(64 位double,这是 Tcl 使用的),但它们代表的值可能与您认为的略有不同(因为许多值不能在固定数量的二进制数字,就像1 / 3几乎但不完全是十进制的 0.333333333;这是完全相同的问题,但在另一个数字基数中)。

为了显示目的, Tcl 采取了一些有限的步骤来解决这个问题;从 8.5 开始,它使用最少的位数呈现浮点数,以便再次获得准确的值,而在 8.4 及之前,它在打印数字时仅使用较少的位数(最多 15 个十进制数字而不是 17这将是进行精确表示所必需的),这可以通过魔法tcl_precision变量进行控制。但是不要设置该变量;它不能满足您的需要,因为它只是将值呈现为字符串,而不是值本身。相反,您需要使用不同的(并且众所周知的)平等策略: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}]
}

然后你会像这样使用它:

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

请注意,这样做的另一个结果是您永远不应该将浮点数用于迭代目的,因为这会导致错误累积并超过 epsilon。不是以 0.1 的步长从 0 到 25,而是以整数步长从 0 到 250,然后通过乘以 0.1 得出浮点值。

于 2012-10-03T09:10:46.750 回答
4

要直接解决您的问题,==只要两个数字不相等,就会失败。

在您的问题中,您提到eq的结果与==. 这是因为eq在比较完成之前将值转换为字符串。当浮点值转换为字符串时,它们必须四舍五入到某个值。如果两个浮点数落在某个范围内,它们将四舍五入到相同的值,产生完全相同的字符串。

于 2012-10-02T16:42:40.997 回答
2

==强制进行数字比较,同时eq强制进行字符串比较(cequal如果记忆对我有用,并且您来自 Tclx,并且本质上与 具有相同的语义eq)。

为严格相等而比较浮点值的问题在设计上存在缺陷,因为这些类型不包含精确值(与整数相反)。比较两个浮点数的一种明智方法是定义某个(非常小的)常数“epsilon”,并查看它们差异的绝对值是否小于该“epsilon”。如果小于,则声明两个浮点数相同,否则不一样。一个快速的谷歌搜索出现了这篇文章这个页面,以及其他有用的链接。

另一种方法是尝试使用整数(通过在适当的情况下使用适当的放大和缩小以防止不足/溢出)。

于 2012-10-02T16:45:29.683 回答