Java 浮点运算不能产生精确的结果

Java 浮点运算不能产生精确的结果,java,floating-point,Java,Floating Point,我需要用Java做一些浮点运算,如下代码所示: public class TestMain { private static Map<Integer, Double> ccc = new HashMap<Integer, Double>() { { put(1, 0.01); put(2, 0.02); put(3, 0.05); put(4, 0.1); put(6, 0.2); put(10, 0.5); put(20, 1.0);

我需要用Java做一些浮点运算,如下代码所示:

public class TestMain {
    private static Map<Integer, Double> ccc = new HashMap<Integer, Double>() {
      { put(1, 0.01); put(2, 0.02); put(3, 0.05); put(4, 0.1); put(6, 0.2);
        put(10, 0.5); put(20, 1.0); put(30, 2.0); put(50, 5.0); put(100, 10.0);
      }
    };

    Double increment(Double i, boolean up) {
        Double inc = null;

        while (inc == null) {
            inc = ccc.get(i.intValue());

            if (up)
                --i;
            else
                ++i;
        }
        return inc;
    }

    public static void main(String[] args) {
        TestMain tt = new TestMain();

        for (double i = 1; i < 1000; i += tt.increment(i, true)) {
            System.out.print(i + ",");
        }
    }
}
公共类TestMain{
私有静态映射ccc=newhashmap(){
{put(1,0.01);put(2,0.02);put(3,0.05);put(4,0.1);put(6,0.2);
推杆(10,0.5);推杆(20,1.0);推杆(30,2.0);推杆(50,5.0);推杆(100,10.0);
}
};
双增量(双i,布尔值向上){
双inc=null;
while(inc==null){
inc=ccc.get(i.intValue());
如果(向上)
--一,;
其他的
++一,;
}
返回公司;
}
公共静态void main(字符串[]args){
TestMain tt=新的TestMain();
对于(双i=1;i<1000;i+=tt.增量(i,真)){
系统输出打印(i+“,”);
}
}
}
这是为了模拟给定值的范围,这些值是由


Java中的浮点算法似乎引入了一些意外错误。例如,我得到的是2.180000000000001,而不是2.18。浮点数的用途是你不能相信对它们执行的算术结果?我怎样才能避开这个问题?

浮点数是不精确的,特别是因为它们是以二进制分数(1/2,1/4,1/8,1/16,1/32,…)而不是十进制分数(1/10,1/100,1/1000,…)工作的。只需定义您认为“足够接近”的值,并使用类似于
Math.abs(a-b)<0.000001
的方法,如果您需要精确的十进制值,您应该使用
java.Math.BigDecimal
。然后阅读背景知识,了解你为什么会得到这些结果


(我有一个你可能会发现它更容易阅读,当然也更短。Java和.NET之间的差异对于理解这个问题来说基本上是无关的。)

通过使用格式化输出,你可以使程序的输出看起来更像你所期望的

显然,底层的浮点算法仍然可以正常工作,但至少输出的可读性会更高

例如,要将结果四舍五入到小数点后两位,请执行以下操作:

System.out.print(String.format(".2f", i) + ","); 

浮点数使用二进制分数,而不是十进制分数。也就是说,您习惯于使用由十分之一位、百分之一位、千分之一位等组成的小数。d1/10+d2/100+d3/1000。。。但是浮点数是二进制的,所以它们有半位数、四分之一位数、八分之一位数等等。d1/2+d2/4+d3/8

许多十进制分数不能用任何有限个二进制数字精确表示。例如,1/2是没有问题的:十进制是.5,二进制是.1。3/4是十进制.75,二进制.11。但1/10在十进制中是干净的.1,但在二进制中是.000110011。。。“0011”永远重复。由于计算机只能存储有限数量的数字,因此在某些情况下必须将其截断,因此答案并不精确。当我们将输出转换回十进制时,我们得到一个看起来很奇怪的数字


正如Jon Skeet所说,如果您需要精确的小数,请使用BigDecimal。如果性能是一个问题,您可以滚动自己的小数点。比如,如果你知道你总是想要精确的3位小数,并且数字不会超过100万左右,你可以简单地使用int加上假定的3位小数,在做算术和编写输出格式函数时进行必要的调整,将小数点插入正确的位置。但是99%的时间性能不是一个值得麻烦的大问题。

从哲学角度讲,我想知道:今天大多数计算机CPU都内置支持整数算术和浮点算术,但不支持十进制算术。为什么不呢?由于这个舍入问题,我已经有好几年没有写过浮动可用的应用程序了。你当然不能用它们来计算金额:没有人想在销售收据上打印“42.320003美元”的价格。没有一个会计会接受“我们可能会在这里或那里损失一分钱,因为我们使用的是二进制分数,并且有舍入错误”

浮标可以用于测量,比如距离或温度,在这些测量中,没有“精确答案”这样的东西,无论如何,你必须在某个点上对仪器的精度进行四舍五入。我想对于在化学实验室编写计算机程序的人来说,浮子是经常使用的。但对我们这些商界人士来说,它们几乎毫无用处

早在我在大型机上编程的古代,IBM360系列CPU就内置了对压缩十进制算法的支持。它们存储字符串,其中每个字节包含两个十进制数字,即前四位的值为0到9,后四位的值也为0到9,CPU具有算术函数来处理它们。为什么英特尔不能做这样的事情?然后Java可以添加一个“decimal”数据类型,我们就不需要所有额外的垃圾了

当然,我不是说要废除浮动。只要加上小数


哦,好吧,随着伟大的社会运动的发展,我不认为这是一个会在街上引起很多公众兴奋或骚乱的运动。

顺便说一句,你可以尝试使用这个功能来确保(小数位数不太多)你的数字将被重新格式化,只保留你需要的小数

n
是一个包含大量小数的数字(例如
Math.PI
),
numberOfDecimals
是您需要的最大小数位数(例如,3.14的小数位数为2,3.151的小数位数为3)

从理论上讲,如果给numberOfDecmals设置一个负值,它也会切断数字的低位整数。例如,放置
n=1588.22
numberOfDecimals=-2
,函数将返回
1500.0


如果有什么问题,请告诉我。

您可以编写一些代码在您的机器上计算ε。我相信STD::C++定义它,它根据你所使用的其他方法来定义。
private static float calcEpsilonFloat() {
    float epsi = 1.0f;


    while ((float) (1.0 + (epsi / 2.0)) != 1.0)
    {
       epsi /= 2.0f;
    }

    return epsi;
}