Double和Decimal的精度(在.NET中)

时间:2013-09-11 06:30:31

标签: .net floating-point decimal precision floating-accuracy

据我所知,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的同义词。

2 个答案:

答案 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位数字,足以区分所有双精度数字,虽然它没有显示数字的确切值。

所以,总结一下:

  • 十进制是WYSIWYG,你有0.99 ...因为你乘以0.33 ...乘以3
  • 二进制文件的结果可能不是1.0,但只能用您的语言中的二进制数字的默认有限小数位数打印
  • 即使它是1.0,这也许是另一个数字而不是3.0的巧合。

杂项说明

  1. 如果F#在这方面与OCaml相似,则可以打印足够的小数,以便将1.0与另一个float区分为Printf.printf "%.16e"
  2. F#的decimal类型是WYSIWYG,但你必须记住,有些数字有28位精度,大多数有29位。有关详细信息,请参阅supercat的答案或下面的注释。
  3. 十六进制表示法对于二进制浮点具有相同的WYSIWYG属性,因为十进制表示法具有decimal。 所有语言和年份的C99都支持精细的浮点操作,并且支持输入和输出的十六进制。
  4. 一个例子:

    #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-20x1.FFFFFFFFFFFFF8p-1。此数字不能完全表示为binary64浮点数(它有太多有效数字),乘法返回的“最近”可表示数字是1.0。 (应用必须四舍五入到最近的偶数数字的规则。在两个同样接近的替代0x1.FFFFFFFFFFFFFp-11.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的精确倍数。