解析货币字符串安全性的最佳方法?

best way to parse currency string safety?

在服务器上我接受字符串作为参数。这个字符串代表金钱。

可能是(例如)

0  
12  
12,  
12,34  
,12

现在我这样削:

long centAmount = (long) (NumberFormat.getInstance(Locale.FRANCE).parse(outSum).doubleValue() * 100)

我担心我会在某个地方丢失一分钱。

请指教如何重写我的代码。

当您使用 (long) 时,这会截断数字。这意味着 0.999999999999999 将被截断为 0 ,这会让你在这里和那里损失一分钱。更好的方法是使用舍入。

double input = NumberFormat.getInstance(Locale.FRANCE).parse(outSum).doubleValue();
long centAmount = Math.round(input * 100);

这将适用于高达 70 万亿美元的价值。如果您需要准确存储更大的金额,您将需要使用 BigDecimal 和 BigInteger。

使用 BigDecimal 的优点之一是它会提醒您何时应该使用舍入(并非总是如此,但如果您不确定,它不太容易出错)缺点是代码要麻烦得多。

如果你使用 BigDecimal,你可以这样做

DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.FRANCE);
df.setParseBigDecimal(true);
BigDecimal bd = (BigDecimal) df.parseObject(outSum);
BigDecimal bd100 = bd.multiply(BigDecimal.valueOf(100))
                     .setScale(0, RoundingMode.HALF_UP);
long centAmount = bd100.longValue();

添加一个简单示例,说明为什么 BigDecimal 不能解决所有问题,但确实隐藏了它们。

    BigDecimal add = new BigDecimal("1.00")
              .divide(BigDecimal.valueOf(3), 2, BigDecimal.ROUND_HALF_UP)
              .multiply(BigDecimal.valueOf(3)).add(new BigDecimal("0.01"));
    System.out.println(add);
    double add2 = 1.0 / 3 * 3 +  0.01;
    System.out.println(add2);

这会打印

1.00
1.01

哪一个更容易出错,你可以用任何一种方式争论(这很可能是基于你的团队,他们发现哪个更不容易出错),但哪个更难理解它试图做什么做什么?

我对 BigDecimal 的问题是黑白评估 double 是有缺陷的,而 BigDecimal 会解决你所有的问题,而实际上,你会遇到所有相同的问题,但使用 BigDecimal 它们会更难发现,所以你可能永远不知道你有问题。

对于 double,如果您看到 1.0999999999999998,您会认为这看起来不对,但是对于 BigDecimal,您可能会看到 1.00 并且认为看起来不错。