《阿里巴巴 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));
}
}