【问题标题】:round BigDecimal to nearest 5 cents将 BigDecimal 舍入到最接近的 5 美分
【发布时间】:2011-01-07 13:49:04
【问题描述】:

我试图弄清楚如何将货币金额向上舍入到最接近的 5 美分。下面显示了我的预期结果

1.03     => 1.05
1.051    => 1.10
1.05     => 1.05
1.900001 => 1.10

我需要结果的精度为 2(如上所示)。

更新

按照下面的建议,我能做的最好的就是这个

    BigDecimal amount = new BigDecimal(990.49)

    // To round to the nearest .05, multiply by 20, round to the nearest integer, then divide by 20
   def result =  new BigDecimal(Math.ceil(amount.doubleValue() * 20) / 20)
   result.setScale(2, RoundingMode.HALF_UP)

我不相信这是 100% 洁净的 - 我担心在转换为双打时可能会丢失精度。不过,这是迄今为止我想出的最好的方法,而且似乎可以工作。

【问题讨论】:

  • 根据定义,由于四舍五入,您无论如何都会失去精度。我认为您不必担心精度损失。
  • 顺便说一句,如果您担心精度,那么您应该使用 String 构造函数而不是 double 构造函数来创建 BigDecimals。
  • 查看@marcolopes 的答案,了解如何在不使用doubleValue() 的情况下使用BigDecimal
  • 1:051 舍入到 1.05,而不是 1.10。

标签: java rounding bigdecimal


【解决方案1】:

使用没有任何双打的BigDecimal(改进了marcolopes的答案):

public static BigDecimal round(BigDecimal value, BigDecimal increment,
                               RoundingMode roundingMode) {
    if (increment.signum() == 0) {
        // 0 increment does not make much sense, but prevent division by 0
        return value;
    } else {
        BigDecimal divided = value.divide(increment, 0, roundingMode);
        BigDecimal result = divided.multiply(increment);
        return result;
    }
}

舍入模式是例如RoundingMode.HALF_UP。对于您的示例,您实际上想要RoundingMode.UPbd 是一个只返回new BigDecimal(input) 的助手):

assertEquals(bd("1.05"), round(bd("1.03"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.10"), round(bd("1.051"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.05"), round(bd("1.05"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.95"), round(bd("1.900001"), bd("0.05"), RoundingMode.UP));

另请注意,您的上一个示例中有一个错误(将 1.900001 舍入到 1.10)。

【讨论】:

  • 显然是最佳答案。请投票,使其排名高于那些使用浮点的 hacky 解决方案。
  • @DavidEasley 我可以理解开发人员对使用语言原语和类似 double 的操作感到不自在,但这并不会让使用它们变得很笨拙。
  • 它们并不是真正的 hacky,但它们只适用于足够小的 BigDecimals 以适应 double。因此,它们不是可靠的解决方案。这个适用于任何 BigDecimal。
  • 使用 BigDecimal 更能表达您正在操纵货币金额这一事实。
  • 这个是完美的答案,解决了我的问题。
【解决方案2】:

我会尝试乘以 20,四舍五入到最接近的整数,然后除以 20。这是一个 hack,但应该会给你正确的答案。

【讨论】:

  • 根本不是 hack,一种完美的技术。
【解决方案3】:

几年前我用 Java 写过这个:https://github.com/marcolopes/dma/blob/master/org.dma.java/src/org/dma/java/math/BusinessRules.java

/**
 * Rounds the number to the nearest<br>
 * Numbers can be with or without decimals<br>
 */
public static BigDecimal round(BigDecimal value, BigDecimal rounding, RoundingMode roundingMode){

    return rounding.signum()==0 ? value :
        (value.divide(rounding,0,roundingMode)).multiply(rounding);

}


/**
 * Rounds the number to the nearest<br>
 * Numbers can be with or without decimals<br>
 * Example: 5, 10 = 10
 *<p>
 * HALF_UP<br>
 * Rounding mode to round towards "nearest neighbor" unless
 * both neighbors are equidistant, in which case round up.
 * Behaves as for RoundingMode.UP if the discarded fraction is >= 0.5;
 * otherwise, behaves as for RoundingMode.DOWN.
 * Note that this is the rounding mode commonly taught at school.
 */
public static BigDecimal roundUp(BigDecimal value, BigDecimal rounding){

    return round(value, rounding, RoundingMode.HALF_UP);

}


/**
 * Rounds the number to the nearest<br>
 * Numbers can be with or without decimals<br>
 * Example: 5, 10 = 0
 *<p>
 * HALF_DOWN<br>
 * Rounding mode to round towards "nearest neighbor" unless
 * both neighbors are equidistant, in which case round down.
 * Behaves as for RoundingMode.UP if the discarded fraction is > 0.5;
 * otherwise, behaves as for RoundingMode.DOWN.
 */
public static BigDecimal roundDown(BigDecimal value, BigDecimal rounding){

    return round(value, rounding, RoundingMode.HALF_DOWN);

}

【讨论】:

  • 我认为使用rounding.signum() == 0 会比rounding.doubleValue() == 0 更好地测试0。除此之外,这个解决方案很好。
  • 谢谢!那是正确的......这段代码很旧,我是java新手! :D signum() 是要走的路!
【解决方案4】:

这是我编写的 C# 中的几个非常简单的方法,用于始终向上或向下舍入到传递的任何值。

public static Double RoundUpToNearest(Double passednumber, Double roundto)
    {

        // 105.5 up to nearest 1 = 106
        // 105.5 up to nearest 10 = 110
        // 105.5 up to nearest 7 = 112
        // 105.5 up to nearest 100 = 200
        // 105.5 up to nearest 0.2 = 105.6
        // 105.5 up to nearest 0.3 = 105.6

        //if no rounto then just pass original number back
        if (roundto == 0)
        {
            return passednumber;
        }
        else
        {
            return Math.Ceiling(passednumber / roundto) * roundto;
        }
    }
    public static Double RoundDownToNearest(Double passednumber, Double roundto)
    {

        // 105.5 down to nearest 1 = 105
        // 105.5 down to nearest 10 = 100
        // 105.5 down to nearest 7 = 105
        // 105.5 down to nearest 100 = 100
        // 105.5 down to nearest 0.2 = 105.4
        // 105.5 down to nearest 0.3 = 105.3

        //if no rounto then just pass original number back
        if (roundto == 0)
        {
            return passednumber;
        }
        else
        {
            return Math.Floor(passednumber / roundto) * roundto;
        }
    }

【讨论】:

    【解决方案5】:

    在 Scala 中,我做了以下操作(下面是 Java)

    import scala.math.BigDecimal.RoundingMode
    
    def toFive(
       v: BigDecimal,
       digits: Int,
       roundType: RoundingMode.Value= RoundingMode.HALF_UP
    ):BigDecimal = BigDecimal((2*v).setScale(digits-1, roundType).toString)/2
    

    在 Java 中

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    
    public static BigDecimal toFive(BigDecimal v){
        return new BigDecimal("2").multiply(v).setScale(1, RoundingMode.HALF_UP).divide(new BigDecimal("2"));
    }
    

    【讨论】:

      【解决方案6】:

      根据您的编辑,另一种可能的解决方案是:

      BigDecimal twenty = new BigDecimal(20);
      BigDecimal amount = new BigDecimal(990.49)
      
      // To round to the nearest .05, multiply by 20, round to the nearest integer, then divide by 20
      BigDecimal result =  new BigDecimal(amount.multiply(twenty)
                                                .add(new BigDecimal("0.5"))
                                                .toBigInteger()).divide(twenty);
      

      这有一个优势,可以保证不会丢失精度,尽管它当然可能会更慢......

      还有scala测试日志:

      scala> var twenty = new java.math.BigDecimal(20) 
      twenty: java.math.BigDecimal = 20
      
      scala> var amount = new java.math.BigDecimal("990.49");
      amount: java.math.BigDecimal = 990.49
      
      scala> new BigDecimal(amount.multiply(twenty).add(new BigDecimal("0.5")).toBigInteger()).divide(twenty)
      res31: java.math.BigDecimal = 990.5
      

      【讨论】:

        【解决方案7】:

        为了通过这个测试:

        assertEquals(bd("1.00"), round(bd("1.00")));
        assertEquals(bd("1.00"), round(bd("1.01")));
        assertEquals(bd("1.00"), round(bd("1.02")));
        assertEquals(bd("1.00"), round(bd("1.024")));
        assertEquals(bd("1.05"), round(bd("1.025")));
        assertEquals(bd("1.05"), round(bd("1.026")));
        assertEquals(bd("1.05"), round(bd("1.049")));
        
        assertEquals(bd("-1.00"), round(bd("-1.00")));
        assertEquals(bd("-1.00"), round(bd("-1.01")));
        assertEquals(bd("-1.00"), round(bd("-1.02")));
        assertEquals(bd("-1.00"), round(bd("-1.024")));
        assertEquals(bd("-1.00"), round(bd("-1.0245")));
        assertEquals(bd("-1.05"), round(bd("-1.025")));
        assertEquals(bd("-1.05"), round(bd("-1.026")));
        assertEquals(bd("-1.05"), round(bd("-1.049")));
        

        ROUND_UP 更改为ROUND_HALF_UP

        private static final BigDecimal INCREMENT_INVERTED = new BigDecimal("20");
        public BigDecimal round(BigDecimal toRound) {
            BigDecimal divided = toRound.multiply(INCREMENT_INVERTED)
                                        .setScale(0, BigDecimal.ROUND_HALF_UP);
            BigDecimal result = divided.divide(INCREMENT_INVERTED)
                                       .setScale(2, BigDecimal.ROUND_HALF_UP);
            return result;
        }
        

        【讨论】:

          【解决方案8】:
            public static BigDecimal roundTo5Cents(BigDecimal amount)
            {
              amount = amount.multiply(new BigDecimal("2"));
              amount = amount.setScale(1, RoundingMode.HALF_UP);
              // preferred scale after rounding to 5 cents: 2 decimal places
              amount = amount.divide(new BigDecimal("2"), 2, RoundingMode.HALF_UP);
              return amount;
            }
          

          请注意,这与John's的答案基本相同。

          【讨论】:

            【解决方案9】:
            public static void roundUp()
            {
                try
                {
                    System.out.println("Enter the currency : $");
                    Scanner keyboard = new Scanner(System.in);
                    String myint = keyboard.next();
                    if (!isEmptyOrBlank(myint).booleanValue())
                    {
                        BigDecimal d = new BigDecimal(myint);
                        System.out.println("Enter the round up factor: $");
                        String roundUpFactor = keyboard.next();
                        if (!isEmptyOrBlank(roundUpFactor).booleanValue())
                        {
                            BigDecimal scale = new BigDecimal(roundUpFactor);
                            BigDecimal y = d.divide(scale, MathContext.DECIMAL128);
                            BigDecimal q = y.setScale(0, 0);
                            BigDecimal z = q.multiply(scale);
                            System.out.println("Final price after rounding up to " + roundUpFactor + " is : $" + z);
                            System.out.println("Want to try with other price Y/N :");
            
                            String exit = keyboard.next();
                            if ((!isEmptyOrBlank(exit).booleanValue()) && ("y".equalsIgnoreCase(exit))) {
                                roundUp();
                            } else {
                                System.out.println("See you take care");
                            }
                        }
                    }
                    else
                    {
                        System.out.println("Please be serious u r dealing with critical Tx Pricing");
                    }
                }
                catch (Exception e)
                {
                    System.out.println("Please be serious u r dealing with critical Tx Pricing enter correct rounding off value");
                }
            }
            

            【讨论】:

            • 这个伪指令从行命令中获取输入,您可以在其中输入要四舍五入的值以及向上取整的值,即输入货币:$ 1.051 输入向上取整因子:$ 0.05 四舍五入后的最终价格0.05 是:$1.10
            【解决方案10】:

            Tom 的想法是正确的,但是您需要使用 BigDecimal 方法,因为您表面上使用的是 BigDecimal,因为您的值不适用于原始数据类型。比如:

            BigDecimal num = new BigDecimal(0.23);
            BigDecimal twenty = new BigDecimal(20);
            //Might want to use RoundingMode.UP instead,
            //depending on desired behavior for negative values of num.
            BigDecimal numTimesTwenty = num.multiply(twenty, new MathContext(0, RoundingMode.CEILING)); 
            BigDecimal numRoundedUpToNearestFiveCents
              = numTimesTwenty.divide(twenty, new MathContext(2, RoundingMode.UNNECESSARY));
            

            【讨论】:

            • 这会抛出异常抛出异常:需要舍入 java.lang.ArithmeticException: 需要舍入
            【解决方案11】:

            你可以使用普通的双精度来做到这一点。

            double amount = 990.49;
            double rounded = ((double) (long) (amount * 20 + 0.5)) / 20;
            

            编辑:对于负数,您需要减去 0.5

            【讨论】:

            • docs.oracle.com/javase/tutorial/java/nutsandbolts/… "如上所述,这种数据类型 [double] 绝不应该用于精确值,例如货币。"
            • @daemonl 一个很好的引用,除了大多数交易系统使用双精度或长整数,尤其是那些使用 C++ 的系统。
            • @D.Kovács 我研究过的系统都是 BigDecimal。我第一年的咨询基本上是通过将 BigDecimal 替换为各种客户的 double 来支付的,所以我欠它很多;)
            • @PeterLawrey 感谢您的回复,但您仍然给我们提供了相当模糊的、手忙脚乱的答案。网上有很多关于将浮点数用于货币等事物的危险的参考资料,即精度至关重要的地方。请您仅指出一个参考资料(最好是有信誉的参考资料)来支持您的断言,即浮点数(或至少双倍数)适用于财务计算。
            • @PeterLawrey “我是” - 我们知道为什么是过去时 ;) 开玩笑:不,那是一个非常复杂的黑客攻击。如果我们谈论的是同一个事件。但就我的想法而言,不是一家银行,而是几家银行。所以也许是另一个:)
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-13
            • 1970-01-01
            • 2012-04-22
            • 2023-03-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多