关于holiman/uint256的代码辅助阅读

本文首先介绍了Big Endian 和 Little Endian的定义和区别,接着介绍uint256的数据结构,以及使用的字节序,然后介绍了如何使用uint256,最后做一下总结。

关于字节序:Big Endian 和 Little Endian

字节序,也就是字节的顺序,指的是多字节的数据在内存中的存放顺序。

Big Endian 和 Little Endian

Big Endian 是指低地址端 存放 高位字节。
Little Endian 是指低地址端 存放 低位字节。

各自的优势

Big Endian: 符号位的判定固定为第一个字节,容易判断正负。
Little Endian: 长度为1,2,4字节的数,排列方式都是一样的,数据类型转换非常方便。

数据结构

uint256的组成由4个uint64组成的数组,并且是用的little-endian序列,也就是说Int[3]是最高有效,Int[0]是最低有效,
type Int [4]uint64

序列化

在Int中定了4中方法用于Int和byte数组之间的转换,函数如下

func (z *Int) SetBytes(buf []byte) *Int
func (z *Int) Bytes32() [32]byte
func (z *Int) Bytes20() [20]byte
func (z *Int) Bytes() []byte

其中转换成bytes的方法有三种,两种是固定长度,一种是不定长。根据代码我们可以看到,不定长的函数Bytes是定长函数Bytes32返回值切片;

// Bytes32 returns the value of z as a 32-byte big-endian array.
func (z *Int) Bytes32() [32]byte {
 // The PutUint64()s are inlined and we get 4x (load, bswap, store) instructions.
 var b [32]byte
 binary.BigEndian.PutUint64(b[0:8], z[3])
 binary.BigEndian.PutUint64(b[8:16], z[2])
 binary.BigEndian.PutUint64(b[16:24], z[1])
 binary.BigEndian.PutUint64(b[24:32], z[0])
 return b
}

// Bytes returns the value of z as a big-endian byte slice.
func (z *Int) Bytes() []byte {
 b := z.Bytes32()
 return b[32-z.ByteLen():]
}

初始化的几种方法

Int的初始化有三种方法,NewInt返回值为0的Int,FromBig是将*big.Int转换成Int,FromHex将16进制的字符串转换成Int。

// NewInt returns a new zero-initialized Int.
func NewInt() *Int {
 return &Int{}
}

func FromBig(b *big.Int) (*Int, bool) 
func FromHex(hex string) (*Int, error)

正负的表示,最大值和最小值

因为Int的String方法返回的是Hex字符串,所以不是很直观的判断到底是正数还是负数,下面的函数可以用来判定,这个Int是正数还是负数;

func (z *Int) Sign() int {
 if z.IsZero() {
  return 0
 }
 if z[3] < 0x8000000000000000 {
  return 1
 }
 return -1
}

上面的函数可以看出只要 z[3] < 0x8000000000000000,即是负数,所以正数最大是:0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,-1表示为
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,最大的负数就是0x8000000000000000000000000000000000000000000000000000000000000000

常用的加减乘除

代码太多,我们简单分析下加减运算,加法运算是针对[4]uint64数组中的数据进行运算,因为最低有效位在[0]uint64中,所以是从下表0开始进行计算的;
Add和AddOverflow的区别是如果最高有效位有进位运算,那么就是Overflow;
同理我们可以自行分析减乘除运算;

// Add sets z to the sum x+y
func (z *Int) Add(x, y *Int) *Int {
 var carry uint64
 z[0], carry = bits.Add64(x[0], y[0], 0)
 z[1], carry = bits.Add64(x[1], y[1], carry)
 z[2], carry = bits.Add64(x[2], y[2], carry)
 z[3], _ = bits.Add64(x[3], y[3], carry)
 return z
}

// Add64 returns the sum with carry of x, y and carry: sum = x + y + carry.
// The carry input must be 0 or 1; otherwise the behavior is undefined.
// The carryOut output is guaranteed to be 0 or 1.
//
// This function's execution time does not depend on the inputs.
func Add64(x, y, carry uint64) (sum, carryOut uint64) {
 sum = x + y + carry
 // The sum will overflow if both top bits are set (x & y) or if one of them
 // is (x | y), and a carry from the lower place happened. If such a carry
 // happens, the top bit will be 1 + 0 + 1 = 0 (&^ sum).
 carryOut = ((x & y) | ((x | y) &^ sum)) >> 63
 return
}


// AddOverflow sets z to the sum x+y, and returns whether overflow occurred
func (z *Int) AddOverflow(x, y *Int) bool {
 var carry uint64
 z[0], carry = bits.Add64(x[0], y[0], 0)
 z[1], carry = bits.Add64(x[1], y[1], carry)
 z[2], carry = bits.Add64(x[2], y[2], carry)
 z[3], carry = bits.Add64(x[3], y[3], carry)
 return carry != 0
}

// Sub sets z to the difference x-y
func (z *Int) Sub(x, y *Int) *Int {
 var carry uint64
 z[0], carry = bits.Sub64(x[0], y[0], 0)
 z[1], carry = bits.Sub64(x[1], y[1], carry)
 z[2], carry = bits.Sub64(x[2], y[2], carry)
 z[3], _ = bits.Sub64(x[3], y[3], carry)
 return z
}

// Sub32 returns the difference of x, y and borrow, diff = x - y - borrow.
// The borrow input must be 0 or 1; otherwise the behavior is undefined.
// The borrowOut output is guaranteed to be 0 or 1.
//
// This function's execution time does not depend on the inputs.
func Sub32(x, y, borrow uint32) (diff, borrowOut uint32) {
 diff = x - y - borrow
 // The difference will underflow if the top bit of x is not set and the top
 // bit of y is set (^x & y) or if they are the same (^(x ^ y)) and a borrow
 // from the lower place happens. If that borrow happens, the result will be
 // 1 - 1 - 1 = 0 - 0 - 1 = 1 (& diff).
 borrowOut = ((^x & y) | (^(x ^ y) & diff)) >> 31
 return
}

逻辑运算或与非

逻辑运算就的原理很简单,就是按位进行逻辑运算,具体的代码如下

// Not sets z = ^x and returns z.
func (z *Int) Not(x *Int) *Int {
 z[3], z[2], z[1], z[0] = ^x[3], ^x[2], ^x[1], ^x[0]
 return z
}

// Or sets z = x | y and returns z.
func (z *Int) Or(x, y *Int) *Int {
 z[0] = x[0] | y[0]
 z[1] = x[1] | y[1]
 z[2] = x[2] | y[2]
 z[3] = x[3] | y[3]
 return z
}

// And sets z = x & y and returns z.
func (z *Int) And(x, y *Int) *Int {
 z[0] = x[0] & y[0]
 z[1] = x[1] & y[1]
 z[2] = x[2] & y[2]
 z[3] = x[3] & y[3]
 return z
}

// Xor sets z = x ^ y and returns z.
func (z *Int) Xor(x, y *Int) *Int {
 z[0] = x[0] ^ y[0]
 z[1] = x[1] ^ y[1]
 z[2] = x[2] ^ y[2]
 z[3] = x[3] ^ y[3]
 return z
}

总结

本文带领大家简单的了解uin256库的实现,目前最新版代码的以太坊虚拟机使用的就是这个库。还有很多的函数以及用法需要大家去慢慢探索。
未来可期,一路前行!


sixgo
156 声望4 粉丝

这里有BUG!