【问题标题】:Rounding double values in C#在 C# 中舍入双精度值
【发布时间】:2011-01-08 22:40:08
【问题描述】:

我想要 C# 中双精度值的舍入方法。它需要能够将双精度值舍入为任何舍入精度值。我手头的代码如下:

public static double RoundI(double number, double roundingInterval) {

    if (roundingInterval == 0.0)
    {
        return;
    }

    double intv = Math.Abs(roundingInterval);
    double sign = Math.Sign(number);
    double val = Math.Abs(number);

    double valIntvRatio = val / intv;
    double k = Math.Floor(valIntvRatio);
    double m = valIntvRatio - k;

    bool mGreaterThanMidPoint = ((m - 0.5) >= 1e-14) ? true : false;
    bool mInMidpoint = (Math.Abs(m - 0.5) < 1e-14) ? true : false;
    return (mGreaterThanMidPoint || mInMidpoint) ? sign * ((k + 1) * intv) : sign * (k * intv);
}

所以 RoundI(100, 3) 应该给出 99,而 RoundI(1.2345, 0.001) 应该给出 1.235。

问题是,RoundI(1.275, 0.01) 返回 1.27,而不是 1.28。这是因为当执行 double valIntvRatio = val/intv,即 double valIntvRatio = 1.275 / 0.01 时,它给出了 0.12749999999999。我知道这是任何编程语言中双重表示的问题。我的问题是,是否有标准代码来做这样的事情,而无需担心双精度?在这里我将容差设置为 1e-14,但这对于这个问题来说太严格了,我不知道要设置的正确容差是多少。感谢您的帮助。

【问题讨论】:

  • 或许您应该考虑使用 Decimal 数据类型。
  • 为什么round(100,3) 会给出99?如果您四舍五入到与 3(0 位)相同的小数位置,您将得到 100,而不是 99。
  • paxdiablo:抱歉,RoundI 的目的不是将第一个参数四舍五入到与第二个参数相同的小数位置。第二个参数是舍入间隔,舍入将第一个参数舍入到第二个参数的模式为0的closet值。
  • 啊,这更有意义(而且我的回答不太有用)。对此深表歉意。

标签: c# double rounding


【解决方案1】:

Jimmy 的回答中提供的使用小数转换的示例没有回答这个问题,因为它们没有显示如何根据要求将双精度值四舍五入到任何舍入精度值。我相信使用十进制转换的正确答案如下:

    public static double RoundI(double number, double roundingInterval)
    {
        return (double)((decimal)roundingInterval * Math.Round((decimal)number / (decimal)roundingInterval, MidpointRounding.AwayFromZero));
    }

因为它使用十进制转换,所以此解决方案受 Jeppe Stig Nielsen 在他对 Jimmy 的回答的评论中提到的转换错误的影响。

另外,请注意我指定了 MidpointRounding.AwayFromZero,因为这与请求者的规范一致,即 RoundI(1.2345, 0.001) 应该给出 1.235。

【讨论】:

    【解决方案2】:

    decimal 的使用示例,正如 Kibbee 指出的那样

    double d = 1.275;
    Math.Round(d, 2);          // 1.27
    Math.Round((decimal)d, 2); // 1.28 
    

    【讨论】:

    • 虽然这个很好的解决方案通常有效,但当d 的有效数字接近double 的精度限制时,转换为decimal 实际上会消除太多的精度。以d = 12345678901234.256; 为例(您需要将其与d.ToString("R") 一起输出以显示“隐藏”精度)。如果你在这个例子中只使用Math.Round(d, 2)(记得用"R"写出结果)你会得到比Math.Round((decimal)d, 2)更好的结果。转换为decimal 在这里消除了太多的精度。在这种情况下,从一开始就使用decimal,不要强制转换。
    • +1 非常好的解决方案,但不幸的是它没有将 0.005 舍入到 0.01 。结果是 0
    • @Zefnus 因为它使用银行家的四舍五入。如果您希望它舍入到 0.01,请使用 Math.Round((decimal)d, 2, MidpointRounding.AwayFromZero)
    • 为什么 0.125 结果是 0.12?
    【解决方案3】:

    如果您确实需要使用double,只需在下面替换它即可,但会出现二进制浮点运算的常见精度问题。

    肯定有比我下面的字符串杂耍更好的方法来实现“四舍五入”(几乎是一种银行家的四舍五入)。

    public static decimal RoundI(decimal number, decimal roundingInterval)
    {
       if (roundingInterval == 0) { return 0;}
    
       decimal intv = Math.Abs(roundingInterval);
       decimal modulo = number % intv;
       if ((intv - modulo) == modulo) {
           var temp = (number - modulo).ToString("#.##################");
           if (temp.Length != 0 && temp[temp.Length - 1] % 2 == 0) modulo *= -1;
       }
        else if ((intv - modulo) < modulo)
            modulo = (intv - modulo);
        else
            modulo *= -1;
    
        return number + modulo;
    }
    

    【讨论】:

    • 可能发现了一个错误。如果您传入: number = 0.5 舍入间隔 1.0 var temp 数组位会出现一些问题。
    • 这是真的,但是比数字更大的间隔没有任何意义,不是吗?我在方法中添加了一个额外的检查。
    • 在我正在查看的情况下可能会发生。尝试用 Y 的 roundingInterval 对 X 的货币进行舍入,这在编译时都是未知的。因此可以得到一个较小的值以较大的间隔四舍五入。谢谢你回来找我。
    • 确实缺少 temp.Length != 0 现在 RoundI(50m,100m) 返回 100 而不是索引外异常。
    【解决方案4】:
    double d = 1.2345;
    
    Math.Round(d, 2);
    

    上面的代码应该可以解决问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-31
      • 1970-01-01
      • 1970-01-01
      • 2011-01-22
      • 1970-01-01
      • 2014-05-16
      相关资源
      最近更新 更多