mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
dfd4f6978f
In some cases n.Add() can reuse the []Word buffer and n.Sub() reallocate it
away. If that happens, we're out of luck with 0.99.0+ versions (since
3945e81857
). I'm not sure why it does that, bit
width doesn't change in most of the cases and even if it does, we still have
enough of it in cap() to hold the old Abs() value (when we have a negative
value we in fact decreate its Abs() first and increase it back
afterwards). Still, that's what we have.
So when we have processTokenTransfer() doing Neg/Neg in-place its value is not
affected, but the original []Word bits that are reused by amount value are
(they're shared initially, Amount: *amount).
name old time/op new time/op delta
ToPreallocatedBytes-8 65.8ns ± 2% 45.6ns ± 2% -30.73% (p=0.008 n=5+5)
name old alloc/op new alloc/op delta
ToPreallocatedBytes-8 0.00B 0.00B ~ (all equal)
name old allocs/op new allocs/op delta
ToPreallocatedBytes-8 0.00 0.00 ~ (all equal)
160 lines
3.1 KiB
Go
160 lines
3.1 KiB
Go
package bigint
|
|
|
|
import (
|
|
"math"
|
|
"math/big"
|
|
"math/bits"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
|
)
|
|
|
|
const (
|
|
// MaxBytesLen is the maximum length of a serialized integer suitable for Neo VM.
|
|
MaxBytesLen = 32 // 256-bit signed integer
|
|
// wordSizeBytes is a size of a big.Word (uint) in bytes.
|
|
wordSizeBytes = bits.UintSize / 8
|
|
)
|
|
|
|
var bigOne = big.NewInt(1)
|
|
|
|
// FromBytesUnsigned converts data in little-endian format to an unsigned integer.
|
|
func FromBytesUnsigned(data []byte) *big.Int {
|
|
bs := slice.CopyReverse(data)
|
|
return new(big.Int).SetBytes(bs)
|
|
}
|
|
|
|
// FromBytes converts data in little-endian format to
|
|
// an integer.
|
|
func FromBytes(data []byte) *big.Int {
|
|
n := new(big.Int)
|
|
size := len(data)
|
|
if size == 0 {
|
|
if data == nil {
|
|
panic("nil slice provided to `FromBytes`")
|
|
}
|
|
return big.NewInt(0)
|
|
}
|
|
|
|
isNeg := data[size-1]&0x80 != 0
|
|
|
|
size = getEffectiveSize(data, isNeg)
|
|
if size == 0 {
|
|
if isNeg {
|
|
return big.NewInt(-1)
|
|
}
|
|
|
|
return big.NewInt(0)
|
|
}
|
|
|
|
lw := size / wordSizeBytes
|
|
ws := make([]big.Word, lw+1)
|
|
for i := 0; i < lw; i++ {
|
|
base := i * wordSizeBytes
|
|
for j := base + 7; j >= base; j-- {
|
|
ws[i] <<= 8
|
|
ws[i] ^= big.Word(data[j])
|
|
}
|
|
}
|
|
|
|
for i := size - 1; i >= lw*wordSizeBytes; i-- {
|
|
ws[lw] <<= 8
|
|
ws[lw] ^= big.Word(data[i])
|
|
}
|
|
|
|
if isNeg {
|
|
for i := 0; i <= lw; i++ {
|
|
ws[i] = ^ws[i]
|
|
}
|
|
|
|
shift := byte(wordSizeBytes-size%wordSizeBytes) * 8
|
|
ws[lw] = ws[lw] & (^big.Word(0) >> shift)
|
|
|
|
n.SetBits(ws)
|
|
n.Neg(n)
|
|
|
|
return n.Sub(n, bigOne)
|
|
}
|
|
|
|
return n.SetBits(ws)
|
|
}
|
|
|
|
// getEffectiveSize returns the minimal number of bytes required
|
|
// to represent a number (two's complement for negatives).
|
|
func getEffectiveSize(buf []byte, isNeg bool) int {
|
|
var b byte
|
|
if isNeg {
|
|
b = 0xFF
|
|
}
|
|
|
|
size := len(buf)
|
|
for ; size > 0; size-- {
|
|
if buf[size-1] != b {
|
|
break
|
|
}
|
|
}
|
|
|
|
return size
|
|
}
|
|
|
|
// ToBytes converts an integer to a slice in little-endian format.
|
|
// Note: NEO3 serialization differs from default C# BigInteger.ToByteArray()
|
|
// when n == 0. For zero is equal to empty slice in NEO3.
|
|
//
|
|
// https://github.com/neo-project/neo-vm/blob/master/src/neo-vm/Types/Integer.cs#L16
|
|
func ToBytes(n *big.Int) []byte {
|
|
return ToPreallocatedBytes(n, []byte{})
|
|
}
|
|
|
|
// ToPreallocatedBytes converts an integer to a slice in little-endian format using the given
|
|
// byte array for conversion result.
|
|
func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
|
|
sign := n.Sign()
|
|
if sign == 0 {
|
|
return data[:0]
|
|
}
|
|
|
|
if sign < 0 {
|
|
bits := n.Bits()
|
|
carry := true
|
|
nonZero := false
|
|
for i := range bits {
|
|
if carry {
|
|
bits[i]--
|
|
carry = (bits[i] == math.MaxUint)
|
|
}
|
|
nonZero = nonZero || (bits[i] != 0)
|
|
}
|
|
defer func() {
|
|
var carry = true
|
|
for i := range bits {
|
|
if carry {
|
|
bits[i]++
|
|
carry = (bits[i] == 0)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
if !nonZero { // n == -1
|
|
return append(data[:0], 0xFF)
|
|
}
|
|
}
|
|
|
|
lb := n.BitLen()/8 + 1
|
|
|
|
if c := cap(data); c < lb {
|
|
data = make([]byte, lb)
|
|
} else {
|
|
data = data[:lb]
|
|
}
|
|
_ = n.FillBytes(data)
|
|
slice.Reverse(data)
|
|
|
|
if sign == -1 {
|
|
for i := range data {
|
|
data[i] = ^data[i]
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|