在网上并没有任何信息或则资料介绍关于浮点数的大小端转换的原理的问题,大小端是不同的内存存储实现方式,大端更符合人的阅读习惯,而小端则是更适合CPU读取。
我先说整型,来说明大小端在内存中的存储方式,整型数据在内存中就是符号和数据两个部分,而且符号占一位,用32位整数举例,大端方式在内存中排列的顺序如下:
[0...7] [8...15] [16...23] [24...31]
0是标志位,1-31是数据。地址由小向大增加,而数据从高位往低位放,我们用 999 来说明,内存中存放的数据是:00 00 03 E7
而小端方式呢,则在内存中如下存储:
[24...31] [16...23] [8...15] [0...7]
地址由小向大增加,而数据从低位往高位放,同样用 999 来说明,内存中存放的数据是:E7 03 00 00
而浮点数则比整型复杂一些。按照ieee的浮点数格式,32位浮点型的格式如下,这个也是大端的存储方式:
B0 B1 B2 B3
[SEEEEEEE] [EDDDDDDD] [DDDDDDDD] [DDDDDDDD]
S表示符号,E表示指数,D表示分数。
小端正好相反:
B3 B2 B1 B0
[DDDDDDDD] [DDDDDDDD] [EDDDDDDD] [SEEEEEEE]
符号不用解释了,老规矩。
E就是指数,这个32位浮点数的指数是8位,指数不是 10 的幂;而是 2 的幂。 存储的 8 位指数的范围为 -127 到 127,并存储为 0 到 254。
因此指数拿到之后,必须先减去127,才是真实的值。如果是double,则是减去1023。
而分数位则稍微复杂点,这儿的分数不是直接表示小数点后面的位数,而是指 2 的 e次方与 2 的 e+1 次方之间的值的一个比例。
举个例子,2.1,这个值,如果按ieee格式来分解,就是2的1次方+0.1,
那么符号S位为0,指数位E为128,而分数位D则为419430。
什么意思?你可以用这个公式来算出结果:
2 ^ 1 + (2 ^ 2 - 2 ^ 1) * (419430 / 8388608)
那么就是2 + 2 * 0.0499999523162841796875 = 2 + 0.099999904632568359375。
答案是不是约等于2.1 ? 8388608这个值就是2^23。
这个数存储在内存中的小端形式是:66 66 06 40
如果存储在大端系统,则形式为:40 06 66 66
换成二进制再看看
dddddddd dddddddd eddddddd seeeeeee
01100110 01100110 00000110 01000000
S:0
E:10000000 = 128
D:0000110 01100110 01100110 = 419430
是不是完美的符合了预期?
我们再换一个小一点的,0.26,
E是-2(1/4到1/2之间),值为125,而D的数值为335544.
带入公式:
2^-2 + ( 2^-1 - 2^-2 ) * (335544 / 8388608) = 1/4 + 0.0099999904632568359375。
正好是约等于0.26。
了解这些基本知识之后,我们就可以对浮点数的大小端问题进行处理:
事实上浮点数在内存中按大小端存储的方式和整数完全一致。如果要转换大小端,就按照整数的方式来处理就行了,另外,转换的时候注意不要直接将float作为int进行转换,会被编译器的生成指令处理成整数,用union来解决这个问题。
我们可以轻松的写出一个取浮点数各个位的结构体。
小端是这样的代码:
union tagFloat
{
float value;
struct
{
unsigned d:23;
unsigned e:8;
unsigned s:1;
} bits;
};
大端正好相反
union tagFloat
{
float value;
struct
{
unsigned s:1;
unsigned e:8;
unsigned d:23;
} bits;
};
另外,如果想理解C++结构体内的bitfield是怎么生效的,可以查阅一下相关的资料,这儿就不再对这个问题进行展开。