【问题标题】:Understanding Float>>asFraction and its variants了解 Float>>asFraction 及其变体
【发布时间】:2021-05-20 23:05:03
【问题描述】:

我目前对类方法Float>>asFraction 及其各种形式提供的响应感到困惑。以下是几个例子:

GNU Smalltalk

0.001 asFraction
1/1000
0.001 asExactFraction
1152921504606847/1152921504606846976

法罗

0.001 asFraction
1152921504606847/1152921504606846976
0.001 asTrueFraction
1152921504606847/1152921504606846976
0.001 asMinimalDecimalFraction
1/1000
0.001 asApproximateFraction
1/1000

出于显而易见的原因,GNU 的 asFraction 和 Pharo 的 asMinimalDecimalFractionasApproximateFraction 对我来说最有意义,因为它们在数学上产生了更“精确”的结果。我不明白其他人。为什么分子和分母很大但值明显不太精确的分数会是对asExactFraction 的响应?为什么我会想要这样的回应?为什么在 Pharo 中我选择 asFractionasTrueFraction 似乎并不重要?为什么会有这些变种?

如果我想将浮点数表示为分数,我想我可能想要基于构成分子和分母的整数的精度等级,或者基于最大分母的最接近近似值。

我查看了 Bluebook,它几乎没有提到 asFraction,也没有提到任何变体。

【问题讨论】:

  • 您认为哪个更准确,1/1000 还是 1152921504606847/1152921504606846976?你知道 0.001 不能用二进制精确表示吗?有关详细信息,请参阅 xhttps://stackoverflow.com/questions/1089018/why-cant-decimal-numbers-be-represented-exactly-in-binary。
  • @JamesFoster 我明白 1/1000 不能精确地表示为作为二进制浮点数。但是,作为表示为两个 整数 分子 1 和分母 1000 的比率的分数,比给出的替代方案更精确。所以你说的是“精确”他们真正的意思是,在尝试用二进制浮点数表示 0.001 之后,你实际上得到 1152921504606847/1152921504606846976,那么这是对精确的不同看法。我不清楚这是什么意思。

标签: floating-point precision smalltalk fractions


【解决方案1】:

我想在已经很出色的答案中添加的唯一内容是突出显示一些合同。

第一个约定,现代 Smalltalk 中的相等、不等和比较操作总是基于比较确切的值。至少,在 Dolphin、gnu、Pharo、Squeak 上是这样。

并非总是如此。以这段 C 代码为例:

int64_t i=1<<60+1;
double d=(double) i;
printf("%d\n',d==i);

这两个数字不具有相等的值(它们不能,因为整数需要 61 位,而 double 只提供 53 位的有效位)。虽然相等的结果是真的,因为整数值在测试之前被转换为双精度。

这也是大多数 Smalltalk 方言的情况,在 2000 年初,1/10 = 0.1 确实回答正确,尽管这两个数字的值不完全相同...幸运的是,我们采用了更明智的 Scheme 语言策略,因为:比较没错。

现在我们有了平等合同,我们可以表达更多关于转换的合同。第一:

aFloat asTrueFraction = aFloat.
"which means that they share the exact same value"
"replace with asExactFraction in gst"

第二份合同是这样的:

aFloat asMinimalDecimalFraction asFloat = aFloat.
"Though the decimal fraction may differ, it will always convert back to same float"

asMinimalDecimalFraction 将回答将舍入为相同浮点数的最短小数部分。它与快速准确地打印浮点数非常相关,实际上共享相同的算法。这与 Python 中的 repr 完全相同。另请参阅 Squeak/Pharo 中的 absPrintExactlyOn:。请注意,这不是一个好名字,因为它不打印 EXACT 值,而是输出将舍入到相同浮点数的 SHORTEST 值(因此,它可以是在阅读/评估/打印活动中无所畏惧地使用)。

在 Squeak 中,打印浮点数的精确十进制值的方法如下:

aFloat printShowingMaxDecimalPlaces: Float emin - Float precision + 1.

这是因为可以用双精度表示的二的最小幂是

(2 raisedTo: Float emin - Float precision + 1) = Float fminDenormalized.

因为 1/2^n 需要打印小数点后 n 位(即 5^n/10^n)。

虽然连分数是一件好事,但我不知道有任何关于asApproximateFraction 的合同。它可能会或可能不会返回到相同的浮点数。问题是我们在哪里停止递归?

历史记录:Integer&gt;&gt;asFloatFraction&gt;&gt;asFloat 的转换将回答最接近现代 Smalltalk 中精确值的浮点数,至少在 gst、Squeak/Pharo 中。 2000 年初的情况并非如此,也许每个方言的方言都不是这样。写成合同:

(aFraction - aFraction asFloat asTrueFraction) abs <= (aFraction - aFraction asFloat predecessor asTrueFraction) abs and: [
(aFraction - aFraction asFloat asTrueFraction) abs <= (aFraction - aFraction asFloat successor asTrueFraction) abs] 

未能提供这些基本属性会破坏表达更高级别的干净和清晰合同的机会。当您尝试检查并了解发生的情况时,这也可能会产生很大的误导。

如今,每个 Smalltalk 实现都应该关注这些特性(合同)。

【讨论】:

  • 谢谢,这很有帮助。一些 cmets/answers 似乎假设我对 CPU 中的数字表示知之甚少,这根本不是我的困惑所在。最终,我只是想知道当它说asExactFraction(或asTrueFraction 中的“真”)时,“精确”是什么意思。但你的回答很好地超越了这一点。
【解决方案2】:

Float 是一种编码数字的数据结构,无论我们如何看待或解释它,从数学上讲,它只能是有理数(即整数或分数)。这种编码适用于 CPU 高速执行的算术运算。我们付出的代价是编纂没有表现出它所代表的分子和分母。 Float &gt;&gt; #asTrueFraction 方法用这些数字来回答,换句话说,它解码 Float 实例中包含的位,并用它编码的实际分数来回答。

您必须了解的是,当您编写0.001 时,您是在告诉编译器创建一个近似于分数1/1000Float。如果 CPU 使用十进制而不是二进制表示,这类似于要求它使用有限的小数位数来编码1/3,这将不可撤销地导致0.33333..3,以获得最大位数3。在分母不是2 的幂的情况下,CPU 必须解决类似的问题并最终逼近提供的数量,以使其适合分配给Floats 的位数。 #asTrueFraction 方法反转该过程并揭示近似值的确切值,Float 隐藏在它打印其实例的方式后面。

在 Pharo 中,Float &gt;&gt; #asFractionFloat &gt;&gt; #asTrueFraction 相同,因此没有区别。

Float &gt;&gt; #asMinimalDecimalFraction 中的注释很清楚,它会给出你通常所期望的,这就是,当转换回 asFloat 时等于 self 的最短十进制分数

最后,Float &gt;&gt; #asApproximateFraction 使用某种算法来生成可接受的接收器近似值。

【讨论】:

  • 感谢您的周到回答。我确实对计算机中的数字表示及其局限性有相当多的了解。我想我不明白他们选择“确切”的意图。对我来说,如果我有一个像 0.001 这样的数字,我知道它可能在计算机中有一个精确的二进制浮点表示。当我转换为分数时,我的意图可能是为了算术目的得到更精确的东西。出于这个原因,我认为 1/1000 响应比大比例响应更“精确”。我对“确切”的定义与他们的不符。 :)
  • 我可能偶然发现了这一点,因为我拥有计算机工程和数学学位。数学方面接管了我对“精确”的解释。
  • 很高兴您提出了这个问题,因为这些消息可能会让人感到困惑,即使对于像您这样对浮点表示非常了解的人来说也是如此。
  • 我确实发现 Float &gt;&gt; asApproximateFraction 是这个系列中最有趣的。我需要玩一下,看看他们在做什么。 :)
【解决方案3】:

虽然其他答案深入研究为什么分数 1/1000 不等于 64 位二进制浮点数 0.001,但答案略有不同:

0.001 printStringBase: 2
"=>" '1.00000110001001001101110100101111000110101001111111e-10'

这就是0.001 真正 的外观,作为有限 精度的二进制 浮点数(仅限64 位)。这就是为什么它等于1/1000

1/1000 = 0.001
"=>" false

如果您想要 exact 具有 unlimited 精度的小数,您需要告诉系统。像0.001s 这样的十进制数确实完全等于小数1/1000

0.001s asFraction
"=>" (1/1000)

1/1000 = 0.001s
"=>" true

我们不经常使用小数的原因是它们的效率较低 - 64 位二进制浮点数学是在硬件中实现的,精确数学是在软件中实现的,这使得它慢了几个数量级。

【讨论】:

    【解决方案4】:

    出于显而易见的原因,GNU 的 asFraction 和 Pharo 的 asMinimalDecimalFractionasApproximateFraction 对我来说最有意义,因为它们在数学上产生了更“精确”的结果。

    相反,他们执行的操作是找到输入的近似值。 但他们收到的输入实际上并不是数字 0.001,尽管这似乎是你写的——而且这些方法中的任何一个都无法知道你最初写的是什么。

    因此,有些方法会准确返回给定的数字(以不同的表示形式),而另一些方法则返回与您最初编写的文本巧合的近似值(如果令人困惑的话!)。


    稍微改写一下代码可能会有所帮助,以便您了解实际发生的近似值。 让我们首先关注 GNU Smalltalk。

    x := '0.001' asNumber.
    y := x asExactFraction.
    

    在这个片段中,'0.001' asNumber 是唯一进行任何近似的操作: 而不是返回代表数字 0.001 的 Float 实例(事实上,没有这样的浮点数!)时,它返回一个Float表示最接近 EM>(IEEE 754 binary64)浮点数,其可以进行各种写为1152921504606846976分之1152921504606847,或作为0.001000000000000000020816681711721685132943093776702880859375,或作为0x1.0624dd2f1a9fcp-10中的精确写入二进制浮点数的最方便的形式。

    只需写0.001 即可得到相同的结果:Smalltalk 将自动舍入到最接近的浮点数。 我将其明确写为'0.001' asNumber,以明确这是返回您编写的数字 0.001 的近似值的操作。

    然后y := x asExactFraction 将? 设置为一个Fraction 实例,代表完全相同的数字;同样在 Pharo 中使用 y := x asTrueFraction。 号码还是1152921504606847/1152921504606846976; asExactFraction永远不会返回分母中除 2 的幂之外的任何数字(至少,不是用于存储二进制浮点数的类)。


    相反,如果您评估(在 GNU Smalltalk 中)

    z := x asFraction.
    

    那么你在 ? 中得到的是一个 Fraction 实例,它表示最简单四舍五入到 ? 的有理数——非常粗略地说,区间 [? - ulp(?)/ 中最简单的有理数2, ? + ulp(?)/2],其中 ulp(?) ≈ 2−52? 是 ? 的浮点表示的最低有效数字的大小(在区间的边缘以及当 ? 等于 2 的幂时)。 这里,区间内“最简单”的有理数是分母最小的有理数。 ? 的近似值是通过扩展 ? 的连分数表示直到第一个收敛到 ?。1

    这可能(尽管我没有仔细观察以验证)与您使用Pharo's definition of asApproximateFraction 得到的相同。 相反,Pharo's asMinimalDecimalFraction 不会返回最简单的理性;相反,它只考虑分母中具有 10 = 2⋅5 次方的有理数,并返回分子最小的有理数,将四舍五入为?。


    总结:

    • x := '0.001' asNumber套?到Float实例表示(IEEE 754 binary64)浮点数最接近0.001,这是1152921504606846976分之1152921504606847= 0.001000000000000000020816681711721685132943093776702880859375 = 0x1.0624dd2f1a9fcp-10;您可以通过编写 x := 0.001 获得相同的效果,但这使得近似值的发生更加模糊
    • GNU Smalltalk 中的 y := x asExactFraction 或 Pharo 中的 y := x asTrueFractiony := asFraction 将 ? 设置为一个 Fraction 实例,表示 与 ?
    • 完全相同的数字
    • GNU Smalltalk 中的 z := x asFraction 或 Pharo 中的 z := x asApproximateFraction 将 ? 设置为 Fraction 实例,该实例表示 最简单的有理数,该实例将四舍五入为 ?
    • Pharo 中的w := x asMinimalDecimalFraction 将? 设置为Fraction 实例,表示具有最短十进制扩展 的数字,该数字将四舍五入为?;如果您想以十进制表示法编写浮点数,则可以使用它,并确保您得到相同的数字,而无需编写比您需要的数字更多的数字

    (如您所见,GNU Smalltalk 和 Pharo 在 asFraction 是否应该返回一个近似值上存在分歧:在 GNU Smalltalk 中是这样,而在 Pharo 中则不是。 这很不幸,因为这是两人共享的唯一名字!)


    为了好玩,请在 Pharo 中尝试以下示例:

    3.141592653589793 asApproximateFractionAtOrder: 1
    3.141592653589793 asApproximateFractionAtOrder: 2
    3.141592653589793 asApproximateFractionAtOrder: 3
    3.141592653589793 asApproximateFractionAtOrder: 4
    3.141592653589793 asApproximateFractionAtOrder: 5
    3.141592653589793 asApproximateFraction
    3.141592653589793 asMinimalDecimalFraction
    3.141592653589793 asTrueFraction
    
    1.618033988749895 asApproximateFractionAtOrder: 1
    1.618033988749895 asApproximateFractionAtOrder: 2
    1.618033988749895 asApproximateFractionAtOrder: 3
    1.618033988749895 asApproximateFractionAtOrder: 4
    1.618033988749895 asApproximateFractionAtOrder: 5
    1.618033988749895 asApproximateFraction
    1.618033988749895 asMinimalDecimalFraction
    1.618033988749895 asTrueFraction
    

    看看你是否注意到关于输出的任何东西——也许你会认出一些分数;看看它们与真实分数的绝对和相对误差有多远;看看分母有多大。


    1 这就是GNU Smalltalk's definition of asFraction 目前所做的。 从技术上讲,文档对近似的性质没有任何承诺,但这是Fraction 最自然的方法,因为它提供了独立于任何基数选择的最佳有理近似。 见 A. 雅。 Khinchin,Continued Fractions,芝加哥大学出版社,1964 年,第 6 节“Convergents as best approximations”,用于进一步讨论作为最佳有理逼近的连分数收敛。 连分数是数学中一个美丽的角落,但在现代教育中却被忽视了!

    【讨论】:

    • 感谢您的详细解释。我已经了解计算机中浮点数的 IEEE 表示的局限性,而且 0.001 对我来说并不是 确切地 0.001 所表示的。让我震惊的是不知道“确切”是什么意思。我在想,如果我从 0.001 开始并生成一个 IEEE 浮点表示,那么如果我将分母限制为“大值”,那么 1/1000 可能是最接近该表示的有理数。但我想,也许没有充分的理由,如果那个“大值”是最大可表示的整数,我不会得到 1/1000。
    • 你确实启发了我进一步探索这一点。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-22
    • 2021-10-27
    • 1970-01-01
    相关资源
    最近更新 更多