【问题标题】:Rounding mismatch between ASP .net C# Decimal to Java DoubleASP .net C# Decimal 到 Java Double 之间的舍入不匹配
【发布时间】:2017-01-13 22:52:14
【问题描述】:

我正在将 .NET 代码翻译成 Java,但遇到了精度不匹配的问题。

.NET 代码:

private decimal roundToPrecision(decimal number, decimal roundPrecision)
{
    if (roundPrecision == 0)
        return number;
    decimal numberDecimalMultiplier = Math.Round(number / roundPrecision, MidpointRounding.AwayFromZero);
    return numberDecimalMultiplier * roundPrecision;
}

在上面的代码中调用roundToPrecision(8.7250, 0.05); 函数会得到8.75,这是预期的。

函数到Java的转换/翻译如下。我找不到确切的 Math.Round 选项。

Java 代码:

public double roundToPrecision(double number, double roundPrecision) {
    if (roundPrecision == 0)
        return number;
    int len = Double.toString(roundPrecision).split("\\.")[1].length();
    double divisor = 0d;
    switch (len) {
        case 1:
            divisor = 10d;
            break;
        case 2:
            divisor = 100d;
            break;
        case 3:
            divisor = 1000d;
            break;
        case 4:
            divisor = 10000d;
            break;
    }
    double numberDecimalMultiplier = Math.round(number / roundPrecision);
    double res = numberDecimalMultiplier * roundPrecision;
    return Math.round(res * divisor) / divisor;
}

在 Java 代码中调用 roundToPrecision(8.7250, 0.05); 会得到 8.7,这是不正确的。

我什至尝试使用C# Double Rounding 此处的参考在Java 中修改代码BigDecimal,如下所示,但没有运气。

public double roundToPrecision(double number, double roundPrecision) {
    if (roundPrecision == 0)
        return number;
    int len = Double.toString(roundPrecision).split("\\.")[1].length();
    double divisor = 0d;
    switch (len) {
        case 1:
            divisor = 10d;
            break;
        case 2:
            divisor = 100d;
            break;
        case 3:
            divisor = 1000d;
            break;
        case 4:
            divisor = 10000d;
            break;
    }
    BigDecimal b = new BigDecimal(number / roundPrecision);
    b = b.setScale(len,BigDecimal.ROUND_UP);
    double numberDecimalMultiplier = Math.round(b.doubleValue());
    double res = numberDecimalMultiplier * roundPrecision;
    return Math.round(res * divisor) / divisor;
}

请指导我如何解决此问题。

这里有几个场景可供尝试。

  • 号码=10.05;精度=.1;预期 = 10.1;
  • 号码=10.12;精度=.01;预期 = 10.12;
  • 号码=8.7250;精度=0.05;预期 = 8.75;
  • 号码=10.999;精度=2;预期 = 10;
  • 号码=6.174999999999999;精度=0.05;预期 = 6.20;

注意:我有超过 6 万个数字,精度可以从小数点后 1 位到小数点后 4 位不等。 .NET 的输出应该与 Java 完全匹配。

【问题讨论】:

    标签: java c# .net rounding precision


    【解决方案1】:

    问题在于双精度与小数如何在内存中存储和表示。有关详细信息,请参阅这些链接:DoublesDecimals

    让我们看看它们各自在您的代码中是如何工作的。使用双精度数,参数为 8.725 和 0.05。 number / roundPrecision 给出 174.499...,因为双精度数不能准确表示 174.5。使用小数number / roundPrecision 给出174.5,小数能够准确地表示这一点。因此,当 174.499... 被四舍五入时,它会向下舍入为 174 而不是 175

    使用BigDecimal 是朝着正确方向迈出的一步。但是,它在您的代码中的使用方式存在问题。当您创建 BigDecimal 值时,问题就出现了。

    BigDecimal b = new BigDecimal(number / roundPrecision);
    

    BigDecimal 是从一个双精度数创建的,因此不精确性已经存在。如果您能够从字符串创建BigDecimal 参数会更好。

    public static BigDecimal roundToPrecision(BigDecimal number, BigDecimal roundPrecision) {
        if (roundPrecision.signum() == 0)
            return number;
        BigDecimal numberDecimalMultiplier = number.divide(roundPrecision, RoundingMode.HALF_DOWN).setScale(0, RoundingMode.HALF_UP);
        return numberDecimalMultiplier.multiply(roundPrecision);
    }
    
    
    BigDecimal n = new BigDecimal("-8.7250");
    BigDecimal p = new BigDecimal("0.05");
    BigDecimal r = roundToPrecision(n, p);
    

    如果函数必须接受并返回双精度数:

    public static double roundToPrecision(double number, double roundPrecision)
    {
        BigDecimal numberBig = new BigDecimal(number).
                setScale(10, BigDecimal.ROUND_HALF_UP);
        BigDecimal roundPrecisionBig = BigDecimal.valueOf(roundPrecision);
        if (roundPrecisionBig.signum() == 0)
            return number;
        BigDecimal numberDecimalMultiplier = numberBig.divide(roundPrecisionBig, RoundingMode.HALF_DOWN).setScale(0, RoundingMode.HALF_UP);
        return numberDecimalMultiplier.multiply(roundPrecisionBig).doubleValue();
    }
    

    请记住,双精度数不能完全表示小数可以表示的相同值。因此,返回双精度的函数不能具有与返回小数的原始 C# 函数完全相同的输出。

    【讨论】:

    • 感谢您的回答。但是,我已经尝试过了。尝试 senarios 1 和 2(我已经更新了问题)。它们在您的实施中没有给出预期的结果。
    • 当数字为 10.0 且精度为 0.1 时,您的解决方案在 BigDecimal numberDecimalMultiplier = number.divide(roundPrecision).setScale(0, RoundingMode.CEILING); 上抛出 java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result
    • 抛出异常时你的论据是什么?
    • 当数字为 10.0 且精度为 0.1 时
    • 此案例失败number = 10.025d; precision = .05d; expected = 10.05d;
    【解决方案2】:

    这里真正的问题是 Math.round 有两个定义。一个返回 long,而另一个返回 int!当您提供双倍时,它会运行很长时间。要解决此问题,只需将输入转换为浮点数,使其运行返回 int。

    double numberDecimalMultiplier = Math.round((float)(number / roundPrecision));
    

    【讨论】:

      猜你喜欢
      • 2014-08-26
      • 2012-12-26
      • 1970-01-01
      • 1970-01-01
      • 2021-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多