双重和舍入很难,但这很疯狂

时间:2013-11-07 10:24:10

标签: c# math rounding-error

double a = 135.24;          // a is set to 135.24000000000001 actually
double b = Math.Round(a, 0);    // set to 135.0
double c = Math.Round(a, 1);    // set to 135.19999999999999
double d = Math.Round(a, 2);    // set to 135.24000000000001 
double e = Math.Round(a, 3);    // set to 135.24000000000001 
double f = Math.Round(a, 4);    // set to 135.24000000000001 
double g = Math.Round(a, 5);    // set to 135.24000000000001 
double h = Math.Round(a, 10);   // set to 135.24000000000001 
double i = Math.Round(a, 14);   // set to 135.24000000000001 


double j = Math.Round(a, 2
 , MidpointRounding.AwayFromZero ); // set to 135.24000000000001 
double k = Math.Round(a, 2
 , MidpointRounding.ToEven );   // set to 135.24000000000001 

Sooooo,这意味着135.24不能用双精度表示,对吗?

5 个答案:

答案 0 :(得分:5)

是的,135.24不能用double表示,因为double使用二进制指数表示法。

即:135.24可以以2为基数以指数形式表示为1.0565625 * 128 =(1 + 1/32 + 1/64 + 1/128 + 1/1024 + ...)*(2 ** 7)

表示不能完全表达,因为13524不会除以5.让我们看看:

  

135.24 = 13524/(10**2)

     

表示是有限的<=>存在整个x和n满足135.24 = x/(2**n)

135.24 = x/(2**n)
13524 / (10**2) = x / (2**n)
13524 * (2**n) = (10**2) * x
13524 * (2**n) = 2*2*5*5 * x
     

左侧没有“5”,所以无法完成   (称为算术的基本定理)

一般来说,只有在十进制数的素数因子分解中有足够数量的“五”时,有限二进制表示才是精确的。

现在有趣的部分:

    double delta = 0.5;
    while( 1 + delta > 1 )
        delta /= 2;

    Console.WriteLine( delta );

double的精度在1附近不同,在0附近不同,对于某些大数字不同。维基百科上的一些二进制表示示例:Double precision floating point format

但最重要的是内部处理器浮点堆栈可能比8字节(双倍)具有更强的更好的精度。如果数字不必转移到RAM并且被剥离到8个字节,我们就可以得到非常好的精度。

在不同的处理器(AMD,Intel)上测试类似的东西,语言(C,C ++,C#,Java)或编译器优化级别可以得到大约1e-16,1e-20甚至1e-320 < / p>

看看CIL /汇编程序/ jasmin代码,看看究竟发生了什么(例如:for C ++ g++ -S test.cpp创建带有汇编程序代码的test.s文件)

答案 1 :(得分:4)

这通常是浮点数的问题。如果您需要精确的数字表示(例如,用于计费,......),那么您应该使用十进制。 尝试下面的代码,你会发现你没有输出0,0.1,0.2,...... 1.0。

for(double i = 0; i <= 1.0; i += 0.001)
{
    Console.WriteLine(i);
}

答案 2 :(得分:3)

尝试使用小数代替。浮点不是很精确(因此无法表示某些数字):)

答案 3 :(得分:0)

是的,它不能。这就是为什么还有一个名为decimal的非整数数据类型。它需要不同的内存量,并且具有与double不同的最小/最大数值范围,并且不能进行位转换*)加倍,但反过来它可以精确保持数字而不会出现任何失真。

*)也就是说,你不能把它复制为字节并推送到C ++代码。但是,您仍然可以cast将其加倍并返回。请注意,演员阵容不准确,因为double无法保留decimal可以包含的某些数字,反之亦然

答案 4 :(得分:0)

你可以看到定义。圆函数定义为 -

public static double Round(double value, int digits, MidpointRounding mode)
    {
      if (digits < 0 || digits > 15)
        throw new ArgumentOutOfRangeException("digits", Environment.GetResourceString("ArgumentOutOfRange_RoundingDigits"));
      if (mode >= MidpointRounding.ToEven && mode <= MidpointRounding.AwayFromZero)
        return Math.InternalRound(value, digits, mode);
      throw new ArgumentException(Environment.GetResourceString("Argument_InvalidEnumValue", (object) mode, (object) "MidpointRounding"), "mode");
    }


private static unsafe double InternalRound(double value, int digits, MidpointRounding mode)
    {
      if (Math.Abs(value) < Math.doubleRoundLimit)
      {
        double num1 = Math.roundPower10Double[digits];
        value *= num1;
        if (mode == MidpointRounding.AwayFromZero)
        {
          double num2 = Math.SplitFractionDouble(&value);
          if (Math.Abs(num2) >= 0.5)
            value += (double) Math.Sign(num2);
        }
        else
          value = Math.Round(value);
        value /= num1;
      }
      return value;
    }