Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
radkomih committed Sep 14, 2023
1 parent a361efc commit 766d8e2
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 113 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

[![codecov](https://codecov.io/gh/LimeChain/goscale/branch/master/graph/badge.svg?token=3OQPC6VNMB)](https://codecov.io/gh/LimeChain/goscale)

The SCALE types in Go are represented by a set of custom-defined types that implement the `Encodable` interface. Each type also has a corresponding `Decode` function. Note that the type to which data should be decoded is inferred from the context and is not self-contained in the SCALE-encoded data.
The SCALE types in Go are represented by a set of custom-defined types that implement the `Encodable` interface.
Each type also has a corresponding decode function using the convention `Decode<TypeName>`. Note that the type to which data should be decoded is inferred from the context and is not self-contained in the SCALE-encoded data.

One exception is the `Tuple` type. It doesn't have methods attached. Instead, there are `EncodeTuple` and `DecodeTuple` functions that can be invoked with any custom struct that embeds the `Tuple` interface.

Expand Down
3 changes: 1 addition & 2 deletions fixed_length_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,8 @@ func Test_DecodeU128(t *testing.T) {
}

func Test_NewU128FromBigIntPanic(t *testing.T) {
t.Skip()
t.Run("Exceeds U128", func(t *testing.T) {
value, ok := new(big.Int).SetString("340282366920938463463374607431768211456", 10) // MaxUint128 + 1
value, ok := new(big.Int).SetString("340282366920938463463374607431768211456", 10) // MaxU128 + 1
if !ok {
panic("not ok")
}
Expand Down
6 changes: 1 addition & 5 deletions numeric.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ import (

var ErrOverflow = errors.New("overflow")

const MinInt64 = 1 << 63
const MaxInt64 = ^uint64(0) >> 1
const MaxUint64 = ^uint64(0)

// Go's primitive numeric types don't have a common interface.
// To be able to write generic code that works with any numeric
// type, including some custom types (U128/I128), we need to
Expand Down Expand Up @@ -808,7 +804,7 @@ func To[N Numeric](n Numeric) N {
}
}

func fromAnyNumberTo128[N Numeric](n any) N {
func fromAnyNumberTo128Bits[N Numeric](n any) N {
switch n := n.(type) {
case int:
return bigIntToGeneric[N](new(big.Int).SetInt64(int64(n)))
Expand Down
86 changes: 30 additions & 56 deletions numeric_i128.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,76 +6,64 @@ import (
"math/bits"
)

// using little endian byte order
// little endian byte order
// [0] least significant bits
// [1] most significant bits
type I128 [2]U64

func NewI128(n any) I128 {
return fromAnyNumberTo128[I128](n)
return fromAnyNumberTo128Bits[I128](n)
}

func bigIntToI128(n *big.Int) I128 {
bytes := make([]byte, 16)
n.FillBytes(bytes)

reverseSlice(bytes)

var result [2]U64
result[0] = U64(binary.LittleEndian.Uint64(bytes[:8]))
result[1] = U64(binary.LittleEndian.Uint64(bytes[8:]))
var result = I128{
U64(binary.LittleEndian.Uint64(bytes[:8])),
U64(binary.LittleEndian.Uint64(bytes[8:])),
}

if n.Sign() < 0 {
result[0] = ^result[0]
result[1] = ^result[1]

result[0]++
if result[0] == 0 {
result[1]++
}
result = negateI128(result)
}

return I128{
result[0],
result[1],
}
return result
}

func (n I128) ToBigInt() *big.Int {
isNegative := n[1]&U64(MinInt64) != 0
if isNegative {
if n[0] == 0 {
n[1]--
}
n[0]--
isNegative := n.isNegative()

n[0] = ^n[0]
n[1] = ^n[1]
if isNegative {
n = negateI128(n)
}

bytes := make([]byte, 16)
binary.BigEndian.PutUint64(bytes[:8], uint64(n[1]))
binary.BigEndian.PutUint64(bytes[8:], uint64(n[0]))

result := big.NewInt(0).SetBytes(bytes)

if isNegative {
result.Neg(result)
}

return result
}

// ffffffffffffffff ffffffffffffff7f
// ff ff ff ff ff ff ff ff | 7f ff ff ff ff ff ff ff
func MaxI128() I128 {
return I128{
U64(MaxUint64),
U64(MaxInt64), // most significant bit set to 0
U64(^uint64(0)),
U64(^uint64(0) >> 1),
}
}

// 0000000000000000 0000000000000080
// 00 00 00 00 00 00 00 00 | 80 00 00 00 00 00 00 00
func MinI128() I128 {
return I128{
U64(0),
U64(MinInt64), // most significant bit set to 1
U64(1 << 63),
}
}

Expand All @@ -84,30 +72,18 @@ func (n I128) Interface() Numeric {
}

func (a I128) Add(b Numeric) Numeric {
// return bigIntToI128(
// new(big.Int).Add(a.ToBigInt(), b.(I128).ToBigInt()),
// )

sumLow, carry := bits.Add64(uint64(a[0]), uint64(b.(I128)[0]), 0)
sumHigh, _ := bits.Add64(uint64(a[1]), uint64(b.(I128)[1]), carry)
return I128{U64(sumLow), U64(sumHigh)}
}

func (a I128) Sub(b Numeric) Numeric {
// return bigIntToI128(
// new(big.Int).Sub(a.ToBigInt(), b.(I128).ToBigInt()),
// )

diffLow, borrow := bits.Sub64(uint64(a[0]), uint64(b.(I128)[0]), 0)
diffHigh, _ := bits.Sub64(uint64(a[1]), uint64(b.(I128)[1]), borrow)
return I128{U64(diffLow), U64(diffHigh)}
}

func (a I128) Mul(b Numeric) Numeric {
// return bigIntToI128(
// new(big.Int).Mul(a.ToBigInt(), b.(I128).ToBigInt()),
// )

negA := a[1]>>(64-1) == 1
negB := b.(I128)[1]>>(64-1) == 1

Expand All @@ -126,8 +102,8 @@ func (a I128) Mul(b Numeric) Numeric {

result := I128{U64(low), U64(high)}

// determine the sign of the result and adjust if necessary
if negA != negB { // If only one of them is negative
// if one of the operands is negative the result is also negative
if negA != negB {
return negateI128(result)
}
return result
Expand Down Expand Up @@ -200,7 +176,6 @@ func (a I128) TrailingZeros() Numeric {
func (a I128) SaturatingAdd(b Numeric) Numeric {
sumLow, carry := bits.Add64(uint64(a[0]), uint64(b.(I128)[0]), 0)
sumHigh, _ := bits.Add64(uint64(a[1]), uint64(b.(I128)[1]), carry)

// check for overflow
if a[1]&(1<<63) == 0 && b.(I128)[1]&(1<<63) == 0 && sumHigh&(1<<63) != 0 {
return MaxI128()
Expand All @@ -215,7 +190,6 @@ func (a I128) SaturatingAdd(b Numeric) Numeric {
func (a I128) SaturatingSub(b Numeric) Numeric {
diffLow, borrow := bits.Sub64(uint64(a[0]), uint64(b.(I128)[0]), 0)
diffHigh, _ := bits.Sub64(uint64(a[1]), uint64(b.(I128)[1]), borrow)

// check for overflow
if a[1]&(1<<63) == 0 && b.(I128)[1]&(1<<63) != 0 && diffHigh&(1<<63) != 0 {
return MaxI128()
Expand All @@ -229,24 +203,24 @@ func (a I128) SaturatingSub(b Numeric) Numeric {

func (a I128) SaturatingMul(b Numeric) Numeric {
result := new(big.Int).Mul(a.ToBigInt(), b.(I128).ToBigInt())

// Define the maximum and minimum representable I128 values
// define the maximum and minimum representable I128 values
maxI128 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 127), big.NewInt(1))
minI128 := new(big.Int).Neg(new(big.Int).Lsh(big.NewInt(1), 127))

// Clamp to the representable range
if result.Cmp(maxI128) > 0 {
return bigIntToI128(maxI128)
} else if result.Cmp(minI128) < 0 {
return bigIntToI128(minI128)
}

return bigIntToI128(result)
}

func negateI128(i I128) I128 {
// using two's complement: flip all bits and add 1
negLow, carry := bits.Add64(^uint64(i[0]), 1, 0)
negHigh, _ := bits.Add64(^uint64(i[1]), 0, carry)
func (n I128) isNegative() bool {
return n[1]&U64(1<<63) != 0
}

func negateI128(n I128) I128 {
// two's complement representation
negLow, carry := bits.Add64(^uint64(n[0]), 1, 0)
negHigh, _ := bits.Add64(^uint64(n[1]), 0, carry)
return I128{U64(negLow), U64(negHigh)}
}
38 changes: 22 additions & 16 deletions numeric_i64.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package goscale

import (
"math"
"math/big"
"math/bits"
)

Expand Down Expand Up @@ -85,38 +84,45 @@ func (a I64) TrailingZeros() Numeric {
}

func (a I64) SaturatingAdd(b Numeric) Numeric {
sum, _ := bits.Add64(uint64(a), uint64(b.(I64)), 0)
// check for overflow
if a > 0 && b.(I64) > 0 && a > (math.MaxInt64-b.(I64)) {
if a >= 0 && b.(I64) >= 0 && int64(sum) < int64(a) {
return I64(math.MaxInt64)
}
// check for underflow
if a < 0 && b.(I64) < 0 && a < (math.MinInt64-b.(I64)) {
if a < 0 && b.(I64) < 0 && int64(sum) >= int64(a) {
return I64(math.MinInt64)
}
return a + b.(I64)
return I64(sum)
}

func (a I64) SaturatingSub(b Numeric) Numeric {
diff := a - b.(I64)
if diff > b.(I64) {
diff, borrow := bits.Sub64(uint64(a), uint64(b.(I64)), 0)
// check for underflow
if a >= 0 && b.(I64) < 0 && (borrow == 1 || int64(diff) < int64(a)) {
return I64(math.MaxInt64)
}
// check for overflow
if a < 0 && b.(I64) >= 0 && (borrow == 1 || int64(diff) >= int64(a)) {
return I64(math.MinInt64)
}
return diff
return I64(diff)
}

func (a I64) SaturatingMul(b Numeric) Numeric {
if a == 0 || b.(I64) == 0 {
if uint64(a) == 0 || uint64(b.(I64)) == 0 {
return I64(0)
}

result := big.NewInt(0).Mul(big.NewInt(int64(a)), big.NewInt(int64(b.(I64))))
// check for overflow
if result.Cmp(big.NewInt(math.MaxInt64)) > 0 {
hi, lo := bits.Mul64(uint64(a), uint64(b.(I64)))
// if hi is not zero, there is an overflow
if hi != 0 || (a > 0 && b.(I64) > 0 && int64(lo) < 0) || (a < 0 && b.(I64) < 0 && int64(lo) > 0) {
if (a < 0 && b.(I64) >= 0) || (a >= 0 && b.(I64) < 0) {
// negative overflow
return I64(math.MinInt64)
}
// positive overflow
return I64(math.MaxInt64)
}
// check for underflow
if result.Cmp(big.NewInt(math.MinInt64)) < 0 {
return I64(math.MinInt64)
}
return I64(result.Int64())
return I64(int64(lo))
}
6 changes: 3 additions & 3 deletions numeric_i8.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (a I8) SaturatingAdd(b Numeric) Numeric {
// check for overflow and underflow
if sum > int16(math.MaxInt8) {
return I8(math.MaxInt8)
} else if sum < math.MinInt8 {
} else if sum < int16(math.MinInt8) {
return I8(math.MinInt8)
}
return I8(sum)
Expand All @@ -109,14 +109,14 @@ func (a I8) SaturatingSub(b Numeric) Numeric {

func (a I8) SaturatingMul(b Numeric) Numeric {
if a == 0 || b.(I8) == 0 {
return U8(0)
return I8(0)
}

product := int16(a) * int16(b.(I8))
// check for overflow and underflow
if product > int16(math.MaxInt8) {
return I8(math.MaxInt8)
} else if product < math.MinInt8 {
} else if product < int16(math.MinInt8) {
return I8(math.MinInt8)
}
return I8(product)
Expand Down
45 changes: 44 additions & 1 deletion numeric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package goscale

import (
"math"
"math/big"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -471,7 +472,7 @@ func Test_SaturatingSub(t *testing.T) {
{"0-1", U64(0), U64(1), U64(0)},
{"0-MaxU64", U64(0), U64(math.MaxUint64), U64(0)},
{"0-1", I64(0), I64(1), I64(-1)},
{"MinU64-1", I64(math.MinInt64), I64(1), I64(math.MinInt64)},
{"MinI64-1", I64(math.MinInt64), I64(1), I64(math.MinInt64)},
{"0-1", NewU128(0), NewU128(1), NewU128(0)},
{"0-MaxU128", NewU128(0), MaxU128(), NewU128(0)},
{"0-1", NewI128(0), NewI128(1), NewI128(-1)},
Expand Down Expand Up @@ -849,3 +850,45 @@ func Test_Gte(t *testing.T) {
})
}
}

func Test_U128ToBigInt(t *testing.T) {
bnMaxU128Value, _ := new(big.Int).SetString("340282366920938463463374607431768211455", 10) // ffffffffffffffff ffffffffffffffff - big endian
// bnMinU128Value, _ := new(big.Int).SetString("0", 10) // 0000000000000000 0000000000000000 - big endian

testExamples := []struct {
label string
input U128
expectation *big.Int
}{
{"340282366920938463463374607431768211455", MaxU128(), bnMaxU128Value},
// {"0", NewU128(0), bnMinU128Value},
}

for _, testExample := range testExamples {
t.Run(testExample.label, func(t *testing.T) {
result := testExample.input.ToBigInt()
assert.Equal(t, testExample.expectation, result)
})
}
}

func Test_I128ToBigInt(t *testing.T) {
bnMaxI128Value, _ := new(big.Int).SetString("170141183460469231731687303715884105727", 10) // 7fffffffffffffff ffffffffffffffff - big endian
bnMinI128Value, _ := new(big.Int).SetString("-170141183460469231731687303715884105728", 10) // 8000000000000000 0000000000000000 - big endian

testExamples := []struct {
label string
input I128
expectation *big.Int
}{
{"170141183460469231731687303715884105727", MaxI128(), bnMaxI128Value},
{"-170141183460469231731687303715884105728", MinI128(), bnMinI128Value},
}

for _, testExample := range testExamples {
t.Run(testExample.label, func(t *testing.T) {
result := testExample.input.ToBigInt()
assert.Equal(t, testExample.expectation, result)
})
}
}
Loading

0 comments on commit 766d8e2

Please sign in to comment.