java中double和float精度丢失问题以及BigDecimal详解

《阿里巴巴 Java 开发手册》中提到:“为了避免精度丢失,可以使用 BigDecimal 来进行浮点数的运算”。

float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false

由此可以得出浮点数的运算确实会有精度丢失的风险!!!

为什么浮点数 float 或 double 运算的时候会有精度丢失的风险

这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。

就比如说十进制下的 0.2 就没办法精确转换成二进制小数:

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,
// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。
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(发生循环)

...
 

想要解决浮点数运算精度丢失这个问题,可以直接使用 BigDecimal 来定义浮点数的值,然后再进行浮点数的运算操作即可。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");

BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);

System.out.println(x);// 0.1
System.out.println(y);// 0.1

注意:BigDecimal 也存在精度丢失问题,这种也是由于double 或者 float 导致,所以,在涉及到精度计算的过程中,我们尽量使用 String 类型来进行转换。

public static void main(String[] args) {
    BigDecimal bigDecimal=new BigDecimal(25);
    System.out.println(bigDecimal);
    bigDecimal=new BigDecimal("2.55");
    System.out.println(bigDecimal);
    bigDecimal=new BigDecimal(2.55);
    System.out.println(bigDecimal);
}

25
2.55
2.54999999999999982236431605997495353221893310546875

BigDecimal工具类

import java.math.BigDecimal;

/**       
 * 类名称:BigDecimalUtil   
 * 类描述:   
 * 创建人:ljf
 * 创建时间:2015-8-24 下午1:53:41         
 */
public class BigDecimalUtil {
	// 默认除法运算精度
	private static final int DEFAULT_DIV_SCALE = 10;

	/**
	 * 提供精确的加法运算。
	 * @param v1 被加数
	 * @param v2 加数
	 * @return 两个参数的和
	 */
	public static double add(Double v1, Double v2) {
		if(v1 == null){
			v1 = 0.0;
		}
		if(v2 == null){
			v2 = 0.0;
		}
		BigDecimal b1 = new BigDecimal(v1.toString());
		BigDecimal b2 = new BigDecimal(v2.toString());
		return b1.add(b2).doubleValue();
	}
	
	/**
	 * 提供精确的减法运算。
	 * @param v1 被减数
	 * @param v2 减数
	 * @return 两个参数的差
	 */
	public static double subtract(Double v1, Double v2) {
		if(v1 == null){
			v1 = 0.0;
		}
		if(v2 == null){
			v2 = 0.0;
		}
		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 multiply(Double v1, Double v2) {
		if(v1 == null){
			v1 = 0.0;
		}
		if(v2 == null){
			v2 = 0.0;
		}
		BigDecimal b1 = new BigDecimal(v1.toString());
		BigDecimal b2 = new BigDecimal(v2.toString());
		return b1.multiply(b2).doubleValue();
	}
	
	
	/**
	 * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到小数点以后10位,以后的数字四舍五入。
	 * @param v1 被除数
	 * @param v2 除数
	 * @return 两个参数的商
	 */
	public static double divide(Double v1, Double v2) {
		return divide(v1, v2, DEFAULT_DIV_SCALE);
	}
	
	/**
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
	 * @param v1 被除数
	 * @param v2 除数
	 * @param scale 表示表示需要精确到小数点以后几位。
	 * @return 两个参数的商
	 */
	public static double divide(Double v1, Double v2, int scale) {
		if(v1 == null){
			v1 = 0.0;
		}
		if(v2 == null){
			v2 = 0.0;
		}
		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b1 = new BigDecimal(v1.toString());
		BigDecimal b2 = new BigDecimal(v2.toString());
		return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
	}
	
	/**
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
	 * @param v1 被除数
	 * @param v2 除数
	 * @param scale 表示表示需要精确到小数点以后几位。
	 * @return 两个参数的商
	 */
	public static BigDecimal divide(Integer v1, Integer v2, int scale) {
		if(v1 == null||v2==null || v1==0 || v2==0){
			return new BigDecimal(0);
		}
		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b1 = new BigDecimal(v1.toString());
		BigDecimal b2 = new BigDecimal(v2.toString());
		return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP);
	}
	
	/**
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度,由roundMode参数指定的模式进行舍入。
	 * @param value1 被除数
	 * @param value2 除数
	 * @param scale 表示表示需要精确到小数点以后几位
	 * @param roundMode 舍入模式
	 * @return 两个参数的商
	 */
	public static double divide(Double value1, Double value2, int scale, int roundingMode) {
		if (scale < 0 || roundingMode < 0) {
			throw new IllegalArgumentException("The scale or roundingMode must be a positive integer or zero");
		}
		BigDecimal b1 = new BigDecimal(value1.toString());
		BigDecimal b2 = new BigDecimal(value2.toString());
		return b1.divide(b2, scale, roundingMode).doubleValue();
	}
	
	/**
	 * 提供精确的小数位四舍五入处理。
	 * @param value 需要四舍五入的数字
	 * @param scale 小数点后保留几位
	 * @return 四舍五入后的结果
	 */
	public static double round(double value, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(value));
		BigDecimal one = new BigDecimal("1");
		return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
	}
	
	/**
	 * 提供精确的小数位四舍五入处理。
	 * @param value 需要四舍五入的数字
	 * @param scale 小数点后保留几位
	 * @param roundingMode 舍入模式
	 * @return 舍入后的结果
	 */
	public static double round(double value, int scale, int roundingMode) {
		if (scale < 0 || roundingMode < 0) {
			throw new IllegalArgumentException("The scale or roundingMode must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(value));
		BigDecimal one = new BigDecimal("1");
		return b.divide(one, scale, roundingMode).doubleValue();
	}
	
	/**
	 * 提供去掉多余0
	 * @param value
	 * @return
	 */
	 public static String removeExcessZero(BigDecimal value){
		 if (value==null) {
			return "0";
		}
		return (value.doubleValue()+"").replaceAll("[.]0+?$", "");
	 }
	 
	 /**
	  * 计算百分比及去掉多余的0
	  * 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入
	  * @return
	  */
	 public static String calculatePercent(Integer v1, Integer v2, int scale){
		 BigDecimal value = divide(v1, v2, scale);
		 int newScale=scale-2;
		 if (newScale<0) {
			newScale=0;
		 }
		 return removeExcessZero(value.multiply(new BigDecimal(100)).setScale(newScale));
	 }
	 
	 /**
	  * 计算修正百分比及去掉多余的0(超过100%则处理为100%)
	  * 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入
	  * @return
	  */
	 public static String calculateAndRevisePercent(Integer v1, Integer v2, int scale){
		 BigDecimal value = divide(v1, v2, scale);
		 if (value.compareTo(new BigDecimal(1))==1) {
			return "100";
		 }
		 int newScale=scale-2;
		 if (newScale<0) {
			newScale=0;
		 }
		 return removeExcessZero(value.multiply(new BigDecimal(100)).setScale(newScale));
	 }
	 
	 
	 /**
	  * 转换单位
	  * @return
	  */
	 public static String conversionUnit(Integer original,Integer unitValue,int scale,String unitName){
		 BigDecimal value = divide(original, unitValue, scale);
		 return removeExcessZero(value)+(unitName==null?"":unitName);
	 }
	 
	/**
	 * 比较2个Double的值大小
	 * @param v1
	 * @param v2
	 * @return -1 0 1 分别代表v1比v2小,一样,大
	 */
	public static int compare(Double v1, Double v2) {
		Double sv1 = v1;
		Double sv2 = v2;
		if (sv1 == null) {
			sv1 = 0.0;
		}
		if (sv2 == null) {
			sv2 = 0.0;
		}
		BigDecimal b1 = new BigDecimal(sv1.toString());
		BigDecimal b2 = new BigDecimal(sv2.toString());
		return b1.compareTo(b2);
	}

	/**
	 * 将任何类型的数据转换成BigDecimal
	 * @param obj
	 * @return
	 * @author xiangmin.fang
	 * @since 2018.01.24
	 */
	public static BigDecimal castToBigDecimal(Object obj) {
		if (obj == null) {
			return null;
		}
		if (obj instanceof BigDecimal) {
			return (BigDecimal) obj;
		}
		if (obj instanceof String) {
			return new BigDecimal((String) obj);
		}
		// 转换成String, 然后用String来构造BigDecimal
		return new BigDecimal(StringUtils.castToString(obj));
	}
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值