c# - System.Math.Round - 舍入到零位并除以舍入到一位或多位数字

标签 c# math rounding

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


引用问题摘要:

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

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

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

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

Are there any practical advantages/disadvantages between these two approaches:

(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为:

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 次迭代,所有三个都执行安静相等(约 70 毫秒)。而且执行起来似乎没有什么区别。

但只是为了检查是否有任何意外惊喜,我 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 毫秒)。

正如 中所预期的那样与四舍五入到所需的小数位数相比,四舍五入和除法没有任何性能优势。


所以重复我的问题:

Are there any practical advantages/disadvantages between these two approaches:

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

最佳答案

对更新问题的简短回答

您实际上可以在CoreCLR中看到当前FCallRound实现的代码。 。如果您经过ecalllist.h#L784 ,您可能会看到它映射到 COMDecimal::DoRound这又将大部分逻辑委托(delegate)给 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 > 是适合 32 位的 10 的最大幂)。这意味着性能差异可能有两个潜在来源:

  1. 您四舍五入到超过 9 位有效数字,因此如果您先相乘,循环将会运行更多次
  2. Div96By32如果相乘后尾数有更多非零 32 位字,则工作速度可能会变慢。

所以,是的,当带有乘法的代码运行速度较慢时,您可以创建一个人工示例。例如,如果您以

开头,则可以利用差异 #2
decimal value = 92233720368.547758m

尾数为 ≈ 2^63/100。那么即使您不考虑计算时间, Math.Round(value, 4) 也会比 Math.Round(value*10000, 0) 更快值*10000 (see online)。

不过,我认为在任何现实生活中的使用中,您都不会注意到任何显着差异。


原始长答案

我认为您错过了所引用问题的全部要点。主要问题是飞沫沫想要将40.23四舍五入为40.25。这个精度不等于小数点后的某个整数!使用四舍五入到指定的小数位,您将得到 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和除法都花费相同的时间,与它们的第二个参数无关。请注意,在第二个示例中,您没有避免第一个示例的任何基本步骤!所以事实并非如此

Why should it be easier to round and divide instead of round to a specified fractional digit?

一个人是否比另一个人更好几乎纯粹是主观的事情。 (我可以想到一些边缘情况,第二个不会失败,而第一个会失败,但只要我们谈论原始问题中提到的任何实际金额,它们就无关紧要。)

总结一下:

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

关于c# - System.Math.Round - 舍入到零位并除以舍入到一位或多位数字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48216487/

相关文章:

java - ROUND_HALF_EVEN 为什么以及如何在重复应用于一系列计算时最小化累积误差?

python - 根据 floor/ceil 条件,将 pandas 列值舍入或截断为 2 位小数

python - 控制数字是否在 python 中向上或向下舍入

c# - 如果未呈现按钮,客户端能否以编程方式获取按钮单击事件?

c++ - 将数字提高到幂的 C++ 函数是什么?

c# - 向网络服务添加参数是否有害?

C#有序组合算法

当在循环内使用计数器时 C++ 程序崩溃

c# - 在 C# 中创建透明覆盖层

c# - 从零开始MVC