为什么在 Tcl 中嵌套 `expr` 时算术结果会有所不同?

4

了解由于浮点数的内部表示而导致的不准确性(请参见维基百科,向下搜索:“等式测试的使用”...),但在执行以下操作时仍会有意外情况:

% expr int(0.57 * 10000)
5699
% expr int([expr 0.57 * 10000])
5700

嵌套的“expr”是否被禁止?为什么会改变传递的浮点值?还是改变了浮点数算术执行的顺序,从而影响了结果?更新:关于比较浮点数速度的话题,这里有一些很好的阅读材料(以及次要链接),这里介绍了一些基础和高级内容,在IEEE 754-2008标准的描述中也有相关内容
1个回答

7

这是一个有趣的问题,因为它涉及到一些微妙的事情。

% expr int(0.57 * 10000)
5699

这段代码本身没有进行任何替换,所以它的工作方式是“不出所料的”。它表明浮点数本身就是令人惊讶的东西。特别地,0.57在IEEE双精度浮点数(即基于2)中没有一个精确的表示; 事实上,它的表示略低于精确的0.57,因此当向下舍入(这就是int(...)的做法; 10000本身是精确的),你会得到5699。你也会在其他语言中看到同样的行为。

% expr int([expr 0.57 * 10000])
5700

现在这非常有趣。首先看到内部计算完成并将结果浮点数转换为字符串(因为没有其他方法可以实现)。现在,您必须使用Tcl 8.4(或更早版本),其中默认的数字呈现规则实际上是您打印数字的前15个有效数字,即在此情况下,它会给出5700.00000000000(右侧一些零被截断),然后重新解释为首先是一个double(恰好是5700.0),然后转换为5700。
数字转换规则在Tcl 8.5中发生了变化。现在,当Tcl将浮点数转换为字符串时,它会生成最短的字符串,该字符串将精确地转换回相同的浮点数(即,通过字符串之旅将在结果双倍中给出相同的位模式)。反过来,这意味着您现在永远不会看到上述两个事物之间的差异。(如果您真的想强制使用固定的小数位数,请使用format %.15g [expr 0.57 * 10000]。)
如果您正确加括号,也就是社区建议人们做的十多年来,您也不会看到8.0到8.4的观察行为。
$ tclsh8.4
% expr {int([expr 0.57 * 10000])}
5699

这是因为它不会强制将内部expr调用的结果解释为字符串。(这也更快、更安全。)

感谢您确认了我的猜想并增加了很多知识。是的,如果有一个tcl8.4标签,我会设置它,夏洛克;-) - cfi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接