由于 BigDecimals 中的二进制表示,数字不准确。我该如何解决?
Inaccurate Digits due to binary representation in BigDecimals. How do I get around it?
我想编写一个将 String 转换为 BigDecimal 的解析器。
要求它是 100% 准确的。 (嗯,我目前编程是为了好玩。所以我宁愿要求它......;-P)
所以我想出了这个程序:
public static BigDecimal parse(String term) {
char[] termArray = term.toCharArray();
BigDecimal val = new BigDecimal(0D);
int decimal = 0;
for(char c:termArray) {
if(Character.isDigit(c)) {
if(decimal == 0) {
val = val.multiply(new BigDecimal(10D));
val = val.add(new BigDecimal(Character.getNumericValue(c)));
} else {
val = val.add(new BigDecimal(Character.getNumericValue(c) * Math.pow(10, -1D * decimal)));
decimal++;
}
}
if(c == '.') {
if(decimal != 0) {
throw new IllegalArgumentException("There mustn't be multiple points in this number: " + term);
} else {
decimal++;
}
}
}
return val;
}
所以我尝试了:
parse("12.45").toString();
我预计会是 12.45
。相反,它是 12.45000000000000002498001805406602215953171253204345703125
。我知道这可能是由于二进制表示的限制。但我该如何解决这个问题?
注意:我知道您可以使用 new BigDecimal("12.45");
。但这不是我的意思 - 我想自己写,不管这可能有多愚蠢。
是的,这是由于二进制表示的限制。 10 的任何负次幂都不能精确表示为 double
.
要解决此问题,请将所有 double
算法替换为所有 BigDecimal
算法。
val = val.add(
new BigDecimal(Character.getNumericValue(c)).divide(BigDecimal.TEN.pow(decimal)));
有了这个我得到 12.45
。
这可以稍微改进一下。只分一次。只需忽略循环内的小数点。只计算小数点。所以 "12.45"
变成 1245
和 decimal == 2
。现在最后,您只需将其除以 BigDecimal.TEN.pow(2)
(或 100)即可得到 12.45
.
public static BigDecimal parse(String term)
{
char[] termArray = term.toCharArray();
// numDecimals: -1: no decimal point at all, so no need to divide
// 0: decimal point found, but no digits counted yet
// > 0: count of digits after decimal point
int numDecimals = -1;
BigDecimal val = new BigDecimal.ZERO;
for(char c: termArray)
{
if (Character.isDigit(c))
{
val = val.multiply(BigDecimal.TEN).add(BigDecimal.valueOf(Character.getNumericValue(c)));
if (numDecimals != -1)
numDecimals++;
}
else if (c == '.')
{
if (numDecimals != -1)
throw new IllegalArgumentException("There mustn't be multiple points in this number: " + term);
else
numDecimals = 0;
}
}
if (numDecimals > 0)
return val.divide(BigDecimal.TEN.pow(numDecimals));
else
return val;
}
请注意,此功能不适用于负值,也不识别科学记数法。为此,使用原始字符串、索引和 charAt(index)
可能比当前循环更理想。但这不是问题。
我想编写一个将 String 转换为 BigDecimal 的解析器。 要求它是 100% 准确的。 (嗯,我目前编程是为了好玩。所以我宁愿要求它......;-P)
所以我想出了这个程序:
public static BigDecimal parse(String term) {
char[] termArray = term.toCharArray();
BigDecimal val = new BigDecimal(0D);
int decimal = 0;
for(char c:termArray) {
if(Character.isDigit(c)) {
if(decimal == 0) {
val = val.multiply(new BigDecimal(10D));
val = val.add(new BigDecimal(Character.getNumericValue(c)));
} else {
val = val.add(new BigDecimal(Character.getNumericValue(c) * Math.pow(10, -1D * decimal)));
decimal++;
}
}
if(c == '.') {
if(decimal != 0) {
throw new IllegalArgumentException("There mustn't be multiple points in this number: " + term);
} else {
decimal++;
}
}
}
return val;
}
所以我尝试了:
parse("12.45").toString();
我预计会是 12.45
。相反,它是 12.45000000000000002498001805406602215953171253204345703125
。我知道这可能是由于二进制表示的限制。但我该如何解决这个问题?
注意:我知道您可以使用 new BigDecimal("12.45");
。但这不是我的意思 - 我想自己写,不管这可能有多愚蠢。
是的,这是由于二进制表示的限制。 10 的任何负次幂都不能精确表示为 double
.
要解决此问题,请将所有 double
算法替换为所有 BigDecimal
算法。
val = val.add(
new BigDecimal(Character.getNumericValue(c)).divide(BigDecimal.TEN.pow(decimal)));
有了这个我得到 12.45
。
这可以稍微改进一下。只分一次。只需忽略循环内的小数点。只计算小数点。所以 "12.45"
变成 1245
和 decimal == 2
。现在最后,您只需将其除以 BigDecimal.TEN.pow(2)
(或 100)即可得到 12.45
.
public static BigDecimal parse(String term)
{
char[] termArray = term.toCharArray();
// numDecimals: -1: no decimal point at all, so no need to divide
// 0: decimal point found, but no digits counted yet
// > 0: count of digits after decimal point
int numDecimals = -1;
BigDecimal val = new BigDecimal.ZERO;
for(char c: termArray)
{
if (Character.isDigit(c))
{
val = val.multiply(BigDecimal.TEN).add(BigDecimal.valueOf(Character.getNumericValue(c)));
if (numDecimals != -1)
numDecimals++;
}
else if (c == '.')
{
if (numDecimals != -1)
throw new IllegalArgumentException("There mustn't be multiple points in this number: " + term);
else
numDecimals = 0;
}
}
if (numDecimals > 0)
return val.divide(BigDecimal.TEN.pow(numDecimals));
else
return val;
}
请注意,此功能不适用于负值,也不识别科学记数法。为此,使用原始字符串、索引和 charAt(index)
可能比当前循环更理想。但这不是问题。