由大小端字节序引发的“血案”
之所以用“血案”是因为在了解Go package
encoding/binary
的时候,突然就触及到了一个明明很基础但是就是想起不来的知识点。字节序
“血案”由字节序引发。那就先聊一下字节序。字节序分大端(
bigEndian
)、小端(littleEndian
)。关于大端小端概念的起源,还有一个有意思的故事:我下面要告诉你的是,Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由Blefuscu的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。”
故事之外,现在我们普遍认为的大端小端定义如下:
大端:随着内存地址的增加,由高位到低位存储数据。网络字节序一般为大端序。
小端:随着内存地址的增加,由低位到高位存储数据。x86 cpu内部字节序一般为小端序。
举个例子:有一个数
0x14 25 0A 0B
,其中0x14
是其最高位,0x0B
是其最低位。那么:Go encoding/binary
Package binary implements simple translation between numbers and byte sequences and encoding and decoding of varints.
在这个包中,有一个
Read
函数: func Read(r io.Reader, order ByteOrder, data interface{}) error
官方例子:
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
func main() {
var pi float64
b := []byte{0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}
buf := bytes.NewReader(b)
err := binary.Read(buf, binary.LittleEndian, &pi)
if err != nil {
fmt.Println("binary.Read failed:", err)
}
fmt.Print(pi)
}
//output
3.141592653589793
问题来了:经过
binary.Read
方法,数据小端序列成pi
的整个过程是怎样的?
首先:小端序,低对低高对高,然后序列就是从左往右的顺序,所以序列最左为最低,序列最高为最高。那么原数应该为:
对应于二进制
0x40 09 21 fb 54 44 2d 18
对应于二进制
0100 0000 0000 1001 0010 0001 1111 1011 0101 0100 0100 0100 0010 1101 0001 1000
。
那么问题又来了,浮点数是怎么存的来着??!这么基础的东西为毛没有什么印象?当时我就慌了:怎么办?连这个都不会了。此处加戏一万字。
然后开始扒资料,信息如下:
在此处要分别解释一下sign(正负)、exponent(指数)、Mantissa(尾数)的概念,举个例子:
有个数
其中有几个点要注意:
10.25
,其二进制:1010.01=1.01001 * (2 ^ 3)
, 所以10.25
的指数就是3
,尾数就是1.01001
,sign
为正。其中有几个点要注意:
- 指数有一个额外的处理环节,
float32
要加上偏移量127
,float64
要加上偏移量1023
; - 因为每个尾数都是
1
开头,所以省去这个一,节省了一位;
那么
10.25
的指数就是2+127=130=0b1000 0010
,尾数为01001
,少于23
位的会低位补零。即10.25=0b0 1000 0010 0100 1000 0000 0000 0000 0000 000
。
基于此,我们就能算出官方例子中所给序列的浮点数值了:
- 其
Sign
为0
; - 指数
11
位为ex = 0b100 0000 0000
,去除偏移量1023
,得到1
,即指数为1
; - 尾数为加上省去的
1
和小数点为manti = 1.1001 0010 0001 1111 1011 0101 0100 0100 0100 0010 1101 0001 1000
,
那么原数就是
+manti * 2 = 11.0010 0100 0011 1111 0110 1010 1000 1000 1000 0101 1010 0011 000
整数部分为
3
,小数部分为:2*(1/(2**4))+4*(1/(2**8))+3*(1/(2**12))+15*(1/(2**16))+6*(1/(2**20))+10*(1/(2**24))+8*(1/(2**28))+8*(1/(2**32))+8*(1/(2**36))+5*(1/(2**40))+10*(1/(2**44))+3*(1/(2**48))=0.14159265358979312
,所以Read
函数处理过的结果为3.14159265358979312
。
至此,破案了。
评论
发表评论