【问题标题】:ResultSet::getBigDecimal throws scale exception for joda-moneyResultSet::getBigDecimal 抛出 joda-money 的比例异常
【发布时间】:2014-06-29 04:27:24
【问题描述】:

尝试从 MySQL 数据库读取的 BigDecimal 中创建 Joda-Money Money 对象会引发错误。

这段代码:

PreparedStatement p_stmt = ...;
ResultSet results = ...;
Money amount = Money.of(CurrencyUnit.USD, results.getBigDecimal("amount"));

抛出这个:

java.lang.ArithmeticException: Scale of amount 1.0000 is greater than the scale of the currency USD
    at org.joda.money.Money.of(Money.java:74)
    at core.DB.getMoney(DB.java:4821)

我实际上已经通过添加四舍五入解决了错误

Money amount = Money.of(CurrencyUnit.USD, results.getBigDecimal("amount"), RoundingMode.HALF_UP);

但是,我对这种解决方案持怀疑态度。是最好的吗?

我正在使用DECIMAL(19,4) 按照this SO answer 将货币值存储在数据库中。老实说,这让我有点困惑,为什么那里的回答者要求在 DB 上精确到小数点后 4 位,但我倾向于相信高价值的答案,我认为他们知道他们在说什么,我会后悔没有关注他们的建议。然而,Joda-Money 不喜欢美国货币的小数点后 4 位精度。也许DECIMAL(19,4) 是他们需要小数点后 4 位精度的国际标准?不确定..

为了让问题简洁:

  1. RoundingMode.HALF_UP 是解决此错误的理想解决方案吗?
  2. 是否应该将 MySQL DB 上的精度从 DECIMAL(19,4) 更改为 DECIMAL(19,2)
  3. 有没有办法改变 Joda-Money 的精度?如果是:我应该这样做吗?

【问题讨论】:

    标签: java mysql jdbc joda-money


    【解决方案1】:

    Joda Money 和 BigDecimal 为每种货币类型定义了一个“比例”。

    规模是没有。货币使用的小数位数。

    所以 USD 的小数位数为 2(小数点后两位小数)。

    如果您尝试从具有两个以上小数位的来源为美元货币实例化 Money 实例,那么您将收到缩放错误。 例如

    20.99 is fine
    20.991 throws an error.
    

    其他货币允许不同的小数位数。

    根据文档RoundMode.UNNECESSARY 如果实际需要舍入,实际上会抛出异常。

    例如:假设 USD 的小数位数为 2,则预计小数点后 2 位。

    使用:

    Money money = Money.of(CurrencyUnit.USD, value, RoundingMode.UNNECESSARY);
    

    值:

    1.20 - works fine
    1.200 - works fine as whilst extra digit rounding isn't necessary.
    1.201 - throws an exception as rounding is necessary
    

    四舍五入和钱总是一个问题。 我的方法是将正确的比例存储在数据库中

    例如:如果 USD 然后 DECIMAL(19,2)

    使用RoundingMode.HALF_UP

    在进行乘法和除法时要小心。 在除法之前进行乘法将导致更少的舍入问题。 如果你在做算术的时候四舍五入,你应该没问题。

    【讨论】:

      【解决方案2】:

      是的,这不是一个好的解决方案。您代表的是金钱,因此您不能向上/向下舍入并损失几美分:)。

      问题在于 CurrencyUnit.USD 的位数为 2,例如:1.45,而来自数据库的数字可能类似于 1.45343,从而导致规模差异。尝试用 BigMoney 而不是 Money 来代表钱。

      BigMoney amount = BigMoney .of(CurrencyUnit.USD, results.getBigDecimal("amount"));
      

      【讨论】:

      • 好吧,你不会损失美分的。你会关闭几分之一美分,因为它会将BigDecimal HALF_UP 舍入到Money 对象的比例,所以你只会损失几分之一美分。
      【解决方案3】:

      有同样的问题并通过添加舍入模式来解决它不必要:

          @Test(dataProvider = "moneyValueProvider")
          public void testMoneyFromFloat(String currency, Float value) {
              CurrencyUnit cu = CurrencyUnit.of(currency);
              //Double dVal = Double.valueOf(value.toString());
              BigDecimal bigDecimal = new BigDecimal(value.toString());
              Money money = Money.of(cu, bigDecimal, RoundingMode.UNNECESSARY);
              Assert.assertNotNull(money);
              Assert.assertEquals(bigDecimal.doubleValue(), money.getAmount().doubleValue());
          }
      
          @DataProvider
          public static Object[][] moneyValueProvider() {
              return new Object[][] {
                          {"GBP", 22.5f},
                          {"GBP", 57.8f},
                          {"JPY", 15000.0f}
      
          };
      

      【讨论】:

      • 我从未注意到RoundingMode.UNNECESSARY。但是,仍然存在一个问题:DECIMAL(19,4) 是数据库上的格式会成为问题吗?也许现在有一个新问题与DECIMAL(19,4) 的必要性有关。
      • 我肯定不愿意假设舍入实际上是必要的。特别是如果 HALF_UP 解决方案有效。
      • 在我的情况下 {"JPY", 15000.0f} 钱正在抛出 ArithmeticException。由roundingMode修复
      • 曾经有这个:java.lang.ArithmeticException:金额15000.0的比例大于货币日元的比例。我没有放弃 HALF_UP 解决方案,只需要阅读更多信息
      猜你喜欢
      • 2014-01-17
      • 1970-01-01
      • 2010-12-21
      • 2012-09-12
      • 1970-01-01
      • 1970-01-01
      • 2013-01-16
      • 2013-05-24
      • 2022-01-23
      相关资源
      最近更新 更多