关于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库的实现,目前最新版代码的以太坊虚拟机使用的就是这个库。还有很多的函数以及用法需要大家去慢慢探索。
未来可期,一路前行!