Java中关于 BigDecimal 的一个导致double精度损失的"bug"
背景
在博客 恶心的0.5四舍五入问题 一文中看到一个关于 0.5 不能正确的四舍五入的问题。主要说的是 double 转换到 BigDecimal 后,进行四舍五入得不到正确的结果:
public class BigDecimalTest {
public static void main(String[] args){
double d = 301353.05;
BigDecimal decimal = new BigDecimal(d);
System.out.println(decimal);//301353.0499999999883584678173065185546875
System.out.println(decimal.setScale(1, RoundingMode.HALF_UP));//301353.0
}
}
输出的结果为:
301353.0499999999883584678173065185546875
301353.0
这个结果显然不是我们所期望的,我们希望的是得到 301353.1 。
原因
允许明眼人一眼就看出另外问题所在——BigDecimal的构造函数 public BigDecimal(double val) 损失了double 参数的精度,最后才导致了错误的结果。所以问题的关键是:BigDecimal的构造函数 public BigDecimal(double val) 损失了double 参数的精度。
解决之道
因为上面找到了原因,所以也就很好解决了。只要防止了 double 到 BigDecimal 的精度的损失,也就不会出现问题。
1)很容易想到第一个解决办法:使用BigDecimal的以String为参数的构造函数:public BigDecimal(String val) 来替代。
public class BigDecimalTest {
public static void main(String[] args){
double d = 301353.05;
System.out.println(new BigDecimal(new Double(d).toString()));
System.out.println(new BigDecimal("301353.05"));
System.out.println(new BigDecimal("301353.895898895455898954895989"));
}
}
输出结果:
301353.05
301353.05
301353.895898895455898954895989
我们看到了没有任何的精度损失,四舍五入也就肯定不会出错了。
2)BigDecimal的构造函数 public BigDecimal(double val) 会损失了double 参数的精度,这个也许应该可以算作是 JDK 中的一个 bug 了。既然存在bug,那么我们就应该解决它。上面的办法是绕过了它。现在我们实现自己的 double 到 BigDecimal 的转换,并且保证在某些情况下可以完全不损失 double 的精度。
import java.math.BigDecimal;
public class BigDecimalUtil {
public static BigDecimal doubleToBigDecimal(double d){
String doubleStr = String.valueOf(d);
if(doubleStr.indexOf(".") != -1){
int pointLen = doubleStr.replaceAll("\\d+\\.", "").length(); // 取得小数点后的数字的位数
pointLen = pointLen > 16 ? 16 : pointLen; // double最大有效小数点后的位数为16
double pow = Math.pow(10, pointLen);
long tmp = (long)(d * pow);
return new BigDecimal(tmp).divide(new BigDecimal(pow));
}
return new BigDecimal(d);
}
public static void main(String[] args){
// System.out.println(doubleToBigDecimal(301353.05));
// System.out.println(doubleToBigDecimal(-301353.05));
// System.out.println(doubleToBigDecimal(new Double(-301353.05)));
// System.out.println(doubleToBigDecimal(301353));
// System.out.println(doubleToBigDecimal(new Double(-301353)));
double d = 301353.05;//5898895455898954895989;
System.out.println(doubleToBigDecimal(d));
System.out.println(d);
System.out.println(new Double(d).toString());
System.out.println(new BigDecimal(new Double(d).toString()));
System.out.println(new BigDecimal(d));
}
}
输出结果:
301353.05
301353.05
301353.05
301353.
301353.9999999883584678173065185546875
上面我们自己写了一个工具类,实现了 double 到 BigDecimal 的“无损失”double精度的转换。方法是将小数点后有有效数字的double先转换到小数点后没有有效数字的double,然后在转换到 BigDecimal ,之后使用BigDecimal的 divide 返回之前的大小。
上面的结果看起来好像十分的完美,但是其实是存在问题的。上面我们也说到了“某些情况下可以完全不损失 double 的精度”,我们先看一个例子:
public static void main(String[] args){
double d = 301353.05;
System.out.println(doubleToBigDecimal(d));
System.out.println(d);
System.out.println(new Double(d).toString());
System.out.println(new BigDecimal(new Double(d).toString()));
System.out.println(new BigDecimal(d));
System.out.println("=========================");
d = 301353.895898895455898954895989;
System.out.println(doubleToBigDecimal(d));
System.out.println(d);
System.out.println(new Double(d).toString());
System.out.println(new BigDecimal(new Double(d).toString()));
System.out.println(new BigDecimal(d));
System.out.println(new BigDecimal("301353.895898895455898954895989"));
System.out.println("=========================");
d = 301353.46899434;
System.out.println(doubleToBigDecimal(d));
System.out.println(d);
System.out.println(new Double(d).toString());
System.out.println(new BigDecimal(new Double(d).toString()));
System.out.println(new BigDecimal(d));
System.out.println("=========================");
d = 301353.45789666;
System.out.println(doubleToBigDecimal(d));
System.out.println(d);
System.out.println(new Double(d).toString());
System.out.println(new BigDecimal(new Double(d).toString()));
System.out.println(new BigDecimal(d));
}
输出结果:
301353.05
301353.05
301353.05
301353.05
301353.9999999883584678173065185546875
=========================
301353.8958988954
301353.8958988954
301353.89589889545
301353.89589889545
301353.8958988954593002796173095703125
301353.895898895458954895989
=========================
301353.46899434
301353.46899434
301353.46899434
301353.46899434
301353.46899439862386286258697509765625
=========================
301353.45789666
301353.45789666
301353.45789666
301353.45789666
301353.4578966600238345563411712646484375
我们可以看到:我们自己实现的 doubleToBigDecimal 方法只有在 double 的小数点后的数字位数比较少时(比如只有5,6位),才能保证完全的不损失精度。
在 double 的小数点后的数字位数比较多时,d * pow 会存在精度损失,所以最终的结果也会存在精度损失。所以如果小数点后的位数比较多时,还是使用 BigDecimal的 String 参数的构造函数为好,只有在小数点后的位数比较少时,才可以采用自己实现的 doubleToBigDecimal 方法。
因为我们看到原始的double的转换之后的BigDecimal的数字的最后一位一个时5,一个是4,原因是在上面的转换方法中:
long tmp = (long)(d * pow);
这一步可能存在很小的精度损失,因为 d 是一个 double ,d * pow 之后还是一个 double(但是小数点之后都是0了,所以到long的转换没有精度损失) ,所以会存在很小的精度损失(double的计算总是有可能存在精度损失的)。但是这个精度损失和 BigDecimal的构造函数 public BigDecimal(double val) 的精度损失相比而言,不会显得那么的突兀(也许我们自己写的doubleToBigDecimal也是存在问题的,欢迎指点)。
总结:
如果需要保证精度,最好是不要使用BigDecimal的double参数的构造函数,因为存在损失double参数精度的可能,最好是使用BigDecimal的String参数的构造函数。最好是杜绝使用BigDecimal的double参数的构造函数。
后记:
其实说这是BigDecimal的一个bug,有标题党的嫌疑,最多可以算作是BigDecimal的一个“坑”。
Java中关于 BigDecimal 的一个导致double精度损失的"bug"的更多相关文章
- java 常用类库:BigInteger大整数;BigDecimal大小数(解决double精度损失);
大整数BigInteger package com.zmd.common_class_libraries; import java.math.BigInteger; /** * @ClassName ...
- Java中的BigDecimal类精度问题
bigdecimal 能保证精度的原理是:BigDecimal的解决方案就是,不使用二进制,而是使用十进制(BigInteger)+小数点位置(scale)来表示小数,就是把所有的小数变成整数,记录小 ...
- Java中的BigDecimal类和int和Integer总结
前言 我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题.如下一段代码: System.out.println(0.05 + 0.01); System.out.println(1.0 - 0. ...
- Java中的Bigdecimal类型运算
Java中的Bigdecimal类型运算 双精度浮点型变量double可以处理16位有效数.在实际应用中,需要对更大或者更小的数进行运算和处理.Java在java.math包中提 供的API类BigD ...
- Java中是否可以调用一个类中的main方法?
前几天面试的时候,被问到在Java中是否可以调用一个类中的main方法?回来测试了下,答案是可以!代码如下: main1中调用main2的主方法 package org.fiu.test; impor ...
- (转)Java中使用正则表达式的一个简单例子及常用正则分享
转自:http://www.jb51.net/article/67724.htm 这篇文章主要介绍了Java中使用正则表达式的一个简单例子及常用正则分享,本文用一个验证Email的例子讲解JAVA中如 ...
- 【笔试题】Java 中如何递归显示一个目录下面的所有目录和文件?
笔试题 Java 中如何递归显示一个目录下面的所有目录和文件? import java.io.File; public class Test { private static void showDir ...
- java中String,int,Integer,char、double类型转换
java中String,int,Integer,char.double类型转换----https://www.cnblogs.com/kangyu222/p/5866025.html
- js double 精度损失 bugs
js double 精度损失 bugs const arr = [ 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01 ]; // [ ...
随机推荐
- css3照片墙+曲线阴影
css3照片墙+曲线阴影 最近在学习jquery,晚上想复习下以前学过的知识,看到网上有关于css3照片墙的,感觉挺好玩的,就做了做.(以下图片均来自网络) 一.css3照片墙 html部分: < ...
- 【推荐】iOS汉字转拼音第三方库
PinYin4Objc是一个在git汉字转拼音的开源库,支持简体和繁体中文.效率POAPinyin等其他库要高,转换库也完整下面简单介绍 实现原理 使用unicode_to_hanyu_pinyin. ...
- IOS高级编程之一:多点触摸与手势验证
前段时间学习了IOS基础的一些控件的使用作为基础,现在开始学习一些高级编程的东西,手势处理器.文件I/O.定位.网络通信.多线程这些,分享一些学习的重点,还是很实用的. 今天就先介绍个简单点得,手势处 ...
- 奥特曼小分队之四(Work Breakdown Structure)
写在前面的话:游戏介绍http://www.cnblogs.com/atmxfd/p/5415107.html 需求 我们的游戏是一款基于局域网的游戏,用户只需将服务端和客户端置于同一局域网下即可使用 ...
- 介绍两种风格的URL
两种风格的“动态资源”——统一资源定位符(Uniform Resource Lactor,URL) 当前互联网上主要有两种主要风格的URL: 第一种直接在URL中知名文件(比如xxx.php,xxx. ...
- linux下基本命令总结
基本linux命令的使用方法及实例,总结一些常用的命令: 一.创建文件和目录命令:mkdir touch(vim用得较多) 1)mdkir创建一个目录,创建多目录格式 mkdir –p a/{a,b ...
- [python学习笔记]Day3
函数 如: def is_leapyear(year): if (year%4 == 0 and year%100 != 0) or (year%400 == 0): return True else ...
- ahjesus C# 4.0 Parallel 并行运算
Parallel.For - for 循环的并行运算 Parallel.ForEach - foreach 循环的并行运算 Parallel.Invoke - 并行调用多个任务 Task - 任务,基 ...
- html button自动提交表单问题
在ie中,button默认的type是button,而其他浏览器和W3C标准中button默认的属性都是submit,所以在chrome中,需要使用<button type="butt ...
- 「C语言」int main还是void main?
从大一入学刚接触C到现在已满7个月了,虽然刚开始就知道```int main```才是标准的写法,但一直没有深刻理解为什么不能用```void main```而必须使用```int main```. ...