由于BigDecimals中的二进制表示而导致的数字不准确。我该如何解决这个问题?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了由于BigDecimals中的二进制表示而导致的数字不准确。我该如何解决这个问题?相关的知识,希望对你有一定的参考价值。

我想编写一个将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 BigInteger("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)可能比当前循环更理想。但那不是问题。

以上是关于由于BigDecimals中的二进制表示而导致的数字不准确。我该如何解决这个问题?的主要内容,如果未能解决你的问题,请参考以下文章

linux命令学习笔记

gym101102J Divisible Numbers(预处理)

Java中的数是用补码表示的检验

5、 啥是位?啥是字节?一个字节有几位?哪个是表示计算机中的基本编码单位?

判断一个整数是否是2的N次方

python中的减法有问题