之所以用“血案”是因为在了解 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要加上偏移量127float64要加上偏移量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

至此,破案了。