关于java中浮点数运算(BigDecimal)

把玩BigDecimal

很久之前了,当时做double的加减乘除,出现了问题,发现是精度原因,换了BigDecimal之后也没有细品。结果发现其中的除法运算出了问题(当时测了加减乘以为过了就当是过了),记录一下。

其中有一些细节,写在注释中了。

package club.mzywucai.blog.decimal_arith_demo.decimal;

import org.junit.Test;

import java.math.BigDecimal;

/**
 * @author mzywucai
 * @Description
 * @date 2020/5/25
 */
public class DecimalTest {

    @Test
    public void errorTest() {
        // 浮点数没有办法是用二进制进行精确表示。
        // 我们的CPU表示浮点数由两个部分组成:指数和尾数,这样的表示方法一般都会失去一定的精确度。
        // 有些浮点数运算也会产生一定的误差。
        System.out.println(0.1 + 0.2); // error
        System.out.println(0.1 - 0.42); // error


        System.out.println(4.15 * 8.7); // right
        System.out.println(4.105 * 8.87); // right
        System.out.println(4.015 * 100); // error

        System.out.println(353.1 / 1000); // error
    }

    @Test
    public void utilTest() {
        System.out.println(add(0.1, 0.2));
        System.out.println(subtract(0.1, 0.42));
        System.out.println(mul(4.015, 100));
        System.out.println(mul(4.1215, 4546.4453));

        System.err.println(div(-353.1, 1000, 6));

        System.out.println(round(3.225, 2));
        System.out.println(remainder(35.221, 1, 2));

        System.out.println(compare(33.212, 33.211));
        System.out.println(compare(33.210, 33.211));
    }

    // 进行加法运算
    public double add(double v1, double v2) {
        // BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));

        // 同还有floatValue
        return b1.add(b2).doubleValue();
    }

    // 进行减法运算
    public double subtract(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    // 进行乘法运算
    public double mul(double d1, double d2) {
        BigDecimal b1 = new BigDecimal(Double.toString(d1));
        BigDecimal b2 = new BigDecimal(Double.toString(d2));
        return b1.multiply(b2).doubleValue();
    }


    // 进行除法运算
    // 1、除数的小数点位数与结果的位数直接相关联,而被除数的小数点位数与结果的位数没有啥关系(至少简单测试一下看上去是这样),但是被除数的小数位数不是说没有用,而是对结果与直接关联。
    // 2、除数如果是N位小数,那么结果就会是N位小数。但有2个例外:
    //  (1)除数如果没有小数,在BigDecimal后面再继续用doubleValue会发现有xxx.0出现;
    //  (2)除数的最后1位如果为0,那么就会被忽略。如上eg2与eg3。
    public double div(double d1, double d2) {
        // BigDecimal 应该使用 String 构造函数,禁止使用double构造函数。
        // 当你使用new BigDecimal(Double d) 时,得到的BigDecimal中的long值并不是精确
        // 可追溯到native long doubleToRawLongBits(double value);
        // 这时使用new BigDecimal(String s)得到的即准确值,用于计算不会出现误差
        // Double.toString / string.valueOf 同
        BigDecimal b1 = new BigDecimal(Double.toString(d1));
        BigDecimal b2 = new BigDecimal(Double.toString(d2));

        return b1.divide(b2).doubleValue();
    }


    // 进行除法运算
    public double div(double d1, double d2, int scale) {
        BigDecimal b1 = new BigDecimal(Double.toString(d1));
        BigDecimal b2 = new BigDecimal(Double.toString(d2));
        // ROUND_UP         0
        // 舍入模式从零开始。
        // 进位处理,2.35变成2.4 。舍入远离零的舍入模式。在丢弃非零部分之前始终增加数字。

        // ROUND_DOWN       1
        // 舍入模式向零舍入。
        // 直接删除多余的小数位,如2.35会变成2.3 。接近零的舍入模式。

        // ROUND_CEILING    2
        // 圆形模式向正无穷大转弯。
        // 接近正无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;如果为负,则舍入行为与 ROUND_DOWN 相同。

        // ROUND_FLOOR      3
        // 舍入模式向负无穷大转弯。
        // 接近负无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;如果为负,则舍入行为与 ROUND_UP 相同。

        // ROUND_HALF_UP    4
        // 四舍五入模式向“最近邻居”转弯,除非两个邻居都是等距的,在这种情况下是圆括弧的。
        // 向上四舍五入,2.35变成2.4

        // ROUND_HALF_DOWN  5   四舍五入模式向“最近邻居”转弯,除非这两个邻居都是等距离的,在这种情况下,这是倒圆的。
        // 向下四舍五入,2.35变成2.3

        // ROUND_HALF_EVEN
        // 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
        // 如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同。
        // 如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。
        // 注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    // 进行四舍五入操作
    public double round(double d, int scale) {   // 进行四舍五入操作
        BigDecimal b1 = new BigDecimal(d);
        BigDecimal b2 = new BigDecimal(1);
        // 任何一个数字除以1都是原数字
        // ROUND_HALF_UP是BigDecimal的一个常量,表示进行四舍五入的操作
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    // 取余数
    public String remainder(double v1, double v2, int scale) {
        BigDecimal b1 = new BigDecimal(String.valueOf(v1));
        BigDecimal b2 = new BigDecimal(String.valueOf(v2));

        return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    // 比较大小
    //如果v1 大于v2 则 返回true 否则false
    public boolean compare(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(String.valueOf(v1));
        BigDecimal b2 = new BigDecimal(String.valueOf(v2));
        // 若需要比较两个BigDecimal, 总是使用compareTo()比较两个BigDecimal的值,不要使用equals()!
        int bj = b1.compareTo(b2);
        boolean res;
        if (bj > 0)
            res = true;
        else
            res = false;
        return res;
    }

}

参考文章:

https://blog.csdn.net/haihongazar/article/details/50466565

https://www.cnblogs.com/chenssy/archive/2012/09/09/2677279.html

https://juejin.im/post/5d81e96b51882507ba2269d8

https://www.jianshu.com/p/c910cf1ee90e

https://www.liaoxuefeng.com/wiki/1252599548343744/1279768011997217

DecimalUtil

最后咱们还是来凑一个工具吧,本来是不用凑的,因为gitlab上我搜到了完整的DecimalUtil,但是我还是想弄一个自己的工具,顺手些。

https://gitlab.com/yudao123/mavendemo/blob/master/src/main/java/com/mcd/common/util/DecimalUtil.java

package club.mzywucai.blog.decimal_arith_demo.decimal;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class DecimalUtil {

    /**
     * 默认保留两位小数
     *
     * @param value
     * @return
     */
    public static double round(double value) {
        return round(value, 2);
    }

    public static double round(double value, int scale) {
        BigDecimal bd = new BigDecimal(String.valueOf(value));
        // value.divide(new BigDecimal(1), scale, BigDecimal.ROUND_HALF_UP).doubleValue()
        Number newValue = bd.setScale(scale, RoundingMode.HALF_UP);

        return newValue.doubleValue();
    }


    /**
     * 加法运算
     * @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 d1
     * @param d2
     * @return
     */
    public static double mul(double d1, double d2) {
        BigDecimal b1 = new BigDecimal(Double.toString(d1));
        BigDecimal b2 = new BigDecimal(Double.toString(d2));

        return b1.multiply(b2).doubleValue();
    }

    /**
     * 进行乘法运算 限制小数位数
     * @param d1
     * @param d2
     * @param scale
     * @return
     */
    public static double mul(double d1, double d2, int scale) {
        BigDecimal b1 = new BigDecimal(Double.toString(d1));
        BigDecimal b2 = new BigDecimal(Double.toString(d2));

        return round(b1.multiply(b2).doubleValue(), scale);
    }

    /**
     * 进行除法运算 默认保留两位
     * @param d1
     * @param d2
     * @return
     */
    public static double div(double d1, double d2) {
        return div(d1, d2, 2);
    }

    /**
     * 除法运算 限制小数位数
     * @param d1
     * @param d2
     * @param scale
     * @return
     */
    public static double div(double d1, double d2,int scale) {
        BigDecimal b1 = new BigDecimal(String.valueOf(d1));
        BigDecimal b2 = new BigDecimal(String.valueOf(d2));

        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 取余数 限制小数位数
     * @param v1
     * @param v2
     * @param scale
     * @return
     */
    public static String remainder(double v1, double v2, int scale) {
        BigDecimal b1 = new BigDecimal(String.valueOf(v1));
        BigDecimal b2 = new BigDecimal(String.valueOf(v2));

        return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 比较大小 如果v1 大于v2 则 返回true 否则false
     * @param v1
     * @param v2
     * @return
     */
    public static boolean compare(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(String.valueOf(v1));
        BigDecimal b2 = new BigDecimal(String.valueOf(v2));

        int bj = b1.compareTo(b2);
        boolean res;
        if (bj > 0)
            res = true;
        else
            res = false;
        return res;
    }



    public static void main(String[] args) {
        System.out.println(add(0.1, 0.2));
        System.out.println(sub(0.1, 0.42));

        System.out.println(mul(4.015, 100));
        System.out.println(mul(4.1215, 4546.4453));
        System.out.println(mul(4.1215, 4546.4453, 3));

        System.out.println(div(-353.1, 1000, 6));
        System.out.println(div(-353.1, 1000));

        System.out.println(round(3.225, 2));
        System.out.println(remainder(35.221, 1, 2));

        System.out.println(compare(33.212, 33.211));
        System.out.println(compare(33.210, 33.211));
    }

}

你可能感兴趣的:(Java基础篇,代码思考)