float,double 的精度, 在内存中的存储方式
?
一, 浮点型变量在内存中的存储方式
Java 的浮点数遵循 IEEE 754 标准, 采用二进制数据的科学计数法来表示浮点数, float 遵从的是 IEEE R32.24 , 而 double 遵从的是 R64.53. 该标准中表示的浮点数表示分为规约形式和非规约形式以及特殊情况.
??? 无论是单精度还是双精度在存储中都分为三个部分:
符号位(Sign) : 0 代表正, 1 代表为负
指数位(Exponent): 用于存储科学计数法中的指数数据, 并且采用移位存储
尾数部分(Mantissa): 尾数部分
根据 IEEE 754 标准, 对于 float 单精度, 第 31 位 (左边第 1 位) 表示浮点数字的符号; 第 30-23 位 (8 位) 表示指数(指数加完偏移量, 即加偏移量 127 后的值); 第 22-0 位是尾数(尾数是 23 位); 存储方式如下图所示:
指数是有符号的, 但并不是使用有符号整形 (int) 的存储方式, 而是使用偏移 (Offset) 算法, 存储的数据 = 元数据 + 127, 所以[实际指数值 = 指数部分二进制值 - 127] .8 位二进制能表示的范围为 0~255, 这样的话范围就是(-127~128), 另外全 0 和全 1 作为特殊处理, 所以指数部分能表示的范围为 - 126~127(规约形式). 可以参考 https://www.zhihu.com/question/21711083
根据 IEEE 754 标准, 对于 double 双精度, 第 63 位表示浮点数字的符号; 第 62-52 位 (11 位) 表示指数(指数加完偏移量); 第 51-0 位是尾数(尾数是 52 位, 尾数位比 float 多, 尾数位越多, 精度越高); 双精度的存储方式为:
? ?
指数部分与 float 单精度存储方式一样使用偏移 (Offset) 算法, 存储的数据 = 元数据 + 1023, 所以[实际指数值 = 指数部分二进制值 - 1023] .11 位二进制能表示的范围为 0~2047, 所以指数部分能表示的范围为 - 1022~1023.
?
非规约形式表示: 当指数部分全 0 而且小数部分不全 0 时表示的是非规格化的浮点数, 因为这里默认没有前导 1, 而是 0。 对于 float 类型,取值位 0.f * 2-126, 表示范围位 2-149~(1-2-23)×2-126 这里没有考虑符号。(IEEE 754 标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值小 1。) |
?
其他特殊表示: 1. 当指数部分和小数部分全为 0 时, 表示 0 值, 有 + 0 和 - 0 之分 (符号位决定),0x00000000 表示正 0,0x80000000 表示负 0. 2. 指数部分全 1, 小数部分全 0 时, 表示无穷大, 有正无穷和负无穷, 0x7f800000 表示正无穷, 0xff800000 表示负无穷. 3. 指数部分全 1, 小数部分不全 0 时, 表示 NaN, 分为 QNaN 和 SNaN,Java 中都是 NaN. |
?
二, 二进制的科学计数法
十进制的科学计数法: 任何数字都可以表示成 a*10n, 其中 1≤a<10,n 表示整数, 一般用于表示很大的数字, 例如: 623500000000 可以表示为 6.235*1011. 同理, 任何二进制都可以表示成 a*2n, 其中 a 为带小数点的二进制序列, 且小数点在第二位, n 表示整数, 例如: 100111101100000000000000 可以表示为 1.001111011*223.
三, 十进制转换为二进制
1. 整数部分
余数法: 用这个十进制的整数除以 2, 会得到一个商值和一个余数值, 再用商除以 2, 一直除到商为 0 为止, 把每次的余数, 逆序连起来, 就是要转的二进制数. 整数总能用二进制准确表示.
2. 小数部分
小数部分就是用十进制小数乘以 2, 得出的积, 然后把积的整数位取出, 再用积的小数部分乘以 2, 再把积的整数位取出, 再用小数部分乘以 2, 循环操作, 直到小数部分为 0, 或者遇到无限循环(小数转换为二进制可能会损失精度), 取到你认为足够精度的小数为止, 然后把取出的整数位顺序连接起来, 就是要转换成的二进制小数.
十进制 | 二进制 |
0.5? | 0.1? |
0.25? | 0.01? |
0.125? | 0.001? |
0.0625? | 0.0001? |
0.03125 | 0.00001? |
0.015625? | 0.000001? |
0.0078125 | 0.0000001 |
0.00390625? | 0.00000001? |
十进制 0.875 转换成二进制 0.111, 十进制 0.3 转换成二进制 0.01001100110011....
四, 二进制转换为十进制
float 与 double 的二进制表示转换为十进制时, 先表示为科学计数法, 再分别转换小数部分和整数部分.
0 01111111 00000000000000000000000 的指数部分为 01111111=(27-1)-127 = 0, 尾数部分为 00000000000000000000000,(因整数部分总为 1, 所以存储时省略整数部分)该数的科学计数法表示为 1. 00000000000000000000000*20, 所以该二进制对应的十进制为 1.
1 10000000 00000000000000000000000 的指数部分为 10000000 = 27-127 = 1, 尾数部分为 00000000000000000000000, 该数的科学计数法表示为 1.00000000000000000000000*21, 左移 1 位变为 10.0000000000000000000000, 所以该二进制对应的十进制为 2.
0 01111101 00110011001100110011001 的指数部分为 01111101 = 125 - 127 = -2, 尾数部分为 00110011001100110011100, 该数的科学计数法表示为 1.00110011001100110011100*2-2, 右移 2 位变为 0.0100110011001100110011001, 整数部分为 0, 小数部分约为 0.3, 该数为 0.3.
0 10000000 10010010000111111011011 的指数部分为 10000000 = 27 - 127 = 1, 尾数部分为 10010010000111111011011, 该数的科学计数法表示为 1.10010010000111111011011*21, 左移 1 位变为 11.0010010000111111011011, 整数部分为 3, 小数部分约为 0. 1415926, 该数为 3.1415926.
五, float,double 的精度
? 数学领域中的精度一般指有效数字, 是十进制位数,? 而计算机中的精度通常是指二进制位数.
从上述几个 float 的二进制表示转换为十进制的示例可以看出:
指数位决定了范围大小, 因为指数位表示的越大则表示的数值越大.
尾数位决定了计算精度, 因为尾数位能表示的越大, 则计算精度越大.
浮点数的精度决定于尾数部分, 而 float 尾数占了 23 个二进制位, 加上省略的整数部分的 1, 共 24 位决定浮点数的精度. 24 位二进制表示的最大数字为 224 转化为十进制数为 16,777,216(8 个十进制位), 因此有一种说法是 float 的十进制精度为 8 位, 但是由于其并不能表示所有 8 位十进制数, 因此也有种说法是其精度为 7 位.[这些说法都不准确, 因为尾数部分包含整数部分和小数部分] 准确的说, float 可以保证 7 位十进制有效数字.
float 能表示的最大数为 0 11111110 11111111111111111111111, 也是 Float.MAX_VALUE 的值,(224-1)* 2(127-23)约为 3.4028235E38.
float 能表示最小正数为 1 00000000 00000000000000000000001(非规约表示), 也是 Float.MIN_VALUE 的值, 2-149 约为 1.4E-45
?
double 位数占 52 位, 加上省略的整数部分的 1, 共 53 位决定浮点数的精度. 53 位二进制表示的最大数字为 253 转化为十进制数为 9,007,199,254,740,992(16 个十进制位), 但是它不能表示所有 16 位十进制数, 其精度介于 15~16 位. 准确的说, double 可以保证 15 位十进制有效数字.
Double 表示的最大数为 1.7976931348623157E308, 最小正数为 4.9E-324
来源: http://www.bubuko.com/infodetail-3399743.html