System.Math.Round - 舍入到零数字并将Round与Round除以一个或多个数字

时间:2018-01-11 21:59:51

标签: c# math rounding

在我的一方误解之后,在阅读问题的答案时How to get numbers to a specific decimal place ...

参考问题的总结:

问: Feii Momo想知道:如何在0.05步内将金额四舍五入到最接近的值。

答: Enigmativity提供的解决方案是将值乘以20并将其四舍五入,至少将其除以20

Math.Round(value * 20.0m, 0) / 20.0m

......我想出了一个更通用的问题:

  

这两种方法之间是否有任何实际的优点/缺点:

(I)  var rValue = Math.Round(value * 10.0m , 0) / 10.0m;  
(II) var rValue = Math.Round(value, 1);

到目前为止我做了什么:

起初我看了Docs - System.Math.Round,但我找不到任何提示。我还看一下Reference Source - Decimal Round以查看是否有任何不同的执行分支,但到目前为止它只出现:

public static Decimal Round(Decimal d, int decimals)
{
    FCallRound (ref d, decimals);
    return d;
}

FCallRound as:

private static extern void FCallRound(ref Decimal d, int decimals);

很遗憾,我没有为FCallRound找到一些代码。

之后我想更实际地考虑它,并想看看是否有任何性能差异,在四舍五入到0位或1..n数字和"raced the horses"之间。

首先我run这三个函数调用:

(1) var rValue = Math.Round(value, 0);
(2) var rValue = Math.Round(value, 1);
(3) var rValue = Math.Round(value, 12);

这告诉我,对于1'000'000次迭代,所有三次执行安静相等(~70ms)。似乎执行没有区别。

但是为了检查任何意外的惊喜,我compared这些行:

(1) var rValue = Math.Round(value, 1);
(2) var rValue = Math.Round(value * 10.0m, 0);
(3) var rValue = Math.Round(value * 10.0m, 0) / 10.0m;

正如所料,每次乘法都会增加时间(每次约70毫秒)。

正如中预期的那样,舍入和除法没有性能优势,而是舍入到所需的小数位数

重复我的问题:

  

这两种方法之间是否有任何实际的优点/缺点:

(I)  var rValue = Math.Round(value * 10.0m , 0) / 10.0m;  
(II) var rValue = Math.Round(value, 1);

1 个答案:

答案 0 :(得分:2)

简短(呃)回答更新后的问题

您实际上可以在CoreCLR中看到当前FCallRound实施的代码。如果您通过ecalllist.h#L784,您可能会看到它已映射到COMDecimal::DoRound,后者又将大部分逻辑委托给VarDecRound。您可以在链接中看到完整的代码,但关键部分是:

iScale = pdecIn->u.u.scale - cDecimals;
do {
  ulSticky |= ulRem;
  if (iScale > POWER10_MAX)
    ulPwr = ulTenToNine;
  else
    ulPwr = rgulPower10[iScale];

  ulRem = Div96By32(rgulNum, ulPwr);
  iScale -= 9;
} while (iScale > 0);

其中常量定义为

#define POWER10_MAX     9

static const ULONG ulTenToNine    = 1000000000U;    

static ULONG rgulPower10[POWER10_MAX+1] = {1, 10, 100, 1000, 10000, 100000, 1000000,
                                       10000000, 100000000, 1000000000};

那么这段代码的作用是找出当前值应该移位多少小数位,然后分批进行分割,最多为10^9(而10^9是10的最大幂。 32位)。这意味着可能存在两个潜在的性能差异来源:

  1. 你向上舍入到超过9位有效数字,所以如果你先乘数,循环将运行更多次
  2. 如果在乘法后尾数有更多的非零32位字,
  3. Div96By32可能会更慢。
  4. 所以是的,当带乘法的代码运行得更慢时,你可以创建一个人为的例子。例如,如果以

    开头,则可以利用差异#2
    decimal value = 92233720368.547758m
    
    尾数为≈2^63/100

    。即使您没有考虑到计算Math.Round(value, 4)see online)的时间,Math.Round(value*10000, 0)也会比value*10000更快。

    我仍然认为,在任何现实生活中,你都不会注意到任何显着差异。

    原来的长答案

    我认为你错过了引用问题的全部内容。主要问题是Feii Momo希望40.2 3 四舍五入到40.2 5 。这是一个精度,不等于某些整数的十进制数字!使用舍入到指定的小数位数,您将获得40.23(> = 2位数)或40.2(0)(1位数)。乘法和除法的技巧是一个简单的技巧,可以在子位数精度上进行四舍五入(但只有当你的"子数位"是2的负幂时才有效,例如0.5或0.25 / 0.5 / 0.75等等)。此外,我不知道有任何其他简单的方法不使用这种乘法 - 舍入技巧。

    是的,无论如何,当你进行乘法 - 舍入时,你是否做

    并不重要
    var rValue = Math.Round(value * 20.0m, 0) / 20.0m;
    

    var rValue = Math.Round(value * 2.0m, 1) / 2.0m;
    

    因为Round和除法都与第二个参数无关。请注意,在第二个示例中,您不必避免第一个示例中的任何必要步骤!所以它不是真的

      

    为什么圆形和分割而不是舍入到指定的小数位更容易?

    一个人是否优于其他人几乎是纯粹主观的事情。 (我可以想到一些边缘情况,当第二个不会失败时,第一个将会,但只要我们谈论原始问题中提到的任何现实的金额,它们就没有关系。)

    总结一下:

    1. 你能避免乘法和除法,只使用Math.Round吗?最可能没有
    2. 你能乘以一个不能被10整除的数字除以它会有什么不同吗?是的你可以。不,几乎可以肯定没有区别。