由大小端字节序引发的“血案”

之所以用“血案”是因为在了解Go package encoding/binary的时候,突然就触及到了一个明明很基础但是就是想起不来的知识点。

字节序

“血案”由字节序引发。那就先聊一下字节序。字节序分大端(bigEndian)、小端(littleEndian)。关于大端小端概念的起源,还有一个有意思的故事:
我下面要告诉你的是,Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由Blefuscu的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。”
故事之外,现在我们普遍认为的大端小端定义如下:
大端:随着内存地址的增加,由高位到低位存储数据。网络字节序一般为大端序。
小端:随着内存地址的增加,由低位到高位存储数据。x86 cpu内部字节序一般为小端序。
举个例子:有一个数0x14 25 0A 0B,其中0x14是其最高位,0x0B是其最低位。那么:
endian

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
那么问题又来了,浮点数是怎么存的来着??!这么基础的东西为毛没有什么印象?当时我就慌了:怎么办?连这个都不会了。此处加戏一万字。
然后开始扒资料,信息如下:
floats
在此处要分别解释一下sign(正负)、exponent(指数)、Mantissa(尾数)的概念,举个例子:
有个数10.25,其二进制:1010.01=1.01001 * (2 ^ 3), 所以10.25的指数就是3,尾数就是1.01001sign为正。
其中有几个点要注意:
  1. 指数有一个额外的处理环节,float32要加上偏移量127, float64要加上偏移量1023;
  2. 因为每个尾数都是1开头,所以省去这个一,节省了一位;
那么10.25的指数就是2+127=130=0b1000 0010,尾数为01001,少于23位的会低位补零。即10.25=0b0 1000 0010 0100 1000 0000 0000 0000 0000 000
基于此,我们就能算出官方例子中所给序列的浮点数值了:
  • Sign0;
  • 指数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
至此,破案了。

评论

此博客中的热门博文

将博客部署到星际文件系统(IPFS)

高中地理必修一知识点总结

一场CF的台前幕后(下)