2

As far as I understand now, Decimal is 2 times bigger than Double (128 bits vs 64 bits). So it can represent numbers with bigger precision. But it also uses numeric system with base 10, instead of binary. Maybe the last trait of Decimal affects the following results?

Microsoft (R) F# Interactive version 11.0.60610.1
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> let x = 1.0m / 3.0m ;;
val x : decimal = 0.3333333333333333333333333333M

> x * 3.0m ;;
val it : decimal = 0.9999999999999999999999999999M

> let y = 1.0 / 3.0 ;;
val y : float = 0.3333333333

> y * 3.0 ;;
val it : float = 1.0

> it = 1.0 ;;
val it : bool = true

As you can see, Double prints as 1.0 again after division and multiplication by 3.0. I tried different divisors, and the situation is the same.
(note for ones who don't know F# - float is basically synonym for double in F#)

4

2 回答 2

1

您观察到的行为double归因于 1/3 乘以 3 的结果与 1/3 的比例不同。这种情况类似于如果始终精确保留三位小数,计算 1.00/7.00(产生 0.143)并将该结果乘以 7(其确切值为 1.001,但会四舍五入)至 1.00)。从本质上讲,除法得到了一个重要的数字(精确到 0.001,即使原始数字只精确到 0.01),这允许乘法产生正确的结果。

使用 type Decimal,即使x/y不能精确存储,如果乘法导致规模变化需要舍入步骤, (x/y)*y(where 1 < y < 10) 的值通常也会相等。x(1D/3D)*3D 不产生 1D 的原因是,虽然Decimal高于大约 7.923 的值每超过该数量的 10 次方就会在右边失去一个小数位,但低于 0.7922 的值不会获得位置。因此,将 7.923 到 23.76 范围内的值除以 3,然后乘以 3 将得到原始值;同样,如果使用值 79.23 到 233.76 等。将低于 7.923 的值除以任何大于 1 的值通常不是可逆运算,除非结果是 10^-28 的精确倍数。

于 2013-09-11T20:30:45.060 回答
1

当以我们通常的十进制表示法显示时,该类型decimal的好处是它是所见即所得:打印足够多的十进制数字(0.3333333333333333333333333333M当然看起来足够),您可以看到机器正在使用的确切数字。毫不奇怪,三倍0.9999999999999999999999999999M:您可以用笔和纸完成并重现结果(2)。

在二进制中,需要更多的十进制数字才能看到所表示的确切数字,而且它们通常不会全部打印出来(但情况就像它们一样简单)。在这种情况下,3.0by的二进制乘法只是一个巧合。该属性适用于某些数字,但不一定适用于所有数字。事实上,结果可能不是 1.0,而且您的语言打印的十进制数字可能比显示的要少。点后有 16 位数字的指数形式 1.DD…DDEXXX 足以区分所有双精度数字,尽管它不显示数字的确切值。1.0 / 3.01.0

所以,总结一下:

  • 十进制是所见即所得,你得到 0.99……因为你将 0.33……乘以 3
  • 二进制结果可能不是 1.0,但仅使用您语言中二进制数的默认有限小数位数打印
  • 即使它是 1.0,这也是一个巧合,可能不会发生在另一个数字而不是 3.0 上。

杂项说明

  1. 如果 F# 在这方面类似于 OCaml,您可以打印足够的小数来区分 1.0 和float另一个Printf.printf "%.16e"
  2. F# 的decimal类型是所见即所得,但您必须记住,有些数字有 28 位精度,大多数有 29 位。有关详细信息,请参阅 supercat 的答案或下面的评论。
  3. 对于二进制浮点,十六进制表示法与十进制表示法具有相同的 WYSIWYG 属性decimal。在所有语言和年份中,C99 对精细浮点操作的支持最好,它支持十六进制的输入和输出。

一个例子:

#include <stdio.h>

int main(){
  double d = 1 / 3.0;
  printf("%a\n%a\n", d, 3*d);
}

执行产生:

$ gcc -std=c99 t.c && ./a.out 
0x1.5555555555555p-2
0x1p+0

用笔和纸,我们可以0x1.5555555555555p-2乘以3。我们得到0x3.FFFFFFFFFFFFFp-2, 或0x1.FFFFFFFFFFFFF8p-1归一化后。这个数字不能完全表示为 binary64 浮点数(它有太多有效数字),乘法返回的“最近”可表示数字是1.0. (应用平局必须四舍五入到最接近的偶数的规则。在两个同样接近的备选方案0x1.FFFFFFFFFFFFFp-11.0中,1.0结果是“偶数”。)

于 2013-09-11T07:54:03.790 回答