美文网首页
浮点数丢失精度问题

浮点数丢失精度问题

作者: 俩傻猫 | 来源:发表于2019-10-25 10:28 被阅读0次

浮点数在操作时不能得到很精确的结果,比如如下代码并未返回0.06。这就是浮点数丢失精度的问题。

System.out.println(0.05 + 0.01);

输出:
0.060000000000000005

我们平时在数字计算时使用的都是10进制,比如0.05+0.01=0.06。但是计算机语言使用的是2进制,也就是计算机只认识0和1。而浮点数在计算时丢失精度,也是因为2进制原因导致的。
比如浮点数0.05在计算机中的表示非0.05(10进制),而是0.000011···等一个无限循环的数字。同样0.01也是一个无限循环的数字。而float和double两个浮点数的位数是有限的,因此计算结果会有丢失。好比10进制中的1/3会约等于0.33,0.333等。
一个小数对应的2进制计算方法如下:

0.1转化成二进制的算法:
0.1*2=0.2======取出整数部分0
0.2*2=0.4======取出整数部分0
0.4*2=0.8======取出整数部分0
0.8*2=1.6======取出整数部分1
0.6*2=1.2======取出整数部分1
0.2*2=0.4======取出整数部分0
0.4*2=0.8======取出整数部分0
0.8*2=1.6======取出整数部分1
0.6*2=1.2======取出整数部分1
接下来会无限循环
0.2*2=0.4======取出整数部分0
0.4*2=0.8======取出整数部分0
0.8*2=1.6======取出整数部分1
0.6*2=1.2======取出整数部分1

所以0.1转化成二进制是:0.0 0011 0011 ......
同样的,0.75对应的二进制为:

0.75*2=1.5=====取出整数部分1
0.5*2 =1.0 =====取出整数部分1

即0.75的二进制为0.11。同样可计算出0.625的二进制为0.101。即0.75和0.625在二进制中是可以精确表示的,那么在计算机里的计算结果为:

System.out.println(0.75+0.625);
输出结果:
1.375

是可以得到正确的结果的。
如果要计算浮点数,要借助于BigDecimal和String

BigDecimal a = new BigDecimal(String.valueOf(0.05));
BigDecimal b = new BigDecimal(String.valueOf(0.01));
System.out.println(a.add(b));
输出结果:
0.06

如果仅仅使用BigDecimal也是会丢失精度的:

BigDecimal a = new BigDecimal(0.05);
BigDecimal b = new BigDecimal(0.01);
System.out.println(a.add(b));
输出结果
0.06000000000000000298372437868010820238851010799407958984375

如下为java源码中的说明

* The results of this constructor can be somewhat unpredictable.
     * One might assume that writing {@code new BigDecimal(0.1)} in
     * Java creates a {@code BigDecimal} which is exactly equal to
     * 0.1 (an unscaled value of 1, with a scale of 1), but it is
     * actually equal to
     * 0.1000000000000000055511151231257827021181583404541015625.
     * This is because 0.1 cannot be represented exactly as a
     * {@code double} (or, for that matter, as a binary fraction of
     * any finite length).  Thus, the value that is being passed
     * <i>in</i> to the constructor is not exactly equal to 0.1,
     * appearances notwithstanding.
     *
     * <li>
     * The {@code String} constructor, on the other hand, is
     * perfectly predictable: writing {@code new BigDecimal("0.1")}
     * creates a {@code BigDecimal} which is <i>exactly</i> equal to
     * 0.1, as one would expect.  Therefore, it is generally
     * recommended that the {@linkplain #BigDecimal(String)
     * <tt>String</tt> constructor} be used in preference to this one.
     *
     * <li>
     * When a {@code double} must be used as a source for a
     * {@code BigDecimal}, note that this constructor provides an
     * exact conversion; it does not give the same result as
     * converting the {@code double} to a {@code String} using the
     * {@link Double#toString(double)} method and then using the
     * {@link #BigDecimal(String)} constructor.  To get that result,
     * use the {@code static} {@link #valueOf(double)} method.
     * </ol>
     *
     * @param val {@code double} value to be converted to
     *        {@code BigDecimal}.
     * @throws NumberFormatException if {@code val} is infinite or NaN.
     */
    public BigDecimal(double val) {
        this(val,MathContext.UNLIMITED);
    }

BigDecimal介绍

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

相关文章

网友评论

      本文标题:浮点数丢失精度问题

      本文链接:https://www.haomeiwen.com/subject/znlzectx.html