Java:精确数字运算,加减乘除运算,解决浮点数计算精度损失的问题

 2024-01-15 03:01:49  阅读 0

今天在数值计算的时候遇到了一个问题。 程序如下:

double a = (3.3-2.4)/0.1;
System.out.println(a);

你可能认为结果很简单,就是9。事实上,结果是:8。为什么呢? 我查阅了一些资料,终于找到了原因。

为什么浮点数会失去精度

十进制数的二进制表示不如浮点数精确,或者双精度浮点数不准确的情况并不少见。 浮点值之所以不能用十进制精确表示,是由于CPU表示浮点数的方式决定的。 这样您可能会牺牲一些精度,并且某些浮点运算可能会引入错误。 以上面提到的情况为例,2.4的二进制表示并不完全是2.4。 相反,最接近的二进制表示形式是 2。原因是浮点数由两部分组成:指数和尾数。 浮点数的值实际上是通过特定的数学公式计算出来的。 您所经历的精度损失在任何操作系统和编程环境中都会发生。

注意:您可以使用编码 (BCD) 库来保持准确性。 BCD 数字编码方法对每个十进制数字进行单独编码。

类型不匹配

您可能会混合浮点和双浮点类型。 执行数学运算时请确保所有数据类型相同。

注意:float 类型的变量只有 7 位精度,而 float 类型的变量有 15 位精度。

如何进行浮点精度计算?

Java 中的简单浮点数类型 float 和 float 无法进行操作。 不仅仅是Java,很多其他编程语言也存在这个问题。 大多数情况下,计算的结果是准确的,但如果尝试几次(可以做一个循环),就可能会出现像上面这样的错误。 现在终于明白为什么会有BCD码了。

这个问题是相当严重的。 如果你有9.0元,你的电脑不会认为你可以购买价值10元的商品。

有些编程语言提供了特殊的货币类型来处理这种情况,但Java没有。 现在让我们看看如何解决这个问题。

四舍五入

我们的第一反应是四舍五入。 Math类中的round方法无法设置保留多少位小数。 我们只能这样做(保留小数点后两位):

public double round(double value){
    return Math.round(value*100)/100.0;
}

不幸的是,上面的代码不能正常工作,将 4.015 传递给这个方法将返回 4.01 而不是 4.02,正如我们在上面看到的

4.015*100=401。

所以如果我们想要实现精确舍入,我们就不能使用简单的类型来做任何操作

java.text。 也没有解决这个问题:

.out.(new java.text.(“0.00”).(4.025));

输出是4.02

这个原理在《Java》一书中也提到过。 float和sum只能用于科学计算或工程计算。 在商业计算中,我们不得不使用java.math。 构建树一共有 4 种方法。 我们不关心用来构建一棵树的两个。 还有两种方法。 他们是:

BigDecimal(double val) 
          Translates a double into a BigDecimal. 
BigDecimal(String val) 
          Translates the String repre sentation of a BigDecimal into a BigDecimal.

上面的API简要说明已经很清楚了,通常上面的比较好用。 我们可能想都没想就用了它,那有什么问题呢? 出现问题的时候,我发现上述方法的详细说明中有这么一段话:

注意:这个的 可以是 。 人们可能认为 new (.1) 等于 .1,但它等于 .. 因此 .1 可以作为 a (或者,为此,可以作为 any 的 a )。 因此, 中的 long 值不等于 0.1, 。

另一方面, () 是:new (“.1”) 等于 .1,正如人们所想的那样。 ,就是这个用了()。

事实证明,如果我们需要精确的计算,我们就必须使用足够的量才能做到! 《Java》书中的例子都是用来创建的,但书中并没有强调这一点。 这可能是一个小错误。

解决方案

现在我们可以解决这个问题了。 原则是能用且必须用足才能构建。

但想象一下,如果我们要做加法运算,我们需要将两个浮点数转换为 转换为浮点数。 这么繁琐的过程你能忍受吗? 下面我们提供一个工具类Arith来简化操作。 它提供以下静态方法,包括加法、减法、乘法、除法和舍入:

public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)

附录

源文件Arith.java:

import java.math.BigDecimal;
/**
* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精
* 确的浮点数运算,包括加减乘除和四舍五入。
*/
public class Arith{
    //默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;
    //这个类不能实例化
    private Arith(){
    }
    /**
     * 提供精确的加法运算。
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */
    public static double add(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }
    /**
     * 提供精确的减法运算。
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static double sub(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    } 
    /**
     * 提供精确的乘法运算。
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static double mul(double v1,double v2){
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }
    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
     * 小数点以后10位,以后的数字四舍五入。
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商
     */
    public static double div(double v1,double v2){
        return div(v1,v2,DEF_DIV_SCALE);
    }
    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
     * 定精度,以后的数字四舍五入。
     * @param v1 被除数
     * @param v2 除数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1,double v2,int scale){
        if(scale<0){
            throw new IllegalArgumentException(
                "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }
    /**
     * 提供精确的小数位四舍五入处理。
     * @param v 需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v,int scale){
        if(scale<0){
            throw new IllegalArgumentException(
                "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}

对于 d = 2.4;

.out.(d);//输出是2.4,但不是2。?

我看了一些资料,发现单个输出值可以正确显示为十进制。 我不明白为什么,但是在进行浮点计算时,浮点计算是指涉及浮点数的运算。 由于无法准确表示,这种运算通常伴随着近似或舍入。 也许它与方法的(d).()有关。 我的猜测是2.超出了输出的精度,所以被拦截了!

本文:Java:精确数字运算,加减乘除运算,解决浮点数计算中精度损失的问题,用法(加减乘除)

帖子

标签: 对象的用法(加减乘除),Java:精确数字运算,加减乘除运算,解决浮点数计算精度损失的问题

标签: 点数 运算 精度

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码