很久之前了,当时做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
最后咱们还是来凑一个工具吧,本来是不用凑的,因为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));
}
}