转自http://www.cnblogs.com/yewsky/articles/1864934.html和http://blog.csdn.net/soft200816/article/details/7569722
为何浮点数可能丢失精度浮点十进制值通常没有完全相同的二进制表示形式。 这是 CPU 所采用的浮点数据表示形式的副作用。 为此,可能会经历一些精度丢失,并且一些浮点运算可能会产生意外的结果。
导致此行为的原因是下面之一:
十进制数的二进制表示形式可能不精确。
使用的数字之间类型不匹配(例如,混合使用浮点型和双精度型)。
为解决此行为,大多数程序员或是确保值比需要的大或者小,或是获取并使用可以维护精度的二进制编码的十进制 (BCD) 库。
现在我们就详细剖析一下浮点型运算为什么会造成精度丢失?
1、小数的二进制表示问题
首先我们要搞清楚下面两个问题:
(1) 十进制整数如何转化为二进制数
算法很简单。举个例子,11表示成二进制数:
11/2=5 余 1
5/2=2 余 1
2/2=1 余 0
1/2=0 余 1
0结束 11二进制表示为(从下往上):1011
这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,所有的整数除以2是不是一定能够最终得到0。换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?绝对不会,整数永远可以用二进制精确表示 ,但小数就不一定了。
(2) 十进制小数如何转化为二进制数
算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数
0.9*2=1.8 取整数部分 1
0.8(1.8的小数部分)*2=1.6 取整数部分 1
0.6*2=1.2 取整数部分 1
0.2*2=0.4 取整数部分 0
0.4*2=0.8 取整数部分 0
0.8*2=1.6 取整数部分 1
0.6*2=1.2 取整数部分 0
......... 0.9二进制表示为(从上往下): 1100100100100......
注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。
2、 float型在内存中的存储
众所周知、 Java 的float型在内存中占4个字节。float的32个二进制位结构如下
float内存存储结构
4bytes 31 30 29----23 22----0
表示 实数符号位 指数符号位 指数位 有效数位
其中符号位1表示正,0表示负。有效位数位24位,其中一位是实数符号位。
将一个float型转化为内存存储格式的步骤为:
(1)先将这个实数的绝对值化为二进制格式,注意实数的整数部分和小数部分的二进制方法在上面已经探讨过了。
(2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。
(3)从小数点右边第一位开始数出二十三位数字放入第22到第0位。
(4)如果实数是正的,则在第31位放入“0”,否则放入“1”。
(5)如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。
(6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。
举例说明: 11.9的内存存储格式
(1) 将11.9化为二进制后大约是" 1011. 1110011001100110011001100..."。
(2) 将小数点左移三位到第一个有效位右侧: "1. 011 11100110011001100110 "。 保证有效位数24位,右侧多余的截取(误差在这里产生了 )。
(3) 这已经有了二十四位有效数字,将最左边一位“1”去掉,得到“ 011 11100110011001100110 ”共23bit。将它放入float存储结构的第22到第0位。
(4) 因为11.9是正数,因此在第31位实数符号位放入“0”。
(5) 由于我们把小数点左移,因此在第30位指数符号位放入“1”。
(6) 因为我们是把小数点左移3位,因此将3减去1得2,化为二进制,并补足7位得到0000010,放入第29到第23位。
最后表示11.9为: 0 1 0000010 011 11100110011001100110
再举一个例子:0.2356的内存存储格式
(1)将0.2356化为二进制后大约是0.00111100010100000100100000。
(2)将小数点右移三位得到1.11100010100000100100000。
(3)从小数点右边数出二十三位有效数字,即11100010100000100100000放
入第22到第0位。
(4)由于0.2356是正的,所以在第31位放入“0”。
(5)由于我们把小数点右移了,所以在第30位放入“0”。
(6)因为小数点被右移了3位,所以将3化为二进制,在左边补“0”补足七
位,得到0000011,各位取反,得到1111100,放入第29到第23位。
最后表示0.2356为:0 0 1111100 11100010100000100100000
将一个内存存储的float二进制格式转化为十进制的步骤:
(1)将第22位到第0位的二进制数写出来,在最左边补一位“1”,得到二十四位有效数字。将小数点点在最左边那个“1”的右边。
(2)取出第29到第23位所表示的值n。当30位是“0”时将n各位求反。当30位是“1”时将n增1。
(3)将小数点左移n位(当30位是“0”时)或右移n位(当30位是“1”时),得到一个二进制表示的实数。
(4)将这个二进制实数化为十进制,并根据第31位是“0”还是“1”加上正号或负号即可。
3、浮点型的减法运算
浮点加减运算过程比定点运算过程复杂。完成浮点加减运算的操作过程大体分为四步:
(1) 0操作数的检查;
如果判断两个需要加减的浮点数有一个为0,即可得知运算结果而没有必要再进行有序的一些列操作。
(2) 比较阶码(指数位)大小并完成对阶;
两浮点数进行加减,首先要看两数的 指数位 是否相同,即小数点位置是否对齐。若两数 指数位 相同,表示小数点是对齐的,就可以进行尾数的加减运算。反之,若两数阶码不同,表示小数点位置没有对齐,此时必须使两数的阶码相同,这个过程叫做对阶 。
如何对 阶(假设两浮点数的指数位为 Ex 和 Ey ):
通过尾数的移位以改变 Ex 或 Ey ,使之相等。 由于浮点表示的数多是规格化的,尾数左移会引起最高有位的丢失,造成很大误差;而尾数右移虽引起最低有效位的丢失,但造成的误差较小,因此,对阶操作规定使尾数右移,尾数右移后使阶码作相应增加,其数值保持不变。很显然,一个增加后的阶码与另一个相等,所增加的阶码一定是小阶。因此在对阶时,总是使小阶向大阶看齐 ,即小阶的尾数向右移位 ( 相当于小数点左移 ) ,每右移一位,其阶码加 1 ,直到两数的阶码相等为止,右移的位数等于阶差 △ E 。
(3) 尾数(有效数位)进行加或减运算;
对阶完毕后就可 有效数位 求和。 不论是加法运算还是减法运算,都按加法进行操作,其方法与定点加减运算完全一样。
(4) 结果规格化并进行舍入处理。
略
4、 计算12.0f-11.9f
12.0f 的内存存储格式为: 0 1 0000010 10000000000000000000000
11.9f 的内存存储格式为: 0 1 0000010 011 11100110011001100110
可见两数的指数位完全相同,只要对有效数位进行减法即可。
12.0f-11.9f 结果: 0 1 0000010 00000011001100110011010
将结果还原为十进制为: 0.000 11001100110011010= 0.10000038
详细的分析
由于对float或double 的使用不当,可能会出现精度丢失的问题。问题大概情况可以通过如下代码理解:
view plaincopy to clipboardprint?
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 to clipboardprint?
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:100000101110011000101100111100101110000000000000000000000000000
Float:1001011100110001011001111001100
对于输出结果分析如下。对于都不 double 的二进制左边补上符号位 0 刚好可以得到 64 位的二进制数。根据double的表示法,分为符号数、幂指数和尾数三个部分如下:
0 10000010111 0011000101100111100101110000000000000000000000000000
对于 float 左边补上符号位 0 刚好可以得到 32 位的二进制数。 根据float的表示法, 也分为 符号数、幂指数和尾数三个部分如下 :
0 10010111 00110001011001111001100
绿色部分是符号位,红色部分是幂指数,蓝色部分是尾数。
对比可以得出:符号位都是 0 ,幂指数为移码表示,两者刚好也相等。唯一不同的是尾数。
在 double 的尾数为: 001100010110011110010111 0000000000000000000000000000 ,省略后面的零,至少需要24位才能正确表示 。
而在 float 下面尾数为: 00110001011001111001100 ,共 23 位。
为什么会这样?原因很明显,因为 float尾数 最多只能表示 23 位,所以 24 位的 001100010110011110010111 在 float 下面经过四舍五入变成了 23 位的 00110001011001111001100 。所以 20014999 在 float 下面变成了 20015000 。也就是说 20014999 虽然是在float的表示范围之内,但 在 IEEE 754 的 float 表示法精度长度没有办法表示出 20014999 ,而只能通过四舍五入得到一个近似值。
计算机系统采纳了所谓的浮点数表达方式。这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa也叫有效数字 ),一个基数(Base),一个指数(Exponent)以及
一个表示正负的符号来表达实数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。
当一个浮点数的尾数为0,不论其阶码为何值,该浮点数的值都为0。当阶码的值为它能表示的最小一个值或更小的值时,不管其尾数为何值,计算机都把该浮点数看成零值,通常称
其为机器零,此时该浮点数的所有各位(包括阶码位和尾数位)都清为0值。
Java 平台上的浮点数类型 float 和 double 采纳了 IEEE 754 标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式。
在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指
数和尾数。这样,通过尾数和可以调节的指数就可以表达给定的数值了。具体的格式参见下面的图例:
在上面的图例中,第一个域为符号域。其中 0 表示数值为正数,而 1 则表示负数。
第二个域为指数域,对应于我们之前介绍的二进制科学计数法中的指数部分。其中单精度数为 8 位,双精度数为 11 位。以单精度数为例,8 位的指数为可以表达 0 到 255 之间
的 255 个指数值(注:指数8位的最高位都是数值位,没有符号位)。但是,指数可以为正数,也可以为负数。为了处理负指数的情况,实际的指数值按要求需要加上一个偏差
(Bias)值作为保存在指数域中的值,单精度数的偏差值为 127(2^7-1),而双精度数的偏差值为1023(2^10-1)。比如,单精度的实际指数值 0 在指数域中将保存为 127;而
保存在指数域中的 64 则表示实际的指数值 -63(64-127=-63)。 偏差的引入使得对于单精度数,实际可以表达的指数值的范围就变成 -127(表示小数点需向左移动127位) 到
128(表示小数点需向右移动128位) 之间(包含两端)。我们不久还将看到,实际的指数值 -127(全 0)以及 +128(全 1)保留用作特殊值的处理。这样,实际可以表达的有效
指数范围就在 -127 和 127 之间。
第三个域为尾数域,其中单精度数为 23 位长,双精度数为 52 位长。除了我们将要讲到的某些特殊值外,IEEE 标准要求浮点数必须是规范的。这意味着尾数的小数点左侧必须为
1,因此我们在保存尾数的时候,可以省略小数点前面这个 1,从而腾出一个二进制位来保存更多的尾数。这样我们实际上用 23 位长的尾数域表达了 24 位的尾数。比如对于单精
度数而言,二进制的 1001.101(对应于十进制的 9.625)可以表达为 1.001101 × 2^3,所以实际保存在尾数域中的值为 00110100000000000000000,即去掉小数点左侧的 1,并
用 0 在右侧补齐。
值得注意的是,对于单精度数,由于我们只有 24 位的尾数(其中一位隐藏),所以可以表达的最大尾数为 2^24- 1 = 16,777,215。特别的,16,777,216 是偶数,所以我们可以
通过将它除以 2 并相应地调整指数来保存这个数,这样 16,777,216 同样可以被精确的保存。相反,数值 16,777,217 则无法被精确的保存。由此,我们可以看到单精度的浮点数
可以表达的十进制数值中,真正有效的数字不高于 8 位。事实上,对相对误差的数值分析结果显示有效的精度大约为 7.22 位(由于位数不可取小数,所以单精度的精度为7,即
可精确到小数点后7位)。参考下面的示例:
Java代码
1. System.out.println(16777215f);//1.6777215E7
2. System.out.println(16777216f);//1.6777216E7
3. System.out.println(16777217f);//1.6777216E7
4. System.out.println(16777218f);//1.6777218E7
5. System.out.println(16777219f);//1.677722E7
6. System.out.println(16777220f);//1.677722E7
7. System.out.println(16777221f);//1.677722E7
8. System.out.println(16777222f);//1.6777222E7
9. System.out.println(16777223f);//1.6777224E7
10. System.out.println(16777224f);//1.6777224E7
11. System.out.println(16777225f);//1.6777224E7
System.out.println(16777215f);//1.6777215E7
System.out.println(16777216f);//1.6777216E7
System.out.println(16777217f);//1.6777216E7
System.out.println(16777218f);//1.6777218E7
System.out.println(16777219f);//1.677722E7
System.out.println(16777220f);//1.677722E7
System.out.println(16777221f);//1.677722E7
System.out.println(16777222f);//1.6777222E7
System.out.println(16777223f);//1.6777224E7
System.out.println(16777224f);//1.6777224E7
System.out.println(16777225f);//1.6777224E7
请看结果推导分析:
111111111111111111111111 16777215f
1.11111111111111111111111 刚好是23位,不会丢失精度,能精确表示
0 23+127 11111111111111111111111
0 10010110 11111111111111111111111
1000000000000000000000000 16777216f
1.00000000000000000000000 0 去掉的是0,所以还是能准确表示
0 24+127 00000000000000000000000
0 10010111 00000000000000000000000
1000000000000000000000001 16777217f
1.00000000000000000000000 1 不能准确表示。先试着进位
1.00000000000000000000001 由于进位后,结果的最末们不是0,所以直接舍掉
1.00000000000000000000000 到这里结果就是16777216f
1000000000000000000000010 16777218f
1.0000000000000000000001 0 去掉的是0,所以还是能准确表示
0 24+127 00000000000000000000001
0 10010111 00000000000000000000001
1000000000000000000000011 16777219f
1.0000000000000000000001 1 不能准确表示。先试着进位
1.0000000000000000000010 进位后的结果,最末们为0,所以进位成功
0 24+127 00000000000000000000010
0 10010111 00000000000000000000010
1.000000000000000000000100*2^24 16777219f存储在内存中的结果实质上为16777220
........
根据标准要求,无法精确保存的值必须向最接近的可保存的值进行舍入。这有点像我们熟悉的十进制的四舍五入,0就舍,但1不一定就进,而是在前后两个等距接近的可保存的值
中,取其中最后一位有效数字为零者(即先试着进1,会得到最后结果,然后看这个结果的尾数最后位是否为0,如果是则进位,否则直接舍去)。从上面的示例中可以看出,奇数
都被舍入为偶数(舍入到偶数有助于从某些角度减小计算中产生的舍入误差累积问题。因此为 IEEE 标准所采用),且有舍有进。我们可以将这种舍入误差理解为"半位"的误差。
所以,为了避免 7.22 对很多人造成的困惑,有些文章经常以 7.5来说明单精度浮点数的精度问题。
假定我们有一个 32 位的数据,用十六进制表示为 0xC0B40000,并且我们知道它实际上是一个单精度的浮点数。为了得到该浮点数实际表达的实数,我们首先将它变换为二进制形
式:
1100 0000 1011 0100 0000 0000 0000 0000
接着按照浮点数的格式切分为相应的域:
1 10000001 01101000000000000000000
符号域 1 意味着负数;指数域为 129 意味着实际的指数为 2 (减去偏差值 127);尾数域为 01101 意味着实际的二进制尾数为 1.01101 (加上隐含的小数点前面的 1)。所以
,实际的实数为:
-1.01101 × 2^2 = -101.101 = -5.625
或使用Java也可计算出:
Java代码
1. /*
2. 注:Float.intBitsToFloat 方法是将内存中int数值的二进制看作是float的
3. 二进制制,这样很方便使用一个二进制位来构造一个float。
4. */
5. System.out.println(Float.intBitsToFloat(0xc0B40000));//-5.625
/*
注:Float.intBitsToFloat方法是将内存中int数值的二进制看作是float的
二进制制,这样很方便使用一个二进制位来构造一个float。
*/
System.out.println(Float.intBitsToFloat(0xc0B40000));//-5.625
从实数向浮点数变换稍微麻烦一点。假定我们需要将实数 -9.625 表达为单精度的浮点数格式。方法是首先将它用二进制浮点数表达,然后变换为相应的浮点数格式。
首先,将小数点左侧的整数部分变换为其二进制形式,9 的二进制性形式为 1001。处理小数部分的算法是将我们的小数部分乘以基数 2,记录乘积结果的整数部分,接着将结果的
小数部分继续乘以 2,并不断继续该过程:
0.625 × 2 = 1.25 1
0.25× 2 = 0.5 0
0.5× 2 = 1 1
0
当最后的结果为零时,结束这个过程。这时右侧的一列数字就是我们所需的二进制小数部分,即 0.101。这样,我们就得到了完整的二进制形式 1001.101。用规范浮点数表达为
1.001101 × 2^3。
因为是负数,所以符号域为 1。指数为 3,所以指数域为 3 + 127 = 130,即二进制的 10000010。尾数省略掉小数点左侧的 1 之后为 001101,右侧用零补齐。最终结果为:
1 10000010 00110100000000000000000
最后可以将浮点数形式表示为十六进制的数据如下:
1100 0001 0001 1010 0000 0000 0000 0000
或使用Java也可计算出:
Java代码
1. /*
2. 注:Float.floatToIntBits:将内存中的float型数值的二进制看作是对应
3. 的int类型的二进制,这样很方便的将一个float的内存数据以二进制来表示。
4. 11000001000110100000000000000000
5. */
6. System.out.println(Integer.toBinaryString(Float.floatToIntBits(-9.625F)));
/*
注:Float.floatToIntBits:将内存中的float型数值的二进制看作是对应
的int类型的二进制,这样很方便的将一个float的内存数据以二进制来表示。
11000001000110100000000000000000
*/
System.out.println(Integer.toBinaryString(Float.floatToIntBits(-9.625F)));
很简单?等等!你可能已经注意到了,在上面这个我们有意选择的示例中,不断的将产生的小数部分乘以 2 的过程掩盖了一个事实。该过程结束的标志是小数部分乘以 2 的结果
为 1,不难想象,很多小数根本不能经过有限次这样的过程而得到结果(比如最简单的 0.1)。我们已经知道浮点数尾数域的位数是有限的,为此,浮点数的处理办法是持续该过
程直到由此得到的尾数足以填满尾数域,之后对多余的位进行舍入。换句话说,除了我们之前讲到的精度问题之外,十进制到二进制的变换也并不能保证总是精确的,而只能是近
似值。事实上,只有很少一部分十进制小数具有精确的二进制浮点数表达。再加上浮点数运算过程中的误差累积,结果是很多我们看来非常简单的十进制运算在计算机上却往往出
人意料。这就是最常见的浮点运算的"不准确"问题。参见下面的 Java 示例:
Java代码
1. // 34.6-34.0=0.5999985
2. System.out.print("34.6-34.0=" + (34.6f-34.0f));
// 34.6-34.0=0.5999985
System.out.print("34.6-34.0=" + (34.6f-34.0f));
产生这个误差的原因是 34.6 无法精确的表达为相应的浮点数,而只能保存为经过舍入的近似值。这个近似值与 34.0 之间的运算自然无法产生精确的结果。
当指数、尾数都为0,则规定该浮点数为0。
当指数255(全1),且尾数为0时,表示无穷大,用符号位来确定是正无穷大还是负无穷大。
当指数255(全1),且尾数不为0时,表示NaN(不是一个数)。
最大的float数:0,11111110,1111111 11111111 11111111 用10进制表示约为 +3.4E38
最小的float数:1,11111110,1111111 11111111 11111111 用10进制表示约为 -3.4E38
绝对值最小的float 数:0,00000000,0000000 00000000 00000001和1,00000000,0000000 00000000 00000001
浮点数的精度:
单精度数的尾数用23位存储,加上默认的小数点前的1位1,2^(23+1) = 16777216。因为 10^7 < 16777216 < 10^8,所以说单精度浮点数的有效位数(精度)是7位(即小数点后7
位)。
双精度的尾数用52位存储,2^(52+1) = 9007199254740992,10^16 < 9007199254740992 < 10^17,所以双精度的有效位数(精度)是16位(即小数点后16位)。
如果你在浮点数的有效位后增加数字的话,结果是不会变化的:
Java代码
1. System.out.println(1.67772156F);//1.6777215
2. System.out.println(1.67772150123456789D);//1.6777215012345679
System.out.println(1.67772156F);//1.6777215
System.out.println(1.67772150123456789D);//1.6777215012345679
float取值范围:
负数取值范围为 -3.4028235E+38 ~ -1.401298E-45,正数取值范围为 1.401298E-45 ~ 3.4028235E+38。
Float.MIN_VALUE:保持 float 类型数据的最小正非零值的常量,最小正非零值为2^-149。该常量等于十六进制的浮点文本 0x0.000002P-126f,也等于 Float.intBitsToFloat
(0x1)=1.4e-45f。(二进制为:0,00000000,0000000 00000000 00000001)
Float.MAX_VALUE:保持 float 类型的最大正有限大值的常量,最大正有限大值为(2-2^-23)*2^127。该常量等于十六进制的浮点文本 0x1.fffffeP+127f,也等于
Float.intBitsToFloat(0x7f7fffff)=3.4028235e+38f。(二进制为:0,11111110,1111111 11111111 11111111)
Java代码
1. public class FloatTest {
2. public static void main(String[] args) {
3. // 只要指数为255,而尾数不为0时,该数不是一个数
4. System.out.println(format(Integer.toBinaryString(0x7FC00000)) + " "
5. + Float.intBitsToFloat(0x7FC00000));// NaN
6. System.out.println(format(Integer.toBinaryString(0xFFC00000)) + " "
7. + Float.intBitsToFloat(0xFFC00000));// NaN
8. System.out.println(format(Integer.toBinaryString(0x7F800001)) + " "
9. + Float.intBitsToFloat(0x7F800001));// NaN
10. // 指数为255,尾数为0时,该数就是无穷,符号位区分正无穷与负无穷
11. System.out.println(format(Integer.toBinaryString(0x7F800000)) + " "
12. + Float.intBitsToFloat(0x7F800000));// Infinity
13. System.out.println(format(Integer.toBinaryString(0xFF800000)) + " "
14. + Float.intBitsToFloat(0xFF800000));// Infinity
15. //规定指数与尾数都为0时,规定结果就是0
16. System.out.println(format(Integer.toBinaryString(0x0)) + " "
17. + Float.intBitsToFloat(0x0));
18. // 正的最小float,趋近于0
19. System.out.println(format(Integer.toBinaryString(0x1)) + " "
20. + Float.intBitsToFloat(0x1));// 1.4E-45
21. // 正的最大float
22. System.out.println(format(Integer.toBinaryString(0x7f7fffff)) + " "
23. + Float.intBitsToFloat(0x7f7fffff));// 3.4028235E38
24. // 负的最大float,趋近于0
25. System.out.println(format(Integer.toBinaryString(0x80000001)) + " "
26. + Float.intBitsToFloat(0x80000001));// -1.4E-45
27. // 负的最小float
28. System.out.println(format(Integer.toBinaryString(0xFf7fffff)) + " "
29. + Float.intBitsToFloat(0xFf7fffff));// -3.4028235E38
30. }
31.
32. private static String format(String str) {
33. StringBuffer sb = new StringBuffer(str);
34. int sub = 32 - str.length();
35. if (sub != 0) {
36. for (int i = 0; i < sub; i++) {
37. sb.insert(0, 0);
38. }
39. }
40. sb.insert(1, " ");
41. sb.insert(10, " ");
42. return sb.toString();
43. }
44. }
public class FloatTest {
public static void main(String[] args) {
// 只要指数为255,而尾数不为0时,该数不是一个数
System.out.println(format(Integer.toBinaryString(0x7FC00000)) + " "
+ Float.intBitsToFloat(0x7FC00000));// NaN
System.out.println(format(Integer.toBinaryString(0xFFC00000)) + " "
+ Float.intBitsToFloat(0xFFC00000));// NaN
System.out.println(format(Integer.toBinaryString(0x7F800001)) + " "
+ Float.intBitsToFloat(0x7F800001));// NaN
// 指数为255,尾数为0时,该数就是无穷,符号位区分正无穷与负无穷
System.out.println(format(Integer.toBinaryString(0x7F800000)) + " "
+ Float.intBitsToFloat(0x7F800000));// Infinity
System.out.println(format(Integer.toBinaryString(0xFF800000)) + " "
+ Float.intBitsToFloat(0xFF800000));// Infinity
//规定指数与尾数都为0时,规定结果就是0
System.out.println(format(Integer.toBinaryString(0x0)) + " "
+ Float.intBitsToFloat(0x0));
// 正的最小float,趋近于0
System.out.println(format(Integer.toBinaryString(0x1)) + " "
+ Float.intBitsToFloat(0x1));// 1.4E-45
// 正的最大float
System.out.println(format(Integer.toBinaryString(0x7f7fffff)) + " "
+ Float.intBitsToFloat(0x7f7fffff));// 3.4028235E38
// 负的最大float,趋近于0
System.out.println(format(Integer.toBinaryString(0x80000001)) + " "
+ Float.intBitsToFloat(0x80000001));// -1.4E-45
// 负的最小float
System.out.println(format(Integer.toBinaryString(0xFf7fffff)) + " "
+ Float.intBitsToFloat(0xFf7fffff));// -3.4028235E38
}
private static String format(String str) {
StringBuffer sb = new StringBuffer(str);
int sub = 32 - str.length();
if (sub != 0) {
for (int i = 0; i < sub; i++) {
sb.insert(0, 0);
}
}
sb.insert(1, " ");
sb.insert(10, " ");
return sb.toString();
}
}
2^-149=(0.00000000000000000000001) *2^-127=(00000000000000000000000.1) *2^-149=0x0.000002P-126f=0.0000 0000 0000 0000 0000 0010*2^126
(2-2^-23)*2^127=(10.00000000000000000000000-0.00000000000000000000001) *2^127
0x1.fffffeP+127f=0x1.1111 1111 1111 1111 1111 1110P+127
double取值范围:
负值取值范围 -1.79769313486231570E+308 ~ -4.94065645841246544E-324,正值取值范围为 4.94065645841246544E-324 ~ 1.79769313486231570E+308。
Double.MIN_VALUE:保持 double 类型数据的最小正非零值的常量,最小正非零值为 2^-1074。它等于十六进制的浮点字面值 0x0.0000000000001P-1022,也等于
Double.longBitsToDouble(0x1L)。
Double.MAX_VALUE 保持 double 类型的最大正有限值的常量,最大正有限值为 (2-2^-52)*2^1023。它等于十六进制的浮点字面值 0x1.fffffffffffffP+1023,也等于
Double.longBitsToDouble(0x7fefffffffffffffL)。
Java中的小数精确计算
Java代码
1. public class FloatCalc {
2.
3. //默认除法运算精度
4. private static final int DEF_DIV_SCALE = 10;
5.
6. /**
7. * 提供精确的加法运算。
8. * @param v1 被加数
9. * @param v2 加数
10. * @return 两个参数的和
11. */
12.
13. public static double add(double v1, double v2) {
14. BigDecimal b1 = new BigDecimal(Double.toString(v1));
15. BigDecimal b2 = new BigDecimal(Double.toString(v2));
16. return b1.add(b2).doubleValue();
17. }
18.
19. /**
20. * 提供精确的减法运算。
21. * @param v1 被减数
22. * @param v2 减数
23. * @return 两个参数的差
24. */
25.
26. public static double sub(double v1, double v2) {
27. BigDecimal b1 = new BigDecimal(Double.toString(v1));
28. BigDecimal b2 = new BigDecimal(Double.toString(v2));
29. return b1.subtract(b2).doubleValue();
30. }
31.
32. /**
33. * 提供精确的乘法运算。
34. * @param v1 被乘数
35. * @param v2 乘数
36. * @return 两个参数的积
37. */
38.
39. public static double mul(double v1, double v2) {
40. BigDecimal b1 = new BigDecimal(Double.toString(v1));
41. BigDecimal b2 = new BigDecimal(Double.toString(v2));
42. return b1.multiply(b2).doubleValue();
43. }
44.
45. /**
46. * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
47. * 小数点以后10位,以后的数字四舍五入。
48. * @param v1 被除数
49. * @param v2 除数
50. * @return 两个参数的商
51. */
52.
53. public static double div(double v1, double v2) {
54. return div(v1, v2, DEF_DIV_SCALE);
55. }
56.
57. /**
58. * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
59. * 定精度,以后的数字四舍五入。
60. * @param v1 被除数
61. * @param v2 除数
62. * @param scale 表示表示需要精确到小数点以后几位。
63. * @return 两个参数的商
64. */
65.
66. public static double div(double v1, double v2, int scale) {
67. if (scale < 0) {
68. throw new IllegalArgumentException(
69. "The scale must be a positive integer or zero");
70. }
71. BigDecimal b1 = new BigDecimal(Double.toString(v1));
72. BigDecimal b2 = new BigDecimal(Double.toString(v2));
73. return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
74. }
75.
76. /**
77. * 提供精确的小数位四舍五入处理。
78. * @param v 需要四舍五入的数字
79. * @param scale 小数点后保留几位
80. * @return 四舍五入后的结果
81. */
82.
83. public static double round(double v, int scale) {
84. if (scale < 0) {
85. throw new IllegalArgumentException(
86. "The scale must be a positive integer or zero");
87. }
88. BigDecimal b = new BigDecimal(Double.toString(v));
89. BigDecimal one = new BigDecimal("1");
90. return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
91. }
92. }
计算机系统采纳了所谓的浮点数表达方式。这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa也叫有效数字 ),一个基数(Base),一个指数(Exponent)以及
一个表示正负的符号来表达实数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。
当一个浮点数的尾数为0,不论其阶码为何值,该浮点数的值都为0。当阶码的值为它能表示的最小一个值或更小的值时,不管其尾数为何值,计算机都把该浮点数看成零值,通常称
其为机器零,此时该浮点数的所有各位(包括阶码位和尾数位)都清为0值。
Java 平台上的浮点数类型 float 和 double 采纳了 IEEE 754 标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式。
在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指
数和尾数。这样,通过尾数和可以调节的指数就可以表达给定的数值了。具体的格式参见下面的图例:
在上面的图例中,第一个域为符号域。其中 0 表示数值为正数,而 1 则表示负数。
第二个域为指数域,对应于我们之前介绍的二进制科学计数法中的指数部分。其中单精度数为 8 位,双精度数为 11 位。以单精度数为例,8 位的指数为可以表达 0 到 255 之间
的 255 个指数值(注:指数8位的最高位都是数值位,没有符号位)。但是,指数可以为正数,也可以为负数。为了处理负指数的情况,实际的指数值按要求需要加上一个偏差
(Bias)值作为保存在指数域中的值,单精度数的偏差值为 127(2^7-1),而双精度数的偏差值为1023(2^10-1)。比如,单精度的实际指数值 0 在指数域中将保存为 127;而
保存在指数域中的 64 则表示实际的指数值 -63(64-127=-63)。 偏差的引入使得对于单精度数,实际可以表达的指数值的范围就变成 -127(表示小数点需向左移动127位) 到
128(表示小数点需向右移动128位) 之间(包含两端)。我们不久还将看到,实际的指数值 -127(全 0)以及 +128(全 1)保留用作特殊值的处理。这样,实际可以表达的有效
指数范围就在 -127 和 127 之间。
第三个域为尾数域,其中单精度数为 23 位长,双精度数为 52 位长。除了我们将要讲到的某些特殊值外,IEEE 标准要求浮点数必须是规范的。这意味着尾数的小数点左侧必须为
1,因此我们在保存尾数的时候,可以省略小数点前面这个 1,从而腾出一个二进制位来保存更多的尾数。这样我们实际上用 23 位长的尾数域表达了 24 位的尾数。比如对于单精
度数而言,二进制的 1001.101(对应于十进制的 9.625)可以表达为 1.001101 × 2^3,所以实际保存在尾数域中的值为 00110100000000000000000,即去掉小数点左侧的 1,并
用 0 在右侧补齐。
值得注意的是,对于单精度数,由于我们只有 24 位的尾数(其中一位隐藏),所以可以表达的最大尾数为 2^24- 1 = 16,777,215。特别的,16,777,216 是偶数,所以我们可以
通过将它除以 2 并相应地调整指数来保存这个数,这样 16,777,216 同样可以被精确的保存。相反,数值 16,777,217 则无法被精确的保存。由此,我们可以看到单精度的浮点数
可以表达的十进制数值中,真正有效的数字不高于 8 位。事实上,对相对误差的数值分析结果显示有效的精度大约为 7.22 位(由于位数不可取小数,所以单精度的精度为7,即
可精确到小数点后7位)。参考下面的示例:
Java代码
1. System.out.println(16777215f);//1.6777215E7
2. System.out.println(16777216f);//1.6777216E7
3. System.out.println(16777217f);//1.6777216E7
4. System.out.println(16777218f);//1.6777218E7
5. System.out.println(16777219f);//1.677722E7
6. System.out.println(16777220f);//1.677722E7
7. System.out.println(16777221f);//1.677722E7
8. System.out.println(16777222f);//1.6777222E7
9. System.out.println(16777223f);//1.6777224E7
10. System.out.println(16777224f);//1.6777224E7
11. System.out.println(16777225f);//1.6777224E7
System.out.println(16777215f);//1.6777215E7
System.out.println(16777216f);//1.6777216E7
System.out.println(16777217f);//1.6777216E7
System.out.println(16777218f);//1.6777218E7
System.out.println(16777219f);//1.677722E7
System.out.println(16777220f);//1.677722E7
System.out.println(16777221f);//1.677722E7
System.out.println(16777222f);//1.6777222E7
System.out.println(16777223f);//1.6777224E7
System.out.println(16777224f);//1.6777224E7
System.out.println(16777225f);//1.6777224E7
请看结果推导分析:
111111111111111111111111 16777215f
1.11111111111111111111111 刚好是23位,不会丢失精度,能精确表示
0 23+127 11111111111111111111111
0 10010110 11111111111111111111111
1000000000000000000000000 16777216f
1.00000000000000000000000 0 去掉的是0,所以还是能准确表示
0 24+127 00000000000000000000000
0 10010111 00000000000000000000000
1000000000000000000000001 16777217f
1.00000000000000000000000 1 不能准确表示。先试着进位
1.00000000000000000000001 由于进位后,结果的最末们不是0,所以直接舍掉
1.00000000000000000000000 到这里结果就是16777216f
1000000000000000000000010 16777218f
1.0000000000000000000001 0 去掉的是0,所以还是能准确表示
0 24+127 00000000000000000000001
0 10010111 00000000000000000000001
1000000000000000000000011 16777219f
1.0000000000000000000001 1 不能准确表示。先试着进位
1.0000000000000000000010 进位后的结果,最末们为0,所以进位成功
0 24+127 00000000000000000000010
0 10010111 00000000000000000000010
1.000000000000000000000100*2^24 16777219f存储在内存中的结果实质上为16777220
........
根据标准要求,无法精确保存的值必须向最接近的可保存的值进行舍入。这有点像我们熟悉的十进制的四舍五入,0就舍,但1不一定就进,而是在前后两个等距接近的可保存的值
中,取其中最后一位有效数字为零者(即先试着进1,会得到最后结果,然后看这个结果的尾数最后位是否为0,如果是则进位,否则直接舍去)。从上面的示例中可以看出,奇数
都被舍入为偶数(舍入到偶数有助于从某些角度减小计算中产生的舍入误差累积问题。因此为 IEEE 标准所采用),且有舍有进。我们可以将这种舍入误差理解为"半位"的误差。
所以,为了避免 7.22 对很多人造成的困惑,有些文章经常以 7.5来说明单精度浮点数的精度问题。
假定我们有一个 32 位的数据,用十六进制表示为 0xC0B40000,并且我们知道它实际上是一个单精度的浮点数。为了得到该浮点数实际表达的实数,我们首先将它变换为二进制形
式:
1100 0000 1011 0100 0000 0000 0000 0000
接着按照浮点数的格式切分为相应的域:
1 10000001 01101000000000000000000
符号域 1 意味着负数;指数域为 129 意味着实际的指数为 2 (减去偏差值 127);尾数域为 01101 意味着实际的二进制尾数为 1.01101 (加上隐含的小数点前面的 1)。所以
,实际的实数为:
-1.01101 × 2^2 = -101.101 = -5.625
或使用Java也可计算出:
Java代码
1. /*
2. 注:Float.intBitsToFloat 方法是将内存中int数值的二进制看作是float的
3. 二进制制,这样很方便使用一个二进制位来构造一个float。
4. */
5. System.out.println(Float.intBitsToFloat(0xc0B40000));//-5.625
/*
注:Float.intBitsToFloat方法是将内存中int数值的二进制看作是float的
二进制制,这样很方便使用一个二进制位来构造一个float。
*/
System.out.println(Float.intBitsToFloat(0xc0B40000));//-5.625
从实数向浮点数变换稍微麻烦一点。假定我们需要将实数 -9.625 表达为单精度的浮点数格式。方法是首先将它用二进制浮点数表达,然后变换为相应的浮点数格式。
首先,将小数点左侧的整数部分变换为其二进制形式,9 的二进制性形式为 1001。处理小数部分的算法是将我们的小数部分乘以基数 2,记录乘积结果的整数部分,接着将结果的
小数部分继续乘以 2,并不断继续该过程:
0.625 × 2 = 1.25 1
0.25× 2 = 0.5 0
0.5× 2 = 1 1
0
当最后的结果为零时,结束这个过程。这时右侧的一列数字就是我们所需的二进制小数部分,即 0.101。这样,我们就得到了完整的二进制形式 1001.101。用规范浮点数表达为
1.001101 × 2^3。
因为是负数,所以符号域为 1。指数为 3,所以指数域为 3 + 127 = 130,即二进制的 10000010。尾数省略掉小数点左侧的 1 之后为 001101,右侧用零补齐。最终结果为:
1 10000010 00110100000000000000000
最后可以将浮点数形式表示为十六进制的数据如下:
1100 0001 0001 1010 0000 0000 0000 0000
或使用Java也可计算出:
Java代码
1. /*
2. 注:Float.floatToIntBits:将内存中的float型数值的二进制看作是对应
3. 的int类型的二进制,这样很方便的将一个float的内存数据以二进制来表示。
4. 11000001000110100000000000000000
5. */
6. System.out.println(Integer.toBinaryString(Float.floatToIntBits(-9.625F)));
/*
注:Float.floatToIntBits:将内存中的float型数值的二进制看作是对应
的int类型的二进制,这样很方便的将一个float的内存数据以二进制来表示。
11000001000110100000000000000000
*/
System.out.println(Integer.toBinaryString(Float.floatToIntBits(-9.625F)));
很简单?等等!你可能已经注意到了,在上面这个我们有意选择的示例中,不断的将产生的小数部分乘以 2 的过程掩盖了一个事实。该过程结束的标志是小数部分乘以 2 的结果
为 1,不难想象,很多小数根本不能经过有限次这样的过程而得到结果(比如最简单的 0.1)。我们已经知道浮点数尾数域的位数是有限的,为此,浮点数的处理办法是持续该过
程直到由此得到的尾数足以填满尾数域,之后对多余的位进行舍入。换句话说,除了我们之前讲到的精度问题之外,十进制到二进制的变换也并不能保证总是精确的,而只能是近
似值。事实上,只有很少一部分十进制小数具有精确的二进制浮点数表达。再加上浮点数运算过程中的误差累积,结果是很多我们看来非常简单的十进制运算在计算机上却往往出
人意料。这就是最常见的浮点运算的"不准确"问题。参见下面的 Java 示例:
Java代码
1. // 34.6-34.0=0.5999985
2. System.out.print("34.6-34.0=" + (34.6f-34.0f));
// 34.6-34.0=0.5999985
System.out.print("34.6-34.0=" + (34.6f-34.0f));
产生这个误差的原因是 34.6 无法精确的表达为相应的浮点数,而只能保存为经过舍入的近似值。这个近似值与 34.0 之间的运算自然无法产生精确的结果。
当指数、尾数都为0,则规定该浮点数为0。
当指数255(全1),且尾数为0时,表示无穷大,用符号位来确定是正无穷大还是负无穷大。
当指数255(全1),且尾数不为0时,表示NaN(不是一个数)。
最大的float数:0,11111110,1111111 11111111 11111111 用10进制表示约为 +3.4E38
最小的float数:1,11111110,1111111 11111111 11111111 用10进制表示约为 -3.4E38
绝对值最小的float 数:0,00000000,0000000 00000000 00000001和1,00000000,0000000 00000000 00000001
浮点数的精度:
单精度数的尾数用23位存储,加上默认的小数点前的1位1,2^(23+1) = 16777216。因为 10^7 < 16777216 < 10^8,所以说单精度浮点数的有效位数(精度)是7位(即小数点后7
位)。
双精度的尾数用52位存储,2^(52+1) = 9007199254740992,10^16 < 9007199254740992 < 10^17,所以双精度的有效位数(精度)是16位(即小数点后16位)。
如果你在浮点数的有效位后增加数字的话,结果是不会变化的:
Java代码
1. System.out.println(1.67772156F);//1.6777215
2. System.out.println(1.67772150123456789D);//1.6777215012345679
System.out.println(1.67772156F);//1.6777215
System.out.println(1.67772150123456789D);//1.6777215012345679
float取值范围:
负数取值范围为 -3.4028235E+38 ~ -1.401298E-45,正数取值范围为 1.401298E-45 ~ 3.4028235E+38。
Float.MIN_VALUE:保持 float 类型数据的最小正非零值的常量,最小正非零值为2^-149。该常量等于十六进制的浮点文本 0x0.000002P-126f,也等于 Float.intBitsToFloat
(0x1)=1.4e-45f。(二进制为:0,00000000,0000000 00000000 00000001)
Float.MAX_VALUE:保持 float 类型的最大正有限大值的常量,最大正有限大值为(2-2^-23)*2^127。该常量等于十六进制的浮点文本 0x1.fffffeP+127f,也等于
Float.intBitsToFloat(0x7f7fffff)=3.4028235e+38f。(二进制为:0,11111110,1111111 11111111 11111111)
Java代码
1. public class FloatTest {
2. public static void main(String[] args) {
3. // 只要指数为255,而尾数不为0时,该数不是一个数
4. System.out.println(format(Integer.toBinaryString(0x7FC00000)) + " "
5. + Float.intBitsToFloat(0x7FC00000));// NaN
6. System.out.println(format(Integer.toBinaryString(0xFFC00000)) + " "
7. + Float.intBitsToFloat(0xFFC00000));// NaN
8. System.out.println(format(Integer.toBinaryString(0x7F800001)) + " "
9. + Float.intBitsToFloat(0x7F800001));// NaN
10. // 指数为255,尾数为0时,该数就是无穷,符号位区分正无穷与负无穷
11. System.out.println(format(Integer.toBinaryString(0x7F800000)) + " "
12. + Float.intBitsToFloat(0x7F800000));// Infinity
13. System.out.println(format(Integer.toBinaryString(0xFF800000)) + " "
14. + Float.intBitsToFloat(0xFF800000));// Infinity
15. //规定指数与尾数都为0时,规定结果就是0
16. System.out.println(format(Integer.toBinaryString(0x0)) + " "
17. + Float.intBitsToFloat(0x0));
18. // 正的最小float,趋近于0
19. System.out.println(format(Integer.toBinaryString(0x1)) + " "
20. + Float.intBitsToFloat(0x1));// 1.4E-45
21. // 正的最大float
22. System.out.println(format(Integer.toBinaryString(0x7f7fffff)) + " "
23. + Float.intBitsToFloat(0x7f7fffff));// 3.4028235E38
24. // 负的最大float,趋近于0
25. System.out.println(format(Integer.toBinaryString(0x80000001)) + " "
26. + Float.intBitsToFloat(0x80000001));// -1.4E-45
27. // 负的最小float
28. System.out.println(format(Integer.toBinaryString(0xFf7fffff)) + " "
29. + Float.intBitsToFloat(0xFf7fffff));// -3.4028235E38
30. }
31.
32. private static String format(String str) {
33. StringBuffer sb = new StringBuffer(str);
34. int sub = 32 - str.length();
35. if (sub != 0) {
36. for (int i = 0; i < sub; i++) {
37. sb.insert(0, 0);
38. }
39. }
40. sb.insert(1, " ");
41. sb.insert(10, " ");
42. return sb.toString();
43. }
44. }
public class FloatTest {
public static void main(String[] args) {
// 只要指数为255,而尾数不为0时,该数不是一个数
System.out.println(format(Integer.toBinaryString(0x7FC00000)) + " "
+ Float.intBitsToFloat(0x7FC00000));// NaN
System.out.println(format(Integer.toBinaryString(0xFFC00000)) + " "
+ Float.intBitsToFloat(0xFFC00000));// NaN
System.out.println(format(Integer.toBinaryString(0x7F800001)) + " "
+ Float.intBitsToFloat(0x7F800001));// NaN
// 指数为255,尾数为0时,该数就是无穷,符号位区分正无穷与负无穷
System.out.println(format(Integer.toBinaryString(0x7F800000)) + " "
+ Float.intBitsToFloat(0x7F800000));// Infinity
System.out.println(format(Integer.toBinaryString(0xFF800000)) + " "
+ Float.intBitsToFloat(0xFF800000));// Infinity
//规定指数与尾数都为0时,规定结果就是0
System.out.println(format(Integer.toBinaryString(0x0)) + " "
+ Float.intBitsToFloat(0x0));
// 正的最小float,趋近于0
System.out.println(format(Integer.toBinaryString(0x1)) + " "
+ Float.intBitsToFloat(0x1));// 1.4E-45
// 正的最大float
System.out.println(format(Integer.toBinaryString(0x7f7fffff)) + " "
+ Float.intBitsToFloat(0x7f7fffff));// 3.4028235E38
// 负的最大float,趋近于0
System.out.println(format(Integer.toBinaryString(0x80000001)) + " "
+ Float.intBitsToFloat(0x80000001));// -1.4E-45
// 负的最小float
System.out.println(format(Integer.toBinaryString(0xFf7fffff)) + " "
+ Float.intBitsToFloat(0xFf7fffff));// -3.4028235E38
}
private static String format(String str) {
StringBuffer sb = new StringBuffer(str);
int sub = 32 - str.length();
if (sub != 0) {
for (int i = 0; i < sub; i++) {
sb.insert(0, 0);
}
}
sb.insert(1, " ");
sb.insert(10, " ");
return sb.toString();
}
}
2^-149=(0.00000000000000000000001) *2^-127=(00000000000000000000000.1) *2^-149=0x0.000002P-126f=0.0000 0000 0000 0000 0000 0010*2^126
(2-2^-23)*2^127=(10.00000000000000000000000-0.00000000000000000000001) *2^127
0x1.fffffeP+127f=0x1.1111 1111 1111 1111 1111 1110P+127
double取值范围:
负值取值范围 -1.79769313486231570E+308 ~ -4.94065645841246544E-324,正值取值范围为 4.94065645841246544E-324 ~ 1.79769313486231570E+308。
Double.MIN_VALUE:保持 double 类型数据的最小正非零值的常量,最小正非零值为 2^-1074。它等于十六进制的浮点字面值 0x0.0000000000001P-1022,也等于
Double.longBitsToDouble(0x1L)。
Double.MAX_VALUE 保持 double 类型的最大正有限值的常量,最大正有限值为 (2-2^-52)*2^1023。它等于十六进制的浮点字面值 0x1.fffffffffffffP+1023,也等于
Double.longBitsToDouble(0x7fefffffffffffffL)。
Java中的小数精确计算
Java代码
1. public class FloatCalc {
2.
3. //默认除法运算精度
4. private static final int DEF_DIV_SCALE = 10;
5.
6. /**
7. * 提供精确的加法运算。
8. * @param v1 被加数
9. * @param v2 加数
10. * @return 两个参数的和
11. */
12.
13. public static double add(double v1, double v2) {
14. BigDecimal b1 = new BigDecimal(Double.toString(v1));
15. BigDecimal b2 = new BigDecimal(Double.toString(v2));
16. return b1.add(b2).doubleValue();
17. }
18.
19. /**
20. * 提供精确的减法运算。
21. * @param v1 被减数
22. * @param v2 减数
23. * @return 两个参数的差
24. */
25.
26. public static double sub(double v1, double v2) {
27. BigDecimal b1 = new BigDecimal(Double.toString(v1));
28. BigDecimal b2 = new BigDecimal(Double.toString(v2));
29. return b1.subtract(b2).doubleValue();
30. }
31.
32. /**
33. * 提供精确的乘法运算。
34. * @param v1 被乘数
35. * @param v2 乘数
36. * @return 两个参数的积
37. */
38.
39. public static double mul(double v1, double v2) {
40. BigDecimal b1 = new BigDecimal(Double.toString(v1));
41. BigDecimal b2 = new BigDecimal(Double.toString(v2));
42. return b1.multiply(b2).doubleValue();
43. }
44.
45. /**
46. * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
47. * 小数点以后10位,以后的数字四舍五入。
48. * @param v1 被除数
49. * @param v2 除数
50. * @return 两个参数的商
51. */
52.
53. public static double div(double v1, double v2) {
54. return div(v1, v2, DEF_DIV_SCALE);
55. }
56.
57. /**
58. * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
59. * 定精度,以后的数字四舍五入。
60. * @param v1 被除数
61. * @param v2 除数
62. * @param scale 表示表示需要精确到小数点以后几位。
63. * @return 两个参数的商
64. */
65.
66. public static double div(double v1, double v2, int scale) {
67. if (scale < 0) {
68. throw new IllegalArgumentException(
69. "The scale must be a positive integer or zero");
70. }
71. BigDecimal b1 = new BigDecimal(Double.toString(v1));
72. BigDecimal b2 = new BigDecimal(Double.toString(v2));
73. return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
74. }
75.
76. /**
77. * 提供精确的小数位四舍五入处理。
78. * @param v 需要四舍五入的数字
79. * @param scale 小数点后保留几位
80. * @return 四舍五入后的结果
81. */
82.
83. public static double round(double v, int scale) {
84. if (scale < 0) {
85. throw new IllegalArgumentException(
86. "The scale must be a positive integer or zero");
87. }
88. BigDecimal b = new BigDecimal(Double.toString(v));
89. BigDecimal one = new BigDecimal("1");
90. return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
91. }
92. }