了解 Float>>asFraction 及其变体

问题描述

我目前对类方法 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,也没有提到任何变体。

解决方法

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

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

在 Pharo 中,FloatFloat >> #asFraction 相同,因此没有区别。

Float >> #asTrueFraction 中的注释非常清楚,它会给出您通常期望的内容,即转换回为浮点数时将等于 self 的最短小数部分

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

,

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

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

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


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

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

在这个片段中,'0.001' asNumber 是唯一可以做任何近似的操作:而不是返回一个表示数字 0.001 的 Float 实例(事实上,没有这样浮!),它返回一个Float表示的最接近(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 获得相同的效果,但这会使近似值的发生变得更加模糊
  • y := x asExactFraction 在 GNU Smalltalk 中,或 y := x asTrueFractiony := asFraction 在 Pharo 中,将 ? 设置为一个 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. Ya。 Khinchin,Continued Fractions,芝加哥大学出版社,1964 年,第 6 节“Convergents as best approximations”,用于进一步讨论连分数收敛作为最佳有理近似。 连分数是数学中一个美丽的角落,但可悲的是在现代教育中被忽视了!

,

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

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

这就是0.001 真正在引擎盖下的样子,作为有限精度的二进制浮点数(仅限 64 位) )。这就是为什么它等于1/1000

1/1000 = 0.001
"=>" false

如果你想要精确小数和无限精度,你需要告诉系统。像 0.001s 这样的十进制数确实与分数 1/1000 完全相等:

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

1/1000 = 0.001s
"=>" true

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

,

对于已经很好的答案,我唯一想补充的就是突出显示一些合同。

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

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

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

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

大多数 Smalltalk 方言也是如此,在 2000 年初,1/10 = 0.1 确实回答为 true,尽管这两个数字的值不完全相同...幸运的是,我们采用了更明智的 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 中,打印 Float 精确十进制值的方法是这样的:

aFloat printShowingMaxDecimalPlaces: Float emin - Float precision + 1.

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

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

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

虽然连分数是个好东西,但我不知道有任何关于 asApproximateFraction 的合同。它可能会也可能不会四舍五入到相同的浮点数。问题是我们在哪里停止递归?

历史注释:转换 Integer>>asFloatFraction>>asFloat 将在现代 Smalltalk 中回答最接近其确切值的 Float,至少在 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 实现都应该关心这些特性(合约)。