Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
JavaGuide/docs/java/basis/bigdecimal.md at main · PDPENG/JavaGuide · GitHub
Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JavaGuide/docs/java/basis/bigdecimal.md at main · PDPENG/JavaGuide · GitHub
Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JavaGuide/docs/java/basis/bigdecimal.md at main · PDPENG/JavaGuide · GitHub
Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' JavaGuide/docs/java/basis/bigdecimal.md at main · PDPENG/JavaGuide · GitHub
Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JavaGuide/docs/java/basis/bigdecimal.md at main · PDPENG/JavaGuide · GitHub
Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JavaGuide/docs/java/basis/bigdecimal.md at main · PDPENG/JavaGuide · GitHub
Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); JavaGuide/docs/java/basis/bigdecimal.md at main · PDPENG/JavaGuide · GitHub
Skip to content

Latest commit

History

History
345 lines (281 loc) · 11 KB

File metadata and controls

345 lines (281 loc) · 11 KB
titleBigDecimal解决浮点数运算精度丢失问题
categoryJava
tag
Java基础

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

这篇文章,我就简单解释一下浮点数运算出现精度丢失的原因以及 BigDecimal 的常见用法,希望对大家有帮助!

BigDecimal 介绍

BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。

纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!

示例代码:

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

为什么浮点数 floatdouble 运算的时候会有精度丢失的风险呢?

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

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

// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。0.2 * 2 = 0.4 -> 00.4 * 2 = 0.8 -> 00.8 * 2 = 1.6 -> 10.6 * 2 = 1.2 -> 10.2 * 2 = 0.4 -> 0发生循环)
...

关于浮点数的更多内容,建议看一下计算机系统基础(四)浮点数这篇文章。

BigDecimal 的用处

《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:

floata = 1.0f - 0.9f;
floatb = 0.9f - 0.8f;
System.out.println(a);// 0.100000024System.out.println(b);// 0.099999964System.out.println(a == b);// false

从输出结果就可以看出发生精度丢失的问题。

想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
BigDecimalc = newBigDecimal("0.8");
BigDecimalx = a.subtract(b);
BigDecimaly = b.subtract(c);
System.out.println(x); /* 0.1 */System.out.println(y); /* 0.1 */System.out.println(Objects.equals(x, y)); /* true */

BigDecimal 常见方法

加减乘除

add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.add(b));// 1.9System.out.println(a.subtract(b));// 0.1System.out.println(a.multiply(b));// 0.90System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。

publicBigDecimaldivide(BigDecimaldivisor, intscale, RoundingModeroundingMode) {
returndivide(divisor, scale, roundingMode.oldMode);
}

保留规则非常多,这里列举几种:

publicenumRoundingMode {
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3UP(BigDecimal.ROUND_UP),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -1 , -2.5 -> -2DOWN(BigDecimal.ROUND_DOWN),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -1 , -2.5 -> -2CEILING(BigDecimal.ROUND_CEILING),
// 2.5 -> 2 , 1.6 -> 1// -1.6 -> -2 , -2.5 -> -3FLOOR(BigDecimal.ROUND_FLOOR),
// 2.5 -> 3 , 1.6 -> 2// -1.6 -> -2 , -2.5 -> -3HALF_UP(BigDecimal.ROUND_HALF_UP),
//......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b

BigDecimala = newBigDecimal("1.0");
BigDecimalb = newBigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。

BigDecimalm = newBigDecimal("1.255433");
BigDecimaln = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 的使用注意事项

注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。

《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享

网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。

我对其进行了简单改进,分享一下源码:

importjava.math.BigDecimal;
importjava.math.RoundingMode;
/** * 简化BigDecimal计算的小工具类 */publicclassBigDecimalUtil {
/** * 默认除法运算精度 */privatestaticfinalintDEF_DIV_SCALE = 10;
privateBigDecimalUtil() {
}
/** * 提供精确的加法运算。 * * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */publicstaticdoubleadd(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.add(b2).doubleValue();
}
/** * 提供精确的减法运算。 * * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */publicstaticdoublesubtract(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.subtract(b2).doubleValue();
}
/** * 提供精确的乘法运算。 * * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */publicstaticdoublemultiply(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.multiply(b2).doubleValue();
}
/** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2) {
returndivide(v1, v2, DEF_DIV_SCALE);
}
/** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */publicstaticdoubledivide(doublev1, doublev2, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的小数位四舍五入处理。 * * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */publicstaticdoubleround(doublev, intscale) {
if (scale < 0) {
thrownewIllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimalb = BigDecimal.valueOf(v);
BigDecimalone = newBigDecimal("1");
returnb.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
/** * 提供精确的类型转换(Float) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticfloatconvertToFloat(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.floatValue();
}
/** * 提供精确的类型转换(Int)不进行四舍五入 * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticintconvertsToInt(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.intValue();
}
/** * 提供精确的类型转换(Long) * * @param v 需要被转换的数字 * @return 返回转换结果 */publicstaticlongconvertsToLong(doublev) {
BigDecimalb = newBigDecimal(v);
returnb.longValue();
}
/** * 返回两个数中大的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中大的一个值 */publicstaticdoublereturnMax(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.max(b2).doubleValue();
}
/** * 返回两个数中小的一个值 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 返回两个数中小的一个值 */publicstaticdoublereturnMin(doublev1, doublev2) {
BigDecimalb1 = newBigDecimal(v1);
BigDecimalb2 = newBigDecimal(v2);
returnb1.min(b2).doubleValue();
}
/** * 精确对比两个数字 * * @param v1 需要被对比的第一个数 * @param v2 需要被对比的第二个数 * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1 */publicstaticintcompareTo(doublev1, doublev2) {
BigDecimalb1 = BigDecimal.valueOf(v1);
BigDecimalb2 = BigDecimal.valueOf(v2);
returnb1.compareTo(b2);
}
}

总结

浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。

不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。