来自http://blog.sina.com.cn/s/blog_827d041701017ctm.html
问题提出:12.0f-11.9f=0.10000038,"减不尽"为什么?
来自MSDN的解释:
http://msdn.microsoft.com/zh-cn/c151dt3s.aspx
为何浮点数可能丢失精度浮点十进制值通常没有完全相同的二进制表示形式。 这是 CPU所采用的浮点数据表示形式的副作用。为此,可能会经历一些精度丢失,并且一些浮点运算可能会产生意外的结果。
导致此行为的原因是下面之一:
十进制数的二进制表示形式可能不精确。
使用的数字之间类型不匹配(例如,混合使用浮点型和双精度型)。
为解决此行为,大多数程序员或是确保值比需要的大或者小,或是获取并使用可以维护精度的二进制编码的十进制 (BCD)库。
现在我们就详细剖析一下浮点型运算为什么会造成精度丢失?
1、小数的二进制表示问题
2、float型在内存中的存储
float内存存储结构
入第22到第0位。
位,得到0000011,各位取反,得到1111100,放入第29到第23位。
3、浮点型的减法运算
(1)0操作数的检查;
(2)比较阶码(指数位)大小并完成对阶;
(3)尾数(有效数位)进行加或减运算;
(4)结果规格化并进行舍入处理。
4、计算12.0f-11.9f
详细的分析
由于对float或double的使用不当,可能会出现精度丢失的问题。问题大概情况可以通过如下代码理解:
view plaincopy toclipboardprint?
public class FloatDoubleTest{
public static void main(String[] args){
float f = 20014999;
double d = f;
double d2 = 20014999;
System.out.println("f=" +f);
System.out.println("d=" +d);
System.out.println("d2=" +d2);
}
}
public class FloatDoubleTest {
public static void main(String[] args) {
float f = 20014999;
double d = f;
double d2 = 20014999;
System.out.println("f=" + f);
System.out.println("d=" + d);
System.out.println("d2=" + d2);
}
}
得到的结果如下:
f=2.0015E7
d=2.0015E7
d2=2.0014999E7
从输出结果可以看出double可以正确的表示20014999 ,而float 没有办法表示20014999,得到的只是一个近似值。这样的结果很让人讶异。20014999这么小的数字在float下没办法表示。于是带着这个问题,做了一次关于float和double学习,做个简单分享,希望有助于大家对java浮点数的理解。
关于 java 的 float 和double
Java语言支持两种基本的浮点类型: float 和 double 。java 的浮点类型都依据 IEEE 754 标准。IEEE 754定义了32 位和 64 位双精度两种浮点二进制小数标准。
IEEE 754用科学记数法以底数为 2 的小数来表示浮点数。32 位浮点数用 1 位表示数字的符号,用 8 位来表示指数,用 23位来表示尾数,即小数部分。作为有符号整数的指数可以有正负之分。小数部分用二进制(底数 2 )小数来表示。对于64 位双精度浮点数,用1 位表示数字的符号,用 11 位表示指数,52 位表示尾数。如下两个图来表示:
float(32位):
double(64位):
都是分为三个部分:
(1) 一个单独的符号位s直接编码符号s 。
(2)k 位的幂指数E,移码表示。
(3)n位的小数,原码表示。
那么 20014999 为什么用float 没有办法正确表示?
结合float和double的表示方法,通过分析 20014999的二进制表示就可以知道答案了。
以下程序可以得出 20014999在 double 和 float 下的二进制表示方式。
view plaincopy toclipboardprint?
public class FloatDoubleTest3{
public static void main(String[] args){
double d = 8;
long l =Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));
float f = 8;
int i =Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(i));
}
}
public class FloatDoubleTest3 {
public static void main(String[] args) {
double d = 8;
long l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));
float f = 8;
int i = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(i));
}
}
输出结果如下:
Double:100000101110011000101100
Float:100101110011000101100111
对于输出结果分析如下。对于都不double 的二进制左边补上符号位 0 刚好可以得到 64位的二进制数。根据double的表示法,分为符号数、幂指数和尾数三个部分如下:
0 10000010111001100010110011110010111
对于 float 左边补上符号位 0刚好可以得到 32 位的二进制数。 根据float的表示法, 也分为符号数、幂指数和尾数三个部分如下:
0 1001011100110001011001111001100
绿色部分是符号位,红色部分是幂指数,蓝色部分是尾数。
对比可以得出:符号位都是 0,幂指数为移码表示,两者刚好也相等。唯一不同的是尾数。
在 double 的尾数为:001100010110011110010111
而在 float 下面尾数为:00110001011001111001100 ,共 23 位。
为什么会这样?原因很明显,因为float尾数 最多只能表示 23 位,所以 24 位的 001100010110011110010111也就是说 20014999 虽然是在float的表示范围之内,但 在 IEEE 754 的 float 表示法精度长度没有办法表示出20014999,而只能通过四舍五入得到一个近似值。