解析货币字符串安全性的最佳方法?
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
并且认为看起来不错。
在服务器上我接受字符串作为参数。这个字符串代表金钱。
可能是(例如)
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
并且认为看起来不错。