再次普及double浮点型数据的换算过程和推算原理

最近在网上找了下关于浮点double型数据的解释,基本都是解释的含糊不清,误人子弟,今天开篇来重点讲一下浮点double数据在计算机中的转换和计算方式,float是32bit单精度,和double原理类似,本文重点讲double。

不管是C,C++,java, 大家对浮点数据的引入是再正常不过了,因为计算机都帮我们自动处理了,而无需关心其真正的原理,但是作为一个高级程序员必须要做到的是知其然并知其所以然,才能在平时工作中战无不胜。

针对double型数据,看了下百科全书英文网站的介绍,虽然介绍的很详细,但是有些东西没有描述清楚,今天重点揭开double数据在计算机二进制世界的真是面纱。

维基百科可以参考:https://en.wikipedia.org/wiki/Double-precision_floating-point_format。

使用中文来描述,是为了更好的解释清楚其中的奥秘和原理,废话不多说,切入正题。

Double型数据根据IEEE754标准,占用计算机64bit,即8个字节的内存空间,浮点(小数位)占用52bit [bit 0 -51], 指数位占用(11bit) [bit 52 - bit 62], 符号位1bit(bit 63), 如下图

照标准转换成十进制的换算公式:

(-1)^{\text{sign}}(1.b_{51}b_{50}...b_{0})_{2}\times 2^{e-1023}

(-1)^{\text{sign}}\left(1+\sum _{i=1}^{52}b_{52-i}2^{-i}\right)\times 2^{e-1023}

10进制数值 = (-1)^sign x(1+ 小数部分F) x 2^(e-1023);

为了便于描述, 我们在公式中把符号位bit 63用s标记,指数位bit 52-62(幂)用e标记, 小数位bit 0-bit 51用F标记。

上面公式第二个更便于理解,先讲一下这公式的意思:

第一项:数据正负关系,(-1)^sign这个sign取自64位数据的bit 63(最高位,即符号位),1代表负数,0代表正数。

第二项:小数部分F求和再加1,就是64位数据的低52位数据(bit 51 - bit 0)计算出小数的具体数值,注意bit 51 - bit 0从公式看, 对应的二进制小数部的-1[bit 51], -2[bit 50], -3[bit 49] ... -50[bit 2], -51 [bit 1], -52 [bit 0],用-1,-2... -50,-51依次表示小数部分的第1位,第2位。。。第50位,第51位。也就是64位数据的bit 51表示小数的第一位, bit 0表示小数的第52位

那二进制世界是如何计算和表示十进制的小数的呢? 这记录2组例子便于理解:

A: 假设bit 51为1(表示为-1), bit [50 - 0] 全为0, 那52位的小数部分就是 0x 8 0000 0000 0000,52位,根据公式,1x2^(-1) = 1/2 = 0.5, 所以bit 51的精度就是对应十进制的0.5.

B:假设bit 51,bit 50为1(表示-1位,-2位), bit [49 - 0] 全为0, 那52位的小数部分就是 0x C 0000 0000 0000,52位,

根据公式,1x2^(-1) + 1x2^(-2) = 1/2 + 1/4 = 0.5 + 0.25 = 0.75 , 所以bit 50的精度就是对应十进制的0.25.

小数第1位bit 51对应精度1/2(2^-1), 第2位bit 50就是1/4(2^-2)...第52位bit 0,就是1/2^(-52);

第三项:2^(e-1023),根据标准, 1023是固定偏移计算,而e的值对应的就是64位数据中的bit [52-62], 所以bit 52-62

存储的是2的指数幂的值,注意是底数2的幂值(2的e次方),而不是直接代表整数,所以double型数据的整数部分在二进制数据是要通过

换算间接体现的。

注意:bit 52 - bit 62的11位数据,是从 0x000 - 0x7FF, 最小0,最到2047,0x0001 -> 0x7FE 代表正数 ,因为bit64是代表符号,

所以0x8001-0xFFFE代表的是负数。0x000和0x7FF(或0xFFFF)这三个数字在标准中比较特殊,上述计算公式

不适用。 如果碰到e=0(bit[52-62]全部为0), 需要公式:{\displaystyle (-1)^{\text{sign}}\times 2^{1-{\text{exponent bias}}}\times 0.{\text{fraction}}}

即10进制数值 = (-1)^sign x 2^(-1022) x(小数部分F); /*这是针对e = 0的情况*/

而e=0x7FF表示正无穷大, e=0xFFFF表示负无穷大。

有了上面的公式和解释我就可以来计算double数值了,在计算数值前我们先要把F小数位的计算公式推导一下,这在

官方网站并没有提到,这公式对下面的计算很重要, 假设我们以double D变量为例。

对于e > 0 and e < 0x7FF(0xFFFF), 公式: D =(-1)^s x (1+ 小数部分F) x2^(e-1023);

对于e = 0,公式 D =(-1)^s x 2^(-1022) x(小数部分F);

e和s的值都很容易获取, s就是bit 64, 0 (正)或者1 (负), e就是bit [52 62]的值. 例如bit[62-52] = 0x3BA = 954(十进制)

那F的如何计算呢? 假设 F的16进制bit[51 - 0] = 0xF9BC87E6A5D21,其实转换成2进制,按照上面的原始求和计算也可以

但是有没有发现计算量很大? 按照位数 F = 2^(-1) + 2^(-2).., 显然手工计算或换算过于复杂,那有没有简单有效的计算方法呢?

显然是有的。为了对比,我们先假设 F的16进制bit[51 - 0] = 0xC000000000000, 便于手工计算,C = b1100, 即bit51(-1位),bit50(-2位)为1,

那 F = 2^(-1) + 2^(-2) = 1/2 + 1/4 = 0.75. 所以bit[51 - 0] = 0xC000000000000 对应0.75, 数字简单还好,如果像上述例子复杂,手工计算就复杂了。

其实公式很简单, 即我们以10进制为例,十进制个位数X => ((X+10)/10 -1) = X/10; 如果X=3,计算得到0.3,意思是3占10的0.3(30%). 同理

F占用52个位,我们把这部分数据当52进制处理, 计算F的值本质就是计算F的十六进制占2^52次方的百分比,就是高位(进位)bit 52补1, 然后整体×2^(-52),再减1, 即F = 0x1XXXXXXXXXXXXX × 2^(-52) - 1;

那么对于e > 0 and e < 0x7FF(0xFFFF)

公式: D = (-1)^s x (1+ 小数部分F) x2^(e-1023) 简化为:

公式: D = (-1)^s x (0x1XXXXXXXXXXXXX * 2^(-52)) x2^(e-1023); 其中0xXXXXXXXXXXXXX就是F的52位十六进制数。


对于e = 0,公式 D = (-1)^s x 2^(-1022) x(小数部分F) 简化为:

h公式 D =(-1)^s x 2^(-1022) x(0x1XXXXXXXXXXXXX * 2^(-52) -1);其中0xXXXXXXXXXXXXX就是F的52位十六进制数。

那F的16进制bit[51 - 0] = 0xF9BC87E6A5D21的计算就是:

F = 0x1F9BC87E6A5D21 * 2^(-52) -1 =(0x1F9BC87E6A5D21 / 2^52) - 1

= 8.897009695×10¹⁵ / 2^52 -1

= 1.975533003 - 1 = 0.975533003

那F的16进制bit[51 - 0] = 0xC000000000000的计算就是

F =0x1C000000000000 * 2^(-52) = 0x1C000000000000 / 2^52 - 1

= 7.881299348×10¹⁵ / 2^52 - 1

= 1.75 - 1 = 0.75 (和上面的原始计算结果完全相同)

=============================================================================================================================

 有了完整的计算公式,我们就可以来看下完整的double型十六进制的换算了。

例如double十六进制:3fd5 5555 5555 5555来计算D, s = 0, e = 0x3fd = 1021, F = 0x5555555555555

F按照上面公式换算:

F =0x15555555555555 × 2^(-52) - 1; 那1+F =0x15555555555555 × 2^(-52);

代入公式:

D = (-1)^s x (0x15555555555555 * 2^(-52)) x 2^(e-1023); 把e和s代入:

D = (-1)^0 x(0x15555555555555 * 2^(-52)) x2^(1021-1023); 计算

D = 1 x(0x15555555555555 * 2^(-52)) x 2^(-2); 计算

D = (6.004799503×10¹⁵ / 2^54) = 0.3333333333 = 1/3;

所以Double型1/3在计算机中的十六进制就是3fd5 5555 5555 5555。

其他就不再一一赘述,可以参考维基百科的英文官网,本文只是更深入的解释了double类型数据的转换。


反过来通过浮点十进制来计算double的十六进制数字,例如随机一个doube D = 12.87.

根据上面的公式: D = (-1)^s x (0x1XXXXXXXXXXXXX * 2^(-52)) x2^(e-1023);

12.87 正数,所以s = 0, 因为我们把e-1023换成y, 0x1XXXXXXXXXXXXX = A = (1 + F), F = 0x1XXXXXXXXXXXXX × 2^(-52) - 1;

D = A * 2^y = 12.87, 因为A = (1 + F) , A > 1 and A < 2;

所以 D/2^y > 1 and D/2^y < 2, 得出y = 3, 所以 D / 2^3 = 12.87 / 8 = 1.60875;

所以 A = 1.60875 = 1 + F, F = 0.60875

所以 1+ F = 0x1XXXXXXXXXXXXX × 2^(-52) = 1.60875(前面已经讲了计算百分比的公式)

所以 0x1XXXXXXXXXXXXX = 1.60875×2^52 = 0x19BD70A3DE2D40,

所以F的bit 51 - bit 0就是0x9BD70A3DE2D40, e = 1023 + y = 1023 + 3 = 1026 = 0x402(bit 52-62), s = 0(bit 63)

12.87的二进制就是:0x4029BD70A3DE2D40.

在把这个值还原到浮点十进制, s = 0, e = 0x402 = 1026 , F = 0x9BD70A3DE2D40

D = (-1)^0 * (1 + F*2^(-52)) * 2^(1026-1023) = 1 * (1 + 2.741566274×10¹⁵ / 4.503599627×10¹⁵) * 8

D = (1 + 0.60875) * 8 = 12.87;


Tony

2017.04.09

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值