浮点数会有精度损失这个在上大学的时候就已经被告知,但是至今完全没有想明白其中的原由,老师讲的时候也是一笔带过的,自己也没有好好琢磨。终于在工作的时候碰到了,于是google了一番。

问题:

  对两个double类型的值进行运算,有时会出现结果值异常的问题。比如:

1     System.out.println(19.99+20);
2 System.out.println(1.0-0.66);
3 System.out.println(0.033*100);
4 System.out.println(12.3/100);

输出:

39.989999999999995
0.33999999999999997
3.3000000000000003
0.12300000000000001

  Java中的简单浮点数类型float和double不能够精确运算。这个问题其实不是JAVA的bug,因为计算机本身是二进制的,而浮点数实际上只是个近似值,所以从二进制转化为十进制浮点数时,精度容易丢失,导致精度下降。

关于精度损失的原理可以很简单的讲,首先一个正整数在计算机中表示使用01010形式表示的,浮点数也不例外。

  比如11,11除以2等于5余1

       5除以2等于2余1

       2除以2等于1余0

       1除以2等于0余1

  所以11二进制表示为:1011.

  double类型占8个字节,64位,第1位为符号位,后面11位是指数部分,剩余部分是有效数字。

  正整数除以2肯定会有个尽头的,之后二进制还原成十进制只需要乘以2即可。

  举个例子:0.99用的有效数字部分,

       0.99 * 2 = 1+0.98 --> 1

       0.98 * 2 = 1+0.96 --> 1

       0.96 * 2 = 1+0.92 -- >1

       0.92 * 2 = 1+0.84 -- >1

         ...............

  这样周而复始是没法有尽头的,而double有效数字有限,所以必定会有损失,所以二进制无法准确表示0.99,就像十进制无法准确表示1/3一样。

解决办法:

  在《Effective Java》中提到一个原则,那就是float和double只能用来作科学计算或者是工程计算,但在商业计算中我们要用java.math.BigDecimal,通过使用BigDecimal类可以解决上述问题,首先需要注意的是,直接使用字符串来构造BigDecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造BigDecimal依然会有精度损失,所以我觉得这种解决方法就是在使用中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失。

1. 相加

 /**
* 相加
* @param double1
* @param double2
* @return
*/
public static double add(String doubleValA, String doubleValB) {
BigDecimal a2 = new BigDecimal(doubleValA);
BigDecimal b2 = new BigDecimal(doubleValB);
return a2.add(b2).doubleValue();
}

2. 相减

 /**
* 相减
* @param double1
* @param double2
* @return
*/
public static double sub(String doubleValA, String doubleValB) {
BigDecimal a2 = new BigDecimal(doubleValA);
BigDecimal b2 = new BigDecimal(doubleValB);
return a2.subtract(b2).doubleValue();
}

3. 相乘

 /**
* 相乘
* @param double1
* @param double2
* @return
*/
public static double mul(String doubleValA, String doubleValB) {
BigDecimal a2 = new BigDecimal(doubleValA);
BigDecimal b2 = new BigDecimal(doubleValB);
return a2.multiply(b2).doubleValue();
}

4. 相除

 /**
* 相除
* @param double1
* @param double2
* @param scale 除不尽时指定精度
* @return
*/
public static double div(String doubleValA, String doubleValB, int scale) {
BigDecimal a2 = new BigDecimal(doubleValA);
BigDecimal b2 = new BigDecimal(doubleValB);
return a2.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}

5. 主函数调用

 public static void main(String[] args) {
String doubleValA = "3.14159267";
String doubleValB = "2.358";
System.out.println("add:" + add(doubleValA, doubleValB));
System.out.println("sub:" + sub(doubleValA, doubleValB));
System.out.println("mul:" + mul(doubleValA, doubleValB));
System.out.println("div:" + div(doubleValA, doubleValB, 8));
}

结果展示如下所示:

add:5.49959267
  sub:0.78359267
  mul:7.40787551586
  div:1.33231241

所以最好的方法是完全抛弃double,用string和java.math.BigDecimal。

  java遵照IEEE制定的浮点数表示法来进行float,double运算。这种结构是一种科学计数法,用符号、指数和尾数来表示,底数定为2——即把一个浮点数表示为尾数乘以2的指数次方再添上符号。具体底层如何存储以及如何进行运行请继续关注我的博客,后续我会将详情总结好的。

JAVA浮点数计算精度损失底层原理与解决方案的更多相关文章

  1. jmeter 中 浮点数计算精度问题

    jmeter 中 浮点数计算精度问题解决方法: 编写 beanshell 时使用 java.math.BigDecimal 方法构造,使用 BigDecimal 并且一定要用 String 来够造. ...

  2. (Jquery)避免数据相加小数点后产生多位数和计算精度损失

    /** * 加法运算,避免数据相加小数点后产生多位数和计算精度损失. * * @param num1加数1 | num2加数2 */ function numAdd(num1, num2) { var ...

  3. 如何避开JavaScript浮点数计算精度问题(如0.1+0.2!==0.3)

    不知道大家在使用JS的过程中有没有发现某些浮点数运算的时候,得到的结果存在精度问题:比如0.1 + 0.2 = 0.30000000000000004以及7 * 0.8 = 5.60000000000 ...

  4. 关于js浮点数计算精度不准确问题的解决办法

    今天在计算商品价格的时候再次遇到js浮点数计算出现误差的问题,以前就一直碰到这个问题,都是简单的使用tofixed方法进行处理一下,这对于一个程序员来说是及其不严谨的.因此在网上收集了一些处理浮点数精 ...

  5. 学以致用:手把手教你撸一个工具库并打包发布,顺便解决JS浮点数计算精度问题

    本文讲解的是怎么实现一个工具库并打包发布到npm给大家使用.本文实现的工具是一个分数计算器,大家考虑如下情况: \[ \sqrt{(((\frac{1}{3}+3.5)*\frac{2}{9}-\fr ...

  6. Java I/O模型及其底层原理

    Java I/O是Java基础之一,在面试中也比较常见,在这里我们尝试通过这篇文章阐述Java I/O的基础概念,帮助大家更好的理解Java I/O. 在刚开始学习Java I/O时,我很迷惑,因为网 ...

  7. JavaScript数字计算精度丢失的问题和解决方案

    一.JS数字精度丢失的一些典型问题 1. 两个简单的浮点数相加:0.1 + 0.2 != 0.3 // true,下图是firebug的控制台截图: 看看java的计算结果:是不是让你很不能接受 再来 ...

  8. js 浮点数计算精度不准确问题

    或许很多人都遇到过,js 对小数的加.减.乘.除时经常得到一些奇怪的结果! 比如 :0.1 + 0.2 = 0.3  ? 这么一个简单的计算,当你用js 计算时会发现结果是:0.30000000000 ...

  9. 详解JAVA字符串类型switch的底层原理

    基础 我们现在使用的Java的版本,基本上是都支持String类型的.当然除了String类型,还有int.char.byte.short.enum等等也都是支持的.然而在其底部实现中,还是基于 整型 ...

随机推荐

  1. Python 3 生成手写体数字数据集

    0.引言 平时上网干啥的基本上都会接触验证码,或者在机器学习学习过程中,大家或许会接触过手写体识别/验证码识别之类问题,会用到手写体的数据集: 自己尝试写了一个生成手写体图片的python程序,在此分 ...

  2. vuex Loding加载..

    技术栈:vuex,mapActions, mapState 先在vuex的状态管理里定义好loding状态,以及加载文字 import Vue from 'vue'; import Vuex from ...

  3. Vuejs 安装与配置

    1.全局安装 vue-cli $ npm install --global vue-cli 2.创建一个基于 webpack 模板的新项目 $ vue init webpack my-project ...

  4. 要学的东西太多了,还想学习opencv

    资料先放这里,以后好好学 http://m.blog.csdn.net/column/details?alias=opencv-tutorial eclipse加载opencv库成功! B站视频教程资 ...

  5. 阿里java开发手册中命名规约解读之DO/BO/DTO/VO/AO

    前言 在阅读<阿里巴巴Java开发手册>时,看到命名规则中有这样一条 虽然知道这些是根据Java对象的角色所分配名称的后缀,但是没有弄清楚分别是什么意思,日常开发中也没有使用到. 网上查找 ...

  6. 国家语言,语言代码,locale id对应表

    国家语言,语言代码,locale id对应表.比如 en_US对应的id为1033, 中文的locale=zh_CN,id=2052. Locale Languagecode LCIDstring L ...

  7. ssm打印sql语句

    在mybatis配置文件中添加<setting name="logImpl" value="STDOUT_LOGGING"/> <?xml v ...

  8. Linux指令--ps

    Linux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信 ...

  9. MySQL查看和修改表的存储引擎

    1 查看系统支持的存储引擎 show engines; 2 查看表使用的存储引擎 两种方法: a.show table status from db_name where name='table_na ...

  10. css中 padding属性的数值赋予顺序为

    4种可能的情况,举例说明:padding:10px; 四个内边距都是10pxpadding:5px 10px; 上下5px 左右10pxpadding:5px 10px 15px; 上5px 右10p ...