据我所知,Decimal是Double的两倍(128位对64位)。因此它可以表示更高精度的数字。但它也使用数字系统与基数10,而不是二进制。也许Decimal的最后一个特征会影响以下结果?
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
如您所见,Double在分割后再次打印为1.0并乘以3.0。我尝试了不同的除数,情况是一样的
(注意那些不了解F#的人 - float
基本上是F#中double
的同义词。
答案 0 :(得分:1)
使用我们通常的十进制表示法显示类型decimal
的好处是,它是WYSIWYG:打印足够的十进制数字(并且0.3333333333333333333333333333M
看起来已经足够了),你可以看到确切的数字机器正在使用。毫无疑问,有三次0.9999999999999999999999999999M
:你可以用笔和纸做,并重现结果(2)。
在二进制文件中,它需要更多的十进制数字来查看所表示的确切数字,并且它们通常不会全部打印出来(但情况就像它们一样简单)。在这种情况下,3.0
乘以1.0 / 3.0
的二进制乘法会产生1.0
,这只是巧合。财产holds for some numbers but does not have to hold for all numbers。实际上,结果可能不是1.0,并且您的语言可能打印的十进制数字少于显示的数字。指数形式1.DD ... DDEXXX在点之后有16位数字,足以区分所有双精度数字,虽然它没有显示数字的确切值。
所以,总结一下:
float
区分为Printf.printf "%.16e"
。decimal
类型是WYSIWYG,但你必须记住,有些数字有28位精度,大多数有29位。有关详细信息,请参阅supercat的答案或下面的注释。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-1
和1.0
中,1.0
结果是“甚至”一个。)
答案 1 :(得分:1)
您在double
中观察到的行为可归因于将1/3乘以3的结果与1/3的比例不同。这种情况类似于如果总是保持三个十进制数字,一个是计算1.00 / 7.00(产生.143)并将结果乘以7(其精确值将是1.001,但是得到舍入)到1.00)。从本质上讲,该部门获得了一个重要的数字(即使原始数字仅精确到0.01,精确到0.001),这允许乘法产生正确的结果。
对于Decimal
类型,即使无法准确存储x/y
,(x/y)*y
(其中1 < y < 10
)的值通常等于x
导致需要舍入步骤的规模变化。 (1D / 3D)* 3D不产生1D的原因是,虽然高于大约7.923的Decimal
值在每个10次幂的右边输入小数位,但是超过0.7922的值不是获得名额。因此,除以3在7.923到23.76范围内的值然后乘以3将产生原始值;同样,如果使用值79.23至233.76等,将值大于7.923除以任何大于1的值通常不是可逆操作,除非结果是10 ^ -28的精确倍数。