浮点型 float和double类型的内存结构和精度问题
首先引用一个例子在java中可能你会遇到这样的问题:
例:0.99999999f==1f //true
0.9999999f==1f //false
这是超出精度造成的,为了知道为什么会造成这样的问题,首先了解一下float和double的内存结构
1.内存结构
float和double的范围是由指数的位数来决定的。
float的指数位有8位,而double的指数位有11位,分布如下:

float:1bit(符号位) 8bits(指数位) 23bits(尾数位)

double:1bit(符号位) 11bits(指数位) 52bits(尾数位)
float的指数范围为-128~+127,而double的指数范围为-1024~+1023,并且指数位是按补码的形式来划分的。
float的范围为-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38;double的范围为-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308。
Float.MAX_VALUE = 3.4028235E38
Float.MIN_VALUE = 1.4E-45
Double.MAX_VALUE = 1.7976931348623157E308
Double.MIN_VALUE = 4.9E-324
public class DoubleBinaryTest {
public static void main(String[] args) {
/*
* float类型的二进制存储是4个字节32位
* 1位符号位 8位阶码位 23位尾数位
* 1、将十进制40.125转换为整数部分和小数部分的二进制 101000.001
* 2、转换为 1.01000001 * 2的5次方
* 3、符号位为0
* 4、指数e=阶码E-127,阶码位,5=E-127,E=132,转换为二进制为10000100
* 5、小数位,0100000100000000000000
* 6、40.125f的二进制表现形式为01000010 00100000 10000000 00000000
*/
float f = 40.125f;
int i = Float.floatToIntBits(f);//Float类的静态方法,以int类型的方式返回这个小数的二进制形式
System.out.println(Integer.toBinaryString(i));
/*
* double类型的二进制存储是8个字节64位
* 1位符号位 ,11位的阶码,52位的尾数位
* 和float同理,只是阶码位不一样
* E-1023=5,E=1028,转换为10000000100
* 40.125d的二进制为 01000000 01000100 00010000 00000000 00000000 00000000 00000000 00000000
*/
double d = 40.125;
long l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));
/*
* float类型的二进制存储是4个字节32位
* 1位符号位 8位阶码位 23位尾数位
* 1、将十进制-40.125转换为整数部分和小数部分的二进制 -101000.001
* 2、转换为 -1.01000001 * 2的5次方
* 3、符号位为1
* 4、阶码位,E-127=5,E=132,转换为二进制为10000100
* 5、小数位,0100000100000000000000
* 6、40.125f的二进制表现形式为11000010 00100000 10000000 00000000
*/
float f2 = -40.125f;
int i2 = Float.floatToIntBits(f2);
System.out.println(Integer.toBinaryString(i2));
}
}
清楚了浮点型型的存储方式后,再来看例题在计算机中怎么存储的
(1)0.99999999f 化为二进制表示:0011 1111 1000 0000 0000 0000 0000 0000
(2)0.9999999f 化为二进制表示:0011 1111 0111 1111 1111 1111 1111 1110
(3)1f 化为二进制表示:0011 1111 1000 0000 0000 0000 0000 0000
通过计算后发现(2)和(3)我们可以根据上述方式计算出来二进制表示方式,但是(1)话就和我们算出来的不一样了,为什么会这样呢,接下来我们再了解一下浮点型的精度。
2.关于精度:
float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
float:2^23 = 8388608,一共七位,由于最左为1的一位省略了,这意味着最多能表示8位数: 2*8388608 = 16777216 。有8位有效数字,但绝对能保证的为7位,也即float的精度为7~8位有效数字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度为16~17位。
我们可以用下面这段代码检验一下:
- float f1 = 16777215f;
- for (int i = 0; i < 10; i++) {
- System.out.println(f1);
- f1++;
- }
对于小数来说,更容易会因为精度而出错误。
- float f = 2.2f;
- double d = (double) f;
- System.out.println(d);
- f = 2.25f;
- d = (double) f;
- System.out.println(d);
输出结果为:
2.200000047683716
2.25
对于这种简单数的输出结果会是这样,是简直无法忍受的。
其实通过上面关于两种存储结果的介绍,我们已经大概能找到答案。首先我们看看2.25的单精度存储方式,转化为2进制位便是10.01,整理为1.001*2 很简单
于是我们可以写出2.25的内存分布:
符号位为:0
指数为1,用补码表示 0000 0001,转为移码就是1000 0001。
尾数位为0010 0000 0000 0000 0000 000
而2.25的双精度表示为:0 100 0000 0001 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,这样2.25在进行强制转换的时候,数值是不会变的,而我们再看看2.2呢,2.2用科学计数法表示应该为:将十进制的小数转换为二进制的小数的方法为将小数*2,取整数部分,所以0.282=0.4,所以二进制小数第一位为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,这样永远也不可能乘到=1.0,得到的二进制是一个无限循环的排列 00110011001100110011... ,对于单精度数据来说,尾数只能表示24bit的精度,所以2.2的float存储为:

但是这样存储方式,换算成十进制的值,却不会是2.2的,因为十进制在转换为二进制的时候可能会不准确,如2.2,而double类型的数据也存在同样的问题,所以在浮点数表示中会产生些许的误差,在单精度转换为双精度的时候,也会存在误差的问题,如下面的代码,输出结果就不一样:
- float f = 2.2f;
- double d = (double) f;
- System.out.println(f);
- System.out.println(d);
对于能够用二进制表示的十进制数据,如2.25,这个误差就会不存在,所以会出现上面比较奇怪的输出结果。
3.关于BigDecimal
再如0.1f,java里面32位的float对0.1在内存当中的表示是不精确的,原理同上,
这个误差对我们生活小打小闹没啥影响,但是对科学计算和银行这样的应用或者领域是致命的,因此要用于银行以及科学计算会用java.math.BigDecimal提高精度,否则后果极其严重。
JAVA的浮点型是会丢失精度的,所以要用精确计算就用BigDecimal。
首先我们先来看如下代码示例:

1 public class Test_1 {
2 public static void main(String[] args) {
3 System.out.println(0.06+0.01);
4 System.out.println(1.0-0.42);
5 System.out.println(4.015*100);
6 System.out.println(303.1/1000);
7 }
8
9 }

运行结果如下。
0.06999999999999999
0.5800000000000001
401.49999999999994
0.30310000000000004
你认为你看错了,但结果却是是这样的。问题在哪里呢?原因在于我们的计算机是二进制的。浮点数没有办法是用二进制进行精确表示。我们的CPU表示浮点数由两个部分组成:指数和尾数,这样的表示方法一般都会失去一定的精确度,有些浮点数运算也会产生一定的误差。如:2.4的二进制表示并非就是精确的2.4。反而最为接近的二进制表示是 2.3999999999999999。浮点数的值实际上是由一个特定的数学公式计算得到的。
其实java的float只能用来进行科学计算或工程计算,在大多数的商业计算中,一般采用java.math.BigDecimal类来进行精确计算。
在使用BigDecimal类来进行计算的时候,主要分为以下步骤:
1、用float或者double变量构建BigDecimal对象。
2、通过调用BigDecimal的加,减,乘,除等相应的方法进行算术运算。
3、把BigDecimal对象转换成float,double,int等类型。
一般来说,可以使用BigDecimal的构造方法或者静态方法的valueOf()方法把基本类型的变量构建成BigDecimal对象。
1 BigDecimal b1 = new BigDecimal(Double.toString(0.48));
2 BigDecimal b2 = BigDecimal.valueOf(0.48);
对于常用的加,减,乘,除,BigDecimal类提供了相应的成员方法。
1 public BigDecimal add(BigDecimal value); //加法
2 public BigDecimal subtract(BigDecimal value); //减法
3 public BigDecimal multiply(BigDecimal value); //乘法
4 public BigDecimal divide(BigDecimal value); //除法
进行相应的计算后,我们可能需要将BigDecimal对象转换成相应的基本数据类型的变量,可以使用floatValue(),doubleValue()等方法。
下面是一个工具类,该工具类提供加,减,乘,除运算。

1 public class Arith {
2 /**
3 * 提供精确加法计算的add方法
4 * @param value1 被加数
5 * @param value2 加数
6 * @return 两个参数的和
7 */
8 public static double add(double value1,double value2){
9 BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
10 BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
11 return b1.add(b2).doubleValue();
12 }
13
14 /**
15 * 提供精确减法运算的sub方法
16 * @param value1 被减数
17 * @param value2 减数
18 * @return 两个参数的差
19 */
20 public static double sub(double value1,double value2){
21 BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
22 BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
23 return b1.subtract(b2).doubleValue();
24 }
25
26 /**
27 * 提供精确乘法运算的mul方法
28 * @param value1 被乘数
29 * @param value2 乘数
30 * @return 两个参数的积
31 */
32 public static double mul(double value1,double value2){
33 BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
34 BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
35 return b1.multiply(b2).doubleValue();
36 }
37
38 /**
39 * 提供精确的除法运算方法div
40 * @param value1 被除数
41 * @param value2 除数
42 * @param scale 精确范围
43 * @return 两个参数的商
44 * @throws IllegalAccessException
45 */
46 public static double div(double value1,double value2,int scale) throws IllegalAccessException{
47 //如果精确范围小于0,抛出异常信息
48 if(scale<0){
49 throw new IllegalAccessException("精确度不能小于0");
50 }
51 BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
52 BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
53 return b1.divide(b2, scale).doubleValue();
54 }
55 }

另外需要注意的一点是:double小数转bigdecimal后四舍五入计算有误差
案例:
double g= 12.35;
BigDecimal bigG=new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); //期望得到12.4
System.out.println("test G:"+bigG.doubleValue());
test G:12.3
原因:
定义double g= 12.35; 而在计算机中二进制表示可能这是样:定义了一个g=12.34444444444444449,
new BigDecimal(g) g还是12.34444444444444449
new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); 得到12.3
正确的定义方式是使用字符串构造函数:
new BigDecimal("12.35").setScale(1, BigDecimal.ROUND_HALF_UP)
setScale(1)表示保留一位小数,默认用四舍五入方式
setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3
setScale(1,BigDecimal.ROUND_UP)进位处理,2.35变成2.4
setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4
参考:
http://blog.csdn.net/zq602316498/article/details/41148063
http://bbs.csdn.net/topics/370105966
Java浮点数float和double精确计算的精度误差问题总结
浮点型 float和double类型的内存结构和精度问题的更多相关文章
- C语言中float,double类型,在内存中的结构(存储方式)
C语言中float,double类型,在内存中的结构(存储方式)从存储结构和算法上来讲,double和float是一样的,不一样的地方仅仅是float是32位的,double是64位的,所以doubl ...
- 不要在精确计算中使用float和double类型
http://blog.csdn.net/androiddevelop/article/details/8478879 一 问题描述 float和double类型不能用于精确计算,其主要目的是为了科 ...
- Effective Java 第三版——60. 需要精确的结果时避免使用float和double类型
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Java语言中:float、double数据类型在内存中是如何存储的
引用参考 https://www.cnblogs.com/chenmingjun/p/8415464.html#4291528 https://blog.csdn.net/yansmile1/arti ...
- Sql的decimal、float、double类型的区别
三者的区别介绍 float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位) double:双精度实型,含字节数为8,64bit数值范围-1.7E308~1.7E ...
- 14、Java中用浮点型数据Float和Double进行精确计算时的精度问题
一.浮点计算中发生精度丢失 大概很多有编程经验的朋友都对这个问题不陌生了:无论你使用的是什么编程语言,在使用浮点型数据进行精确计算时,你都有可能遇到计算结果出错的情况.来看下面的例子. // 这是一个 ...
- Delphi XE7中各种字符串与字符类型的内存结构
1. ShortString 类型 定义:type ShortString = string[255]; 内存结构与大小:ShortString 是每个字符为单字节的字符串.ShortString 的 ...
- float和double类型的存储方式
Float double 类型在计算机的存储方式 计算机中只认识10的二进制数,那么该如何存储小数呢? 那么我们先看Floa类型: Float在计算机(32位)中是4个字节的,具体地:第一位为符号位0 ...
- 关于float和double类型能表示的数据范围和精度分析
来自教材<计算机组成原理>p16 float:6--7位 double:15--16位 意思就是double类型的数据,你确实能表达出很大的数字,但是其只有15位是精确的. 1.计算机中, ...
随机推荐
- (十九)TableView的点击监听和数据刷新(Alert的多种样式) -tag传值的技巧
要实现监听,要使用代理,控制器要成为TableView的代理. 注意下面的方式是代理方法: - (void)tableView:(UITableView *)tableView didSelectRo ...
- Java深拷贝浅拷贝
首先,Java中常用的拷贝操作有三个,operator = .拷贝构造函数 和 clone()方法.由于Java不支持运算符重载,我们无法在自己的自定义类型中定义operator=.拷贝构造函数大家应 ...
- 【Android 应用开发】Android开发技巧--Application, ListView排列,格式化浮点数,string.xml占位符,动态引用图片
一. Application用途 1. Application用途 创建Application时机 : Application在启动的时候会调用Application无参的构造方法创建实例; Appl ...
- Android监听电池状态
监听电池状态只需要接收Intent.ACTION_BATTERY_CHANGED的广播即可,当电池状态发生变化时会发出广播. 1.运行状态如下图: (1)连接USB时的状态 (2)断开USB时的状态 ...
- Advanced Pricing - How to source Pricing Attributes using QP_CUSTOM_SOURCE.Get_Custom_Attribute_Valu
详细内容需要参考文档:Oracle 11i Advanced Pricing-Don't Customize, Extend! utl:http://blog.csdn.net/cai_xingyun ...
- windows下c语言获取程序当前的执行目录,读文件的代码片
代码如下: #include "stdafx.h" #include "stdlib.h" #include <direct.h> #include ...
- C语言算法--统计字符串中单词的个数
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(void) { int le ...
- Java I/O最简单的几个类
今天把I/O中最简单的几个类整理了一下,之所以整理最简单的,是因为这样会让我更加快速方便的理顺这里面的东西,以前每一次用的时候都要先百度一下,觉得很烦. 首先需要先看一下Read,Write和Stre ...
- Java工程师成神之路思维导图
前面看Hollis的微信公众号更新了Java工程师成神之路的文档,感觉里面的内容清晰.齐全,可以用来审视自己,也能够知道自己在那些方面可以继续前行,想着有时间把它画下来,画下来之后分享出来. 主要内容 ...
- permutations(全排列)
Given a collection of distinct numbers, return all possible permutations. For example,[1,2,3] have t ...