【问题标题】:Why does 1.0 not equal to 1.0 in tcl when using ==为什么使用==时tcl中的1.0不等于1.0
【发布时间】:2012-10-02 15:56:38
【问题描述】:

在下面的代码中,我遇到了一个奇怪的现象。在 if {$ma == $mb} 线上运行时,mbma 都等于 1.0,但没有采用 if。当我将== 更改为eq[cequal $ma $mb] 时,if 被采用。

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

在将== 更改为eq 后发生在我身上的另一件事是0.0-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 {}
    }

【问题讨论】:

  • 你确定这两个变量的字面值都是 1.0 吗?是否有可能其中一个的值像 1.0000000001 并且由于四舍五入而打印为 1.0?
  • @BryanOakley,我不知道,当我执行[set ma][puts $ma] 时,我得到了1.0,而eq 在它们之间确实返回了true。
  • 当您使用eq 时,Tcl 会将值转换为字符串,从而丢弃精度。
  • 我注意到您在使用 expr 时没有在表达式周围加上大括号,这非常慢,因为它每次都强制重新解析表达式(以防万一其中一个论点是1+2+3*4-79 或其他)。除非您需要重新解释,否则将表达式放在大括号中并使其快速运行。 (它也更安全,因为它使像 [exit] 这样的值成为语法错误,而不是结束进程的调用......)

标签: floating-point tcl


【解决方案1】:

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

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 得出浮点值。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 实际上,浮点数包含精确值。只是不一定是您正在寻找的精确值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-19
      • 2013-10-02
      • 1970-01-01
      • 2019-09-28
      • 2014-08-23
      • 2014-02-16
      • 2013-05-23
      相关资源
      最近更新 更多