为什么要使用 bigdecimal?

借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。

BigDecimal简介

BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负scale 次幂。因此,BigDecimal表示的数值是(unscaledValue × 10-scale)。

构造方法

BigDecimal aString = new BigDecimal("1.22");

BigDecimal aDouble = new BigDecimal(1.22);

BigDecimal bDouble = BigDecimal.valueOf(1.22);

System.out.println(aString);

System.out.println(aDouble);

System.out.println(bDouble);

Result:

1.22

1.2199999999999999733546474089962430298328399658203125

1.22

源码分析:

BigDecimal.valueOf(1.22) 构造方法,其实是调用 构造方法BigDecimal(String val);

Jdk中有说明:

1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。

3、当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。

在数据量精度不是很大的情况下,通过decimal输出正确的结果。(保留三位小数)

BigDecimal aString = new BigDecimal("1.22");

BigDecimal aDouble = new BigDecimal(1.22);

BigDecimal aValue = BigDecimal.valueOf(1.22);

System.out.println(new DecimalFormat("0.000").format(aString));

System.out.println(new DecimalFormat("0.000").format(aDouble));

System.out.println(new DecimalFormat("0.000").format(aValue));

Result:

1.220

1.220

1.220

原因分析:DecimalFormat做用了自己的舍余规则

总结

(1)商业计算使用BigDecimal。

(2)尽量使用参数类型为String的构造函数。

(3) BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。

(4)我们往往容易忽略JDK底层的一些实现细节,导致出现错误,需要多加注意。

问题一:BigDecimal的精度问题(StackOverflow上有个家伙问了相关的问题)

System.out.println(new BigDecimal(0.1).toString()); // 0.1000000000000000055511151231257827021181583404541015625
System.out.println(new BigDecimal("0.1").toString()); // 0.1
System.out.println(new BigDecimal(
Double.toString(0.1000000000000000055511151231257827021181583404541015625)).toString());// 0.1
System.out.println(new BigDecimal(Double.toString(0.1)).toString()); // 0.1

  

分析一下上面代码的问题(注释的内容表示此语句的输出)

第一行:事实上,由于二进制无法精确地表示十进制小数0.1,但是编译器读到字符串"0.1"之后,必须把它转成8个字节的double值,因此,编译器只能用一个最接近的值来代替0.1了,即0.1000000000000000055511151231257827021181583404541015625。因此,在运行时,传给BigDecimal构造函数的真正的数值是0.1000000000000000055511151231257827021181583404541015625。
第二行:BigDecimal能够正确地把字符串转化成真正精确的浮点数。
第三行:问题在于Double.toString会使用一定的精度来四舍五入double,然后再输出。会。Double.toString(0.1000000000000000055511151231257827021181583404541015625)输出的事实上是"0.1",因此生成的BigDecimal表示的数也是0.1。
第四行:基于前面的分析,事实上这一行代码等价于第三行
结论:
1.如果你希望BigDecimal能够精确地表示你希望的数值,那么一定要使用字符串来表示小数,并传递给BigDecimal的构造函数。
2.如果你使用Double.toString来把double转化字符串,然后调用BigDecimal(String),这个也是不靠谱的,它不一定按你的想法工作。
3.如果你不是很在乎是否完全精确地表示,并且使用了BigDecimal(double),那么要注意double本身的特例,double的规范本身定义了几个特殊的double值(Infinite,-Infinite,NaN),不要把这些值传给BigDecimal,否则会抛出异常。
问题二:把double强制转化成int,难道不是扔掉小数部分吗?
int x=(int)1023.99999999999999; // x=1024为什么?

原因还是在于二进制无法精确地表示某些十进制小数,因此1023.99999999999999在编译之后的double值变成了1024。

所以,把double强制转化成int确实是扔掉小数部分,但是你写在代码中的值,并不一定是编译器生成的真正的double值。
验证代码:
double d = 1023.99999999999999;
int x = (int) d;
System.out.println(new BigDecimal(d).toString()); // 1024
System.out.println(Long.toHexString(Double.doubleToRawLongBits(d))); // 4090000000000000
System.out.println(x); // 1024

前面提过BigDecimal可以精确地把double表示出来还记得吧。

我们也可以直接打印出d的二进制形式,根据IEEE 754的规定,我们可以算出0x4090000000000000=(1024)。
问题二:BigDecimal如何比较大小?
    public static void main(String[] args) {BigDecimal a = BigDecimal.valueOf(1.01);BigDecimal b = BigDecimal.valueOf(1.00000001);if (a.compareTo(b) == 0) {System.out.println("a==b");} else if (a.compareTo(b) == 1) {System.out.println("a>b");} else if (a.compareTo(b) == -1) {System.out.println("a<b");}}

参考:

http://www.cnblogs.com/mingforyou/p/3344489.html

http://stackoverflow.com/questions/8073912/why-do-we-need-to-convert-the-double-into-a-string-before-we-can-convert-it-int

http://www.cnblogs.com/linjiqin/p/3413894.html

转载于:https://www.cnblogs.com/chihirotan/p/5857827.html

Java 中浮点数---------BigDecimal和double(初探)相关推荐

  1. Java中的BigDecimal类你真的了解吗?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:HikariCP www.jianshu.com/p/c81 ...

  2. Java 中的 BigDecimal 类你了解多少?

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:什么?你还在使用fastjson,性能太差了个人原创+1博客:点击前往,查看更多 作者:HikariCP 链接: ...

  3. 后端:Java中的BigDecimal类你了解多少?

    我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题.如下一段代码: System.out.println(0.05 + 0.01); System.out.println(1.0 - 0.42) ...

  4. Java中的BigDecimal类你了解多少?

    点击上方"IT牧场",选择"设为星标"技术干货每日送达! 来源:https://urlify.cn/naiEva 前言 我们都知道浮点型变量在进行计算的时候会出 ...

  5. Java 中的 BigDecimal,你真的会用吗?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | LanceToBigData 来源 | cnb ...

  6. Java中的BigDecimal,你真的会用吗?

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 每天凌晨00点00分, 第一时间与你相约 每日英文 Forgetting someone doesn't mean never ...

  7. Java中浮点数的表示方法

    Java中浮点数的表示方法 Java中浮点数的表示方法 1.计算机中的表示方法 2.具体分析表示方法 小结 3.移位存储 小结 1.计算机中的表示方法 对于float来说,4个字节,32位,0-22位 ...

  8. 计算价格, java中浮点数精度丢失的解决方案

    计算价格, java中浮点数精度丢失的解决方案 转载于:https://www.cnblogs.com/gloryhope/p/9896719.html

  9. c语言对浮点数的处理默认是double吗,C语言中浮点数float和double输出的问题

    C语言中浮点数float和double输出的问题 关注:260  答案:6  信息版本:手机版 解决时间 2019-01-12 07:33 斑駁影 2019-01-11 09:20 #includev ...

最新文章

  1. 网站性能调优开发工具: Lighthouse, Puppeteer 以及进阶部分丨 Google 开发者大会 2018...
  2. abap submit 的使用方法 不同情况
  3. 新建centos6虚拟机黑屏_虚拟机centos无法进去选择系统界面,也就是开机过bios就黑屏解决方案...
  4. 转:[C#]获取某年指定周的开始日期和结束日期的通用方法
  5. C# HttpWebRequest 方式提交数据,参数为普通键值对
  6. Spring MVC Boot Cloud 技术教程汇总
  7. LeetCode 226. 翻转二叉树(DFS BFS)
  8. android view显示隐藏动画效果,Android 根据手势顶部View自动展示与隐藏效果
  9. STM32F103单片机RTC实时时钟的使用
  10. java.util.ConcurrentModificationException 异常问题详解
  11. elementUI 下拉框隐藏时触发相关事件(下拉框下拉显示时不触发)
  12. c++——block_type_is_valid怎么解决
  13. docker镜像加速器
  14. 服务器上安装python anaconda最简明教程
  15. 人工智能学习——模糊控制
  16. Flex Builder 3 正版注册码及破解版方法
  17. 【自用】网页设计与制作教程 笔记
  18. 微信小程序分享二维码扫码进入如何获取参数,小程序码进入参数为啥拿不到;
  19. 百度BAE部署微信开发环境
  20. 5分钟搞定各类USB转serial串口驱动,最简单的方法

热门文章

  1. linux下wget命令(下载文件命令)
  2. Android activity之间的滑入切换
  3. 老弟,Redis 6.0 除了多线程,别忘了这个牛逼特性!
  4. 好文推荐 | MySQL binlog应用场景与原理深度剖析
  5. 这个开源项目帮你将Linux命令行一网打尽!
  6. Java管理Cookie增删改查操作
  7. Spring Cloud构建微服务架构:服务消费(Ribbon)【Dalston版】
  8. spring-session使用教程(一):redis共享session
  9. vue-cli-service 不是内部或者外部命令的解决办法(用了你就知道了)
  10. 响应格式html,设置响应格式的HTML邮件