From 8abeec4f3a68f1f6f908abe55b8eadb79915bd87 Mon Sep 17 00:00:00 2001 From: failfmi Date: Tue, 26 Sep 2023 12:42:20 +0300 Subject: [PATCH] refactor: types and calculations (#83) --- README.md | 54 +- length_compact.go => compact.go | 0 length_compact_test.go => compact_test.go | 0 empty.go | 4 - fixed_length.go | 202 ----- fixed_length_test.go | 499 ---------- i128.go | 199 ++++ i128_test.go | 320 +++++++ i16.go | 17 + i16_test.go | 53 ++ i32.go | 17 + i32_test.go | 54 ++ i64.go | 17 + i64_test.go | 50 + i8.go | 18 + i8_test.go | 57 ++ math.go | 139 +++ math_test.go | 270 ++++++ numeric.go | 808 +---------------- numeric_i128.go | 230 ----- numeric_i16.go | 123 --- numeric_i32.go | 123 --- numeric_i64.go | 128 --- numeric_i8.go | 123 --- numeric_test.go | 1008 +-------------------- numeric_u128.go | 172 ---- numeric_u16.go | 113 --- numeric_u32.go | 112 --- numeric_u64.go | 120 --- numeric_u8.go | 115 --- option_test.go | 12 +- u128.go | 145 +++ u128_test.go | 303 +++++++ u16.go | 27 + u16_test.go | 51 ++ u32.go | 27 + u32_test.go | 50 + u64.go | 27 + u64_test.go | 50 + u8.go | 22 + u8_test.go | 51 ++ 41 files changed, 2015 insertions(+), 3895 deletions(-) rename length_compact.go => compact.go (100%) rename length_compact_test.go => compact_test.go (100%) delete mode 100644 fixed_length.go delete mode 100644 fixed_length_test.go create mode 100644 i128.go create mode 100644 i128_test.go create mode 100644 i16.go create mode 100644 i16_test.go create mode 100644 i32.go create mode 100644 i32_test.go create mode 100644 i64.go create mode 100644 i64_test.go create mode 100644 i8.go create mode 100644 i8_test.go create mode 100644 math.go create mode 100644 math_test.go delete mode 100644 numeric_i128.go delete mode 100644 numeric_i16.go delete mode 100644 numeric_i32.go delete mode 100644 numeric_i64.go delete mode 100644 numeric_i8.go delete mode 100644 numeric_u128.go delete mode 100644 numeric_u16.go delete mode 100644 numeric_u32.go delete mode 100644 numeric_u64.go delete mode 100644 numeric_u8.go create mode 100644 u128.go create mode 100644 u128_test.go create mode 100644 u16.go create mode 100644 u16_test.go create mode 100644 u32.go create mode 100644 u32_test.go create mode 100644 u64.go create mode 100644 u64_test.go create mode 100644 u8.go create mode 100644 u8_test.go diff --git a/README.md b/README.md index 10f4488..fed7dfa 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ One exception is the `Tuple` type. It doesn't have methods attached. Instead, th Some quirks deserve mention. For example, the `FixedSequence` type, which has the same representation as the `Sequence` type, facilitates the encoding of arrays. As arrays are fixed-size sequences, they cannot be encoded as the `Sequence` type. Note that there are no type checks on the size. -Additionally, there's a `Numeric` interface that's implemented by all primitive (wrapper) types, as well as the custom `U128` and `I128` types. This allows for the writing of more generic code that works with any numeric type, since Go's primitive numeric types don't share a common interface. Although this could be extracted into a separate package, to define encoding/decoding methods, the type must reside within the same package. Introducing this separation would necessitate the definition of new types and their respective encoding/decoding methods, leading to frequent type conversions – not an ideal scenario, e.g., `goscale.U8(numeric.U8(1))`. - -The use of custom-defined types and generics reduces reliance on reflection, which isn't fully supported by Tinygo. +The use of custom-defined types and generics reduces reliance on reflection, which isn't fully supported by TinyGo. --- @@ -60,16 +58,16 @@ The use of custom-defined types and generics reduces reliance on reflection, whi ## [Dictionary](https://github.com/LimeChain/goscale/blob/master/dictionary.go) -| SCALE/Rust | Go | -| ------------------ | -------------------------- | -| | `goscale.Dictionary[K, V]` | +| SCALE/Rust | Go | +|------------|----------------------------| +| | `goscale.Dictionary[K, V]` | ## [Empty](https://github.com/LimeChain/goscale/blob/master/empty.go) -| SCALE/Rust | Go | -| ------------------ | --------------- | -| | `goscale.Empty` | +| SCALE/Rust | Go | +|------------|-----------------| +| | `goscale.Empty` | ## [VaryingData](https://github.com/LimeChain/goscale/blob/master/varying_data.go) @@ -82,29 +80,29 @@ The use of custom-defined types and generics reduces reliance on reflection, whi ## [Option](https://github.com/LimeChain/goscale/blob/master/option.go) -| SCALE/Rust | Go | -| ------------------ | ------------------------ | -| `Option` | `Option[goscale.Bool]` | -| `Option` | `Option[goscale.I8]` | -| `Option` | `Option[goscale.U8]` | -| `Option` | `Option[goscale.I16]` | -| `Option` | `Option[goscale.U16]` | -| `Option` | `Option[goscale.I32]` | -| `Option` | `Option[goscale.U32]` | -| `Option` | `Option[goscale.I64]` | -| `Option` | `Option[goscale.U64]` | -| `Option` | `Option[goscale.I128]` | -| `Option` | `Option[goscale.U128]` | -| `Option` | `Option[Sequence[U8]]` | -| `OptionBool` | `OptionBool` | -| `None` | `nil` | +| SCALE/Rust | Go | +|-----------------|------------------------| +| `Option` | `Option[goscale.Bool]` | +| `Option` | `Option[goscale.I8]` | +| `Option` | `Option[goscale.U8]` | +| `Option` | `Option[goscale.I16]` | +| `Option` | `Option[goscale.U16]` | +| `Option` | `Option[goscale.I32]` | +| `Option` | `Option[goscale.U32]` | +| `Option` | `Option[goscale.I64]` | +| `Option` | `Option[goscale.U64]` | +| `Option` | `Option[goscale.I128]` | +| `Option` | `Option[goscale.U128]` | +| `Option` | `Option[Sequence[U8]]` | +| `OptionBool` | `OptionBool` | +| `None` | `nil` | ## [Result](https://github.com/LimeChain/goscale/blob/master/result.go) -| SCALE/Rust | Go | -| ------------------ | ------------------------ | -| | | +| SCALE/Rust | Go | +|------------|----| +| | | ## [Tuple](https://github.com/LimeChain/goscale/blob/master/tuple.go) diff --git a/length_compact.go b/compact.go similarity index 100% rename from length_compact.go rename to compact.go diff --git a/length_compact_test.go b/compact_test.go similarity index 100% rename from length_compact_test.go rename to compact_test.go diff --git a/empty.go b/empty.go index 9558ece..4d40e12 100644 --- a/empty.go +++ b/empty.go @@ -21,10 +21,6 @@ func (e Empty) Bytes() []byte { return []byte{} } -func (e Empty) String() string { - return "" -} - func DecodeEmpty() Empty { return Empty{} } diff --git a/fixed_length.go b/fixed_length.go deleted file mode 100644 index 9800be9..0000000 --- a/fixed_length.go +++ /dev/null @@ -1,202 +0,0 @@ -package goscale - -/* - Ref: https://spec.polkadot.network/#defn-little-endian - - SCALE Fixed Length type translates to Go's fixed-width integer types. - Values are encoded using a fixed-width, non-negative, little-endian format. -*/ - -import ( - "bytes" - "encoding/binary" - "reflect" -) - -func (value U8) Encode(buffer *bytes.Buffer) { - // do not use value.Bytes() here: https://github.com/LimeChain/goscale/issues/77 - encoder := Encoder{Writer: buffer} - encoder.EncodeByte(byte(value)) -} - -func (value U8) Bytes() []byte { - return []byte{byte(value)} -} - -func DecodeU8(buffer *bytes.Buffer) U8 { - decoder := Decoder{Reader: buffer} - result := make([]byte, 1) - decoder.Read(result) - return U8(result[0]) -} - -func (value I8) Encode(buffer *bytes.Buffer) { - U8(value).Encode(buffer) -} - -func (value I8) Bytes() []byte { - return U8(value).Bytes() -} - -func DecodeI8(buffer *bytes.Buffer) I8 { - decoder := Decoder{Reader: buffer} - return I8(decoder.DecodeByte()) -} - -func (value U16) Encode(buffer *bytes.Buffer) { - encoder := Encoder{Writer: buffer} - encoder.Write(value.Bytes()) -} - -func (value U16) Bytes() []byte { - result := make([]byte, 2) - binary.LittleEndian.PutUint16(result, uint16(value)) - - return result -} - -func DecodeU16(buffer *bytes.Buffer) U16 { - decoder := Decoder{Reader: buffer} - result := make([]byte, 2) - decoder.Read(result) - return U16(binary.LittleEndian.Uint16(result)) -} - -func (value I16) Encode(buffer *bytes.Buffer) { - U16(value).Encode(buffer) -} - -func (value I16) Bytes() []byte { - return U16(value).Bytes() -} - -func DecodeI16(buffer *bytes.Buffer) I16 { - return I16(DecodeU16(buffer)) -} - -func (value U32) Encode(buffer *bytes.Buffer) { - encoder := Encoder{Writer: buffer} - encoder.Write(value.Bytes()) -} - -func (value U32) Bytes() []byte { - result := make([]byte, 4) - binary.LittleEndian.PutUint32(result, uint32(value)) - - return result -} - -func DecodeU32(buffer *bytes.Buffer) U32 { - decoder := Decoder{Reader: buffer} - result := make([]byte, 4) - decoder.Read(result) - return U32(binary.LittleEndian.Uint32(result)) -} - -func (value I32) Encode(buffer *bytes.Buffer) { - U32(value).Encode(buffer) -} - -func (value I32) Bytes() []byte { - return U32(value).Bytes() -} - -func DecodeI32(buffer *bytes.Buffer) I32 { - return I32(DecodeU32(buffer)) -} - -func (value U64) Encode(buffer *bytes.Buffer) { - encoder := Encoder{Writer: buffer} - encoder.Write(value.Bytes()) -} - -func (value U64) Bytes() []byte { - result := make([]byte, 8) - binary.LittleEndian.PutUint64(result, uint64(value)) - - return result -} - -func DecodeU64(buffer *bytes.Buffer) U64 { - decoder := Decoder{Reader: buffer} - result := make([]byte, 8) - decoder.Read(result) - return U64(binary.LittleEndian.Uint64(result)) -} - -func (value I64) Encode(buffer *bytes.Buffer) { - U64(value).Encode(buffer) -} - -func (value I64) Bytes() []byte { - return U64(value).Bytes() -} - -func DecodeI64(buffer *bytes.Buffer) I64 { - return I64(DecodeU64(buffer)) -} - -func (value U128) Encode(buffer *bytes.Buffer) { - value[0].Encode(buffer) - value[1].Encode(buffer) -} - -func (u U128) Bytes() []byte { - return append(u[0].Bytes(), u[1].Bytes()...) -} - -func DecodeU128(buffer *bytes.Buffer) U128 { - decoder := Decoder{Reader: buffer} - buf := make([]byte, 16) - decoder.Read(buf) - - return U128{ - U64(binary.LittleEndian.Uint64(buf[:8])), - U64(binary.LittleEndian.Uint64(buf[8:])), - } -} - -func (value I128) Encode(buffer *bytes.Buffer) { - value[0].Encode(buffer) - value[1].Encode(buffer) -} - -func (value I128) Bytes() []byte { - return append(value[0].Bytes(), value[1].Bytes()...) -} - -func DecodeI128(buffer *bytes.Buffer) I128 { - return I128{ - DecodeU64(buffer), - DecodeU64(buffer), - } -} - -func DecodeNumeric[N Numeric](buffer *bytes.Buffer) N { - var result interface{} - - switch reflect.Zero(reflect.TypeOf(*new(N))).Interface().(type) { - case U8: - result = DecodeU8(buffer) - case I8: - result = DecodeI8(buffer) - case U16: - result = DecodeU16(buffer) - case I16: - result = DecodeI16(buffer) - case U32: - result = DecodeU32(buffer) - case I32: - result = DecodeI32(buffer) - case U64: - result = DecodeU64(buffer) - case I64: - result = DecodeI64(buffer) - case U128: - result = DecodeU128(buffer) - case I128: - result = DecodeI128(buffer) - } - - return result.(N) -} diff --git a/fixed_length_test.go b/fixed_length_test.go deleted file mode 100644 index 76cc48d..0000000 --- a/fixed_length_test.go +++ /dev/null @@ -1,499 +0,0 @@ -package goscale - -import ( - "bytes" - "math" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_EncodeU8(t *testing.T) { - var testExamples = []struct { - label string - input U8 - expectation []byte - }{ - {label: "uint8(255)", input: U8(255), expectation: []byte{0xff}}, - {label: "uint8(0)", input: U8(0), expectation: []byte{0x00}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeU8(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation U8 - }{ - {label: "(0xff)", input: []byte{0xff}, expectation: U8(255)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeU8(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeI8(t *testing.T) { - var testExamples = []struct { - label string - input I8 - expectation []byte - }{ - {label: "int8(0)", input: I8(0), expectation: []byte{0x00}}, - {label: "int8(-128)", input: I8(-128), expectation: []byte{0x80}}, - {label: "int8(127)", input: I8(127), expectation: []byte{0x7f}}, - {label: "int8(-1)", input: I8(-1), expectation: []byte{0xff}}, - {label: "int8(69)", input: I8(69), expectation: []byte{0x45}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeI8(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation I8 - }{ - {label: "(0x80)", input: []byte{0x80}, expectation: I8(-128)}, - {label: "(0x7f)", input: []byte{0x7f}, expectation: I8(127)}, - {label: "(0xff)", input: []byte{0xff}, expectation: I8(-1)}, - {label: "(0x45)", input: []byte{0x45}, expectation: I8(69)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeI8(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeU16(t *testing.T) { - var testExamples = []struct { - label string - input U16 - expectation []byte - }{ - {label: "uint16(127)", input: U16(127), expectation: []byte{0x7f, 0x00}}, - {label: "uint16(42)", input: U16(42), expectation: []byte{0x2a, 0x00}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeU16(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation U16 - }{ - {label: "(0x2a00)", input: []byte{0x2a, 0x00}, expectation: U16(42)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeU16(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeI16(t *testing.T) { - var testExamples = []struct { - label string - input I16 - expectation []byte - }{ - {label: "int16(-128)", input: I16(-128), expectation: []byte{0x80, 0xff}}, - {label: "int16(127)", input: I16(127), expectation: []byte{0x7f, 0x00}}, - {label: "int16(42)", input: I16(42), expectation: []byte{0x2a, 0x00}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeI16(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation I16 - }{ - {label: "(0x80ff)", input: []byte{0x80, 0xff}, expectation: I16(-128)}, - {label: "(0x2a00)", input: []byte{0x2a, 0x00}, expectation: I16(42)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeI16(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeU32(t *testing.T) { - var testExamples = []struct { - label string - input U32 - expectation []byte - }{ - {label: "uint32(127)", input: U32(127), expectation: []byte{0x7f, 0x00, 0x00, 0x00}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeU32(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation U32 - }{ - {label: "(0x7f000000)", input: []byte{0x7f, 0x00, 0x00, 0x00}, expectation: U32(127)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeU32(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeI32(t *testing.T) { - var testExamples = []struct { - label string - input I32 - expectation []byte - }{ - {label: "int32(-128)", input: I32(-128), expectation: []byte{0x80, 0xff, 0xff, 0xff}}, - {label: "int32(16777215)", input: I32(16777215), expectation: []byte{0xff, 0xff, 0xff, 0x00}}, - {label: "int32(127)", input: I32(127), expectation: []byte{0x7f, 0x00, 0x00, 0x00}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeI32(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation I32 - }{ - {label: "(0x80ffffff)", input: []byte{0x80, 0xff, 0xff, 0xff}, expectation: I32(-128)}, - {label: "(0xffffff00)", input: []byte{0xff, 0xff, 0xff, 0x00}, expectation: I32(16777215)}, - {label: "(0x7f000000)", input: []byte{0x7f, 0x00, 0x00, 0x00}, expectation: I32(127)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeI32(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeU64(t *testing.T) { - var testExamples = []struct { - label string - input U64 - expectation []byte - }{ - {label: "uint64(127)", input: U64(127), expectation: []byte{0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeU64(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation U64 - }{ - {label: "(0x7f00000000000000)", input: []byte{0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, expectation: U64(127)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeU64(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeI64(t *testing.T) { - var testExamples = []struct { - label string - input I64 - expectation []byte - }{ - {label: "int64(-128)", input: I64(-128), expectation: []byte{0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - testExample.input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), testExample.expectation) - assert.Equal(t, testExample.input.Bytes(), testExample.expectation) - }) - } -} - -func Test_DecodeI64(t *testing.T) { - var testExamples = []struct { - label string - input []byte - expectation I64 - }{ - {label: "(0x80ffffffffffffff)", input: []byte{0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expectation: I64(-128)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(testExample.input) - - result := DecodeI64(buffer) - - assert.Equal(t, result, testExample.expectation) - }) - } -} - -func Test_EncodeU128(t *testing.T) { - var examples = []struct { - label string - input string - expect []byte - }{ - {label: "Encode U128 - (0)", input: "0", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, - {label: "Encode U128 - (42)", input: "42", expect: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, - {label: "Encode U128 - (const.MaxInt64 + 1)", input: "9223372036854775808", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, - {label: "Encode U128 - (0x9cFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)", input: "340282366920938463463374607431768211356", expect: []byte{0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - {label: "Encode U128 - (MaxInt128)", input: "340282366920938463463374607431768211455", expect: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - } - - for _, e := range examples { - t.Run(e.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - value, ok := new(big.Int).SetString(e.input, 10) - if !ok { - panic("not ok") - } - input := NewU128(value) - - input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), e.expect) - assert.Equal(t, input.Bytes(), e.expect) - }) - } -} - -func Test_DecodeU128(t *testing.T) { - var examples = []struct { - label string - input []byte - expect U128 - stringValue string - }{ - {label: "Decode U128 - (0)", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: U128{U64(0), U64(0)}, stringValue: "0"}, - {label: "Decode U128 - (42)", input: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: U128{42, 0}, stringValue: "42"}, - {label: "Decode U128 - (math.MaxInt64 + 1)", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: U128{U64(math.MaxInt64 + 1), 0}, stringValue: "9223372036854775808"}, - {label: "Decode U128 - (0x9cFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)", input: []byte{0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: U128{18446744073709551516, 18446744073709551615}, stringValue: "340282366920938463463374607431768211356"}, - {label: "Decode U128 - (MaxInt128)", input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: U128{math.MaxUint64, math.MaxUint64}, stringValue: "340282366920938463463374607431768211455"}, - } - - for _, e := range examples { - t.Run(e.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(e.input) - - result := DecodeU128(buffer) - bigInt := result.ToBigInt() - - assert.Equal(t, result, e.expect) - assert.Equal(t, bigInt.String(), e.stringValue) - }) - } -} - -func Test_NewU128FromBigIntPanic(t *testing.T) { - t.Run("Exceeds U128", func(t *testing.T) { - value, ok := new(big.Int).SetString("340282366920938463463374607431768211456", 10) // MaxU128 + 1 - if !ok { - panic("not ok") - } - - assert.Panics(t, func() { NewU128(value) }) - }) -} - -func Test_EncodeI128(t *testing.T) { - var examples = []struct { - label string - input string - expect []byte - }{ - {label: "Encode I128 - (MinI128)", input: "-170141183460469231731687303715884105728", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}}, - {label: "Encode I128 - (-123456789)", input: "-123456789", expect: []byte{0xeb, 0x32, 0xa4, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - {label: "Encode I128 - (-42)", input: "-42", expect: []byte{0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - {label: "Encode I128 - (-1)", input: "-1", expect: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - {label: "Encode I128 - (0)", input: "0", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, - {label: "Encode I128 - (1)", input: "1", expect: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, - {label: "Encode I128 - (42)", input: "42", expect: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, - {label: "Encode I128 - (123456789)", input: "123456789", expect: []byte{0x15, 0xcd, 0x5b, 0x07, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, - {label: "Encode I128 - (MaxInt128)", input: "170141183460469231731687303715884105727", expect: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}}, - } - - for _, e := range examples { - t.Run(e.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - - value, ok := new(big.Int).SetString(e.input, 10) - if !ok { - panic("not ok") - } - input := bigIntToI128(value) - - input.Encode(buffer) - - assert.Equal(t, buffer.Bytes(), e.expect) - assert.Equal(t, input.Bytes(), e.expect) - }) - } -} - -func Test_DecodeI128(t *testing.T) { - var examples = []struct { - label string - input []byte - expect I128 - stringValue string - }{ - {label: "Decode I128 - (MinInt128 == -170141183460469231731687303715884105728", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}, expect: I128{U64(0), U64(math.MaxInt64 + 1)}, stringValue: "-170141183460469231731687303715884105728"}, - {label: "Decode I128 - (-123456789)", input: []byte{0xeb, 0x32, 0xa4, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: I128{U64(math.MaxUint64 - 123456789 + 1), U64(math.MaxUint64)}, stringValue: "-123456789"}, - {label: "Decode I128 - (-42)", input: []byte{0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: I128{U64(math.MaxUint64 - 41), U64(math.MaxUint64)}, stringValue: "-42"}, - {label: "Decode I128 - (-1)", input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: I128{U64(math.MaxUint64), U64(math.MaxUint64)}, stringValue: "-1"}, - {label: "Encode I128 - (0)", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(0), U64(0)}, stringValue: "0"}, - {label: "Encode I128 - (1)", input: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(1), U64(0)}, stringValue: "1"}, - {label: "Decode I128 - (42)", input: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(42), U64(0)}, stringValue: "42"}, - {label: "Encode I128 - (123456789)", input: []byte{0x15, 0xcd, 0x5b, 0x07, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(123456789)}, stringValue: "123456789"}, - {label: "Decode I128 - (MaxInt128 == 170141183460469231731687303715884105727)", input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, expect: I128{U64(math.MaxUint64), U64(math.MaxInt64)}, stringValue: "170141183460469231731687303715884105727"}, - } - - for _, e := range examples { - t.Run(e.label, func(t *testing.T) { - buffer := &bytes.Buffer{} - buffer.Write(e.input) - - result := DecodeI128(buffer) - bigInt := result.ToBigInt() - - assert.Equal(t, result, e.expect) - assert.Equal(t, bigInt.String(), e.stringValue) - }) - } -} diff --git a/i128.go b/i128.go new file mode 100644 index 0000000..401d1f3 --- /dev/null +++ b/i128.go @@ -0,0 +1,199 @@ +package goscale + +import ( + "bytes" + "encoding/binary" + "math/big" + "math/bits" +) + +// little endian byte order +// [0] least significant bits +// [1] most significant bits +type I128 [2]U64 + +func NewI128[N Integer](n N) I128 { + return anyIntegerTo128Bits[I128](n) +} + +func NewI128FromString(n string) (I128, error) { + return stringTo128Bits[I128](n) +} + +func (n I128) Encode(buffer *bytes.Buffer) { + n[0].Encode(buffer) + n[1].Encode(buffer) +} + +func (n I128) Bytes() []byte { + return append(n[0].Bytes(), n[1].Bytes()...) +} + +func DecodeI128(buffer *bytes.Buffer) I128 { + return I128{ + DecodeU64(buffer), + DecodeU64(buffer), + } +} + +func (n I128) ToBigInt() *big.Int { + isNegative := n.isNegative() + + 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 +} + +func (n I128) Add(other I128) I128 { + sumLow, carry := bits.Add64(uint64(n[0]), uint64(other[0]), 0) + sumHigh, _ := bits.Add64(uint64(n[1]), uint64(other[1]), carry) + return I128{U64(sumLow), U64(sumHigh)} +} + +func (n I128) Sub(other I128) I128 { + diffLow, borrow := bits.Sub64(uint64(n[0]), uint64(other[0]), 0) + diffHigh, _ := bits.Sub64(uint64(n[1]), uint64(other[1]), borrow) + return I128{U64(diffLow), U64(diffHigh)} +} + +func (n I128) Mul(other I128) I128 { + negA := n[1]>>(64-1) == 1 + negB := other[1]>>(64-1) == 1 + + absA := n + if negA { + absA = negateI128(n) + } + + absB := other + if negB { + absB = negateI128(other) + } + + high, low := bits.Mul64(uint64(absA[0]), uint64(absB[0])) + high += uint64(absA[1])*uint64(absB[0]) + uint64(absA[0])*uint64(absB[1]) + + result := I128{U64(low), U64(high)} + + // if one of the operands is negative the result is also negative + if negA != negB { + return negateI128(result) + } + return result +} + +func (n I128) Div(other I128) I128 { + return bigIntToI128( + new(big.Int).Div(n.ToBigInt(), other.ToBigInt()), + ) +} + +func (n I128) Mod(other I128) I128 { + return bigIntToI128( + new(big.Int).Mod(n.ToBigInt(), other.ToBigInt()), + ) +} + +func (n I128) Eq(other I128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) == 0 +} + +func (n I128) Ne(other I128) bool { + return !n.Eq(other) +} + +func (n I128) Lt(other I128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) < 0 +} + +func (n I128) Lte(other I128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) <= 0 +} + +func (n I128) Gt(other I128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) > 0 +} + +func (n I128) Gte(other I128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) >= 0 +} + +func bigIntToI128(n *big.Int) I128 { + bytes := make([]byte, 16) + n.FillBytes(bytes) + reverseSlice(bytes) + + var result = I128{ + U64(binary.LittleEndian.Uint64(bytes[:8])), + U64(binary.LittleEndian.Uint64(bytes[8:])), + } + + if n.Sign() < 0 { + result = negateI128(result) + } + + return result +} + +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)} +} + +//func (n 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() +// } +// // check for underflow +// if a[1]&(1<<63) != 0 && b.(I128)[1]&(1<<63) != 0 && sumHigh&(1<<63) == 0 { +// return MinI128() +// } +// return I128{U64(sumLow), U64(sumHigh)} +//} +// +//func (n 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() +// } +// // check for underflow +// if a[1]&(1<<63) != 0 && b.(I128)[1]&(1<<63) == 0 && diffHigh&(1<<63) == 0 { +// return MinI128() +// } +// return I128{U64(diffLow), U64(diffHigh)} +//} +// +//func (n I128) SaturatingMul(b Numeric) Numeric { +// result := new(big.Int).Mul(a.ToBigInt(), b.(I128).ToBigInt()) +// // 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)) +// if result.Cmp(maxI128) > 0 { +// return bigIntToI128(maxI128) +// } else if result.Cmp(minI128) < 0 { +// return bigIntToI128(minI128) +// } +// return bigIntToI128(result) +//} diff --git a/i128_test.go b/i128_test.go new file mode 100644 index 0000000..8de3275 --- /dev/null +++ b/i128_test.go @@ -0,0 +1,320 @@ +package goscale + +import ( + "bytes" + "math" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeI128(t *testing.T) { + var examples = []struct { + label string + input string + expect []byte + }{ + {label: "Encode I128 - (MinI128)", input: "-170141183460469231731687303715884105728", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}}, + {label: "Encode I128 - (-123456789)", input: "-123456789", expect: []byte{0xeb, 0x32, 0xa4, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {label: "Encode I128 - (-42)", input: "-42", expect: []byte{0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {label: "Encode I128 - (-1)", input: "-1", expect: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {label: "Encode I128 - (0)", input: "0", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {label: "Encode I128 - (1)", input: "1", expect: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {label: "Encode I128 - (42)", input: "42", expect: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {label: "Encode I128 - (123456789)", input: "123456789", expect: []byte{0x15, 0xcd, 0x5b, 0x07, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {label: "Encode I128 - (MaxInt128)", input: "170141183460469231731687303715884105727", expect: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}}, + } + + for _, e := range examples { + t.Run(e.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + value, ok := new(big.Int).SetString(e.input, 10) + if !ok { + panic("not ok") + } + input := bigIntToI128(value) + + input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), e.expect) + assert.Equal(t, input.Bytes(), e.expect) + }) + } +} + +func Test_DecodeI128(t *testing.T) { + var examples = []struct { + label string + input []byte + expect I128 + stringValue string + }{ + {label: "Decode I128 - (MinInt128 == -170141183460469231731687303715884105728", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}, expect: I128{U64(0), U64(math.MaxInt64 + 1)}, stringValue: "-170141183460469231731687303715884105728"}, + {label: "Decode I128 - (-123456789)", input: []byte{0xeb, 0x32, 0xa4, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: I128{U64(math.MaxUint64 - 123456789 + 1), U64(math.MaxUint64)}, stringValue: "-123456789"}, + {label: "Decode I128 - (-42)", input: []byte{0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: I128{U64(math.MaxUint64 - 41), U64(math.MaxUint64)}, stringValue: "-42"}, + {label: "Decode I128 - (-1)", input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: I128{U64(math.MaxUint64), U64(math.MaxUint64)}, stringValue: "-1"}, + {label: "Encode I128 - (0)", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(0), U64(0)}, stringValue: "0"}, + {label: "Encode I128 - (1)", input: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(1), U64(0)}, stringValue: "1"}, + {label: "Decode I128 - (42)", input: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(42), U64(0)}, stringValue: "42"}, + {label: "Encode I128 - (123456789)", input: []byte{0x15, 0xcd, 0x5b, 0x07, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: I128{U64(123456789)}, stringValue: "123456789"}, + {label: "Decode I128 - (MaxInt128 == 170141183460469231731687303715884105727)", input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, expect: I128{U64(math.MaxUint64), U64(math.MaxInt64)}, stringValue: "170141183460469231731687303715884105727"}, + } + + for _, e := range examples { + t.Run(e.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(e.input) + + result := DecodeI128(buffer) + bigInt := result.ToBigInt() + + assert.Equal(t, result, e.expect) + assert.Equal(t, bigInt.String(), e.stringValue) + }) + } +} + +func Test_I128_Add(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect I128 + }{ + {"-2+(-1)", NewI128(-2), NewI128(-1), NewI128(-3)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Add(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Sub(t *testing.T) { + u1, _ := NewI128FromString("340282366920938463463374607431657675311") + u2, _ := NewI128FromString("340282366920938463463374607421657675311") + u3, _ := NewI128FromString("340282366920938463463374607431657675310") + + testExamples := []struct { + label string + a I128 + b I128 + expect I128 + }{ + {"2-1", NewI128(2), NewI128(1), NewI128(1)}, + {"-2-(-1)", NewI128(-2), NewI128(-1), NewI128(-1)}, + {"-170141183460469231731687303715884105728-1", MinI128(), NewI128(1), MaxI128()}, + {"499999889463855-10000000000", NewI128(499999889463855), NewI128(10000000000), NewI128(499989889463855)}, + {"340282366920938463463374607431657675311-10000000000", u1, NewI128(10000000000), u2}, + {"9889463854-10000000000", NewI128(9889463854), NewI128(10000000000), u3}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Sub(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Mul(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect I128 + }{ + {"MaxI128*0", MaxI128(), NewI128(0), NewI128(0)}, + {"MaxI128*1", MaxI128(), NewI128(1), MaxI128()}, + {"MaxI128*2", MaxI128(), NewI128(2), NewI128(-2)}, + {"MaxI128*MaxI128", MaxI128(), MaxI128(), NewI128(1)}, + + {"-2*3", NewI128(-2), NewI128(3), NewI128(-6)}, + {"-2*-3", NewI128(-2), NewI128(-3), NewI128(6)}, + {"1*0", NewI128(1), NewI128(0), NewI128(0)}, + {"-1*0", NewI128(-1), NewI128(0), NewI128(0)}, + {"1*1", NewI128(1), NewI128(1), NewI128(1)}, + {"-1*1", NewI128(-1), NewI128(1), NewI128(-1)}, + {"-1*-1", NewI128(-1), NewI128(-1), NewI128(1)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Mul(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Div(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect I128 + }{ + {"0/-1", NewI128(0), NewI128(-1), NewI128(0)}, + {"-1/1", NewI128(-1), NewI128(1), NewI128(-1)}, + {"-1/-1", NewI128(-1), NewI128(-1), NewI128(1)}, + {"-6/2", NewI128(-6), NewI128(2), NewI128(-3)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Div(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Eq(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect bool + }{ + {"1==1", NewI128(1), NewI128(1), true}, + {"1==2", NewI128(1), NewI128(2), false}, + {"-1==1", NewI128(-1), NewI128(1), false}, + {"-1==-1", NewI128(-1), NewI128(-1), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Eq(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Ne(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect bool + }{ + {"1!=1", NewI128(U8(1)), NewI128(U16(1)), false}, + {"1!=2", NewI128(U32(1)), NewI128(U64(2)), true}, + {"-1!=1", NewI128(I8(-1)), NewI128(I16(1)), true}, + {"-1!=-1", NewI128(I32(-1)), NewI128(I64(-1)), false}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Ne(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Lt(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect bool + }{ + {"1<1", NewI128(1), NewI128(1), false}, + {"1<2", NewI128(1), NewI128(2), true}, + {"-1<1", NewI128(-1), NewI128(1), true}, + {"-1<-1", NewI128(-1), NewI128(-1), false}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Lt(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Lte(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect bool + }{ + {"1<=1", NewI128(1), NewI128(1), true}, + {"1<=2", NewI128(1), NewI128(2), true}, + {"-1<=1", NewI128(-1), NewI128(1), true}, + {"-1<=-1", NewI128(-1), NewI128(-1), true}, + {"-1<=-2", NewI128(-1), NewI128(-2), false}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Lte(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Gt(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect bool + }{ + {"1>1", NewI128(1), NewI128(1), false}, + {"1>2", NewI128(1), NewI128(2), false}, + {"1>-1", NewI128(1), NewI128(-1), true}, + {"-1>-1", NewI128(-1), NewI128(-1), false}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Gt(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_Gte(t *testing.T) { + testExamples := []struct { + label string + a I128 + b I128 + expect bool + }{ + {"1>=1", NewI128(1), NewI128(1), true}, + {"1>=2", NewI128(1), NewI128(2), false}, + {"1>=-1", NewI128(1), NewI128(-1), true}, + {"-1>=-1", NewI128(-1), NewI128(-1), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Gte(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_I128_ToBigInt(t *testing.T) { + bnMaxI128Value, _ := new(big.Int).SetString("170141183460469231731687303715884105727", 10) + bnMinI128Value, _ := new(big.Int).SetString("-170141183460469231731687303715884105728", 10) + + testExamples := []struct { + label string + input I128 + expect *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.expect, result) + }) + } +} diff --git a/i16.go b/i16.go new file mode 100644 index 0000000..7a94454 --- /dev/null +++ b/i16.go @@ -0,0 +1,17 @@ +package goscale + +import "bytes" + +type I16 int16 + +func (value I16) Encode(buffer *bytes.Buffer) { + U16(value).Encode(buffer) +} + +func (value I16) Bytes() []byte { + return U16(value).Bytes() +} + +func DecodeI16(buffer *bytes.Buffer) I16 { + return I16(DecodeU16(buffer)) +} diff --git a/i16_test.go b/i16_test.go new file mode 100644 index 0000000..52e2897 --- /dev/null +++ b/i16_test.go @@ -0,0 +1,53 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeI16(t *testing.T) { + var testExamples = []struct { + label string + input I16 + expectation []byte + }{ + {label: "int16(-128)", input: -128, expectation: []byte{0x80, 0xff}}, + {label: "int16(127)", input: 127, expectation: []byte{0x7f, 0x00}}, + {label: "int16(42)", input: 42, expectation: []byte{0x2a, 0x00}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeI16(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation I16 + }{ + {label: "(0x80ff)", input: []byte{0x80, 0xff}, expectation: -128}, + {label: "(0x2a00)", input: []byte{0x2a, 0x00}, expectation: 42}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeI16(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +} diff --git a/i32.go b/i32.go new file mode 100644 index 0000000..31296d6 --- /dev/null +++ b/i32.go @@ -0,0 +1,17 @@ +package goscale + +import "bytes" + +type I32 int32 + +func (value I32) Encode(buffer *bytes.Buffer) { + U32(value).Encode(buffer) +} + +func (value I32) Bytes() []byte { + return U32(value).Bytes() +} + +func DecodeI32(buffer *bytes.Buffer) I32 { + return I32(DecodeU32(buffer)) +} diff --git a/i32_test.go b/i32_test.go new file mode 100644 index 0000000..e57e0ac --- /dev/null +++ b/i32_test.go @@ -0,0 +1,54 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeI32(t *testing.T) { + var testExamples = []struct { + label string + input I32 + expectation []byte + }{ + {label: "int32(-128)", input: -128, expectation: []byte{0x80, 0xff, 0xff, 0xff}}, + {label: "int32(16777215)", input: 16777215, expectation: []byte{0xff, 0xff, 0xff, 0x00}}, + {label: "int32(127)", input: 127, expectation: []byte{0x7f, 0x00, 0x00, 0x00}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeI32(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation I32 + }{ + {label: "(0x80ffffff)", input: []byte{0x80, 0xff, 0xff, 0xff}, expectation: -128}, + {label: "(0xffffff00)", input: []byte{0xff, 0xff, 0xff, 0x00}, expectation: 16777215}, + {label: "(0x7f000000)", input: []byte{0x7f, 0x00, 0x00, 0x00}, expectation: 127}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeI32(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +} diff --git a/i64.go b/i64.go new file mode 100644 index 0000000..2885091 --- /dev/null +++ b/i64.go @@ -0,0 +1,17 @@ +package goscale + +import "bytes" + +type I64 int64 + +func (value I64) Encode(buffer *bytes.Buffer) { + U64(value).Encode(buffer) +} + +func (value I64) Bytes() []byte { + return U64(value).Bytes() +} + +func DecodeI64(buffer *bytes.Buffer) I64 { + return I64(DecodeU64(buffer)) +} diff --git a/i64_test.go b/i64_test.go new file mode 100644 index 0000000..88608f5 --- /dev/null +++ b/i64_test.go @@ -0,0 +1,50 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeI64(t *testing.T) { + var testExamples = []struct { + label string + input I64 + expectation []byte + }{ + {label: "int64(-128)", input: -128, expectation: []byte{0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeI64(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation I64 + }{ + {label: "(0x80ffffffffffffff)", input: []byte{0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expectation: -128}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeI64(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +} diff --git a/i8.go b/i8.go new file mode 100644 index 0000000..d914aad --- /dev/null +++ b/i8.go @@ -0,0 +1,18 @@ +package goscale + +import "bytes" + +type I8 int8 + +func (value I8) Encode(buffer *bytes.Buffer) { + U8(value).Encode(buffer) +} + +func (value I8) Bytes() []byte { + return U8(value).Bytes() +} + +func DecodeI8(buffer *bytes.Buffer) I8 { + decoder := Decoder{Reader: buffer} + return I8(decoder.DecodeByte()) +} diff --git a/i8_test.go b/i8_test.go new file mode 100644 index 0000000..a4f0881 --- /dev/null +++ b/i8_test.go @@ -0,0 +1,57 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeI8(t *testing.T) { + var testExamples = []struct { + label string + input I8 + expectation []byte + }{ + {label: "int8(0)", input: 0, expectation: []byte{0x00}}, + {label: "int8(-128)", input: -128, expectation: []byte{0x80}}, + {label: "int8(127)", input: 127, expectation: []byte{0x7f}}, + {label: "int8(-1)", input: -1, expectation: []byte{0xff}}, + {label: "int8(69)", input: 69, expectation: []byte{0x45}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeI8(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation I8 + }{ + {label: "(0x80)", input: []byte{0x80}, expectation: -128}, + {label: "(0x7f)", input: []byte{0x7f}, expectation: 127}, + {label: "(0xff)", input: []byte{0xff}, expectation: -1}, + {label: "(0x45)", input: []byte{0x45}, expectation: 69}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeI8(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +} diff --git a/math.go b/math.go new file mode 100644 index 0000000..8e9aff9 --- /dev/null +++ b/math.go @@ -0,0 +1,139 @@ +package goscale + +import ( + "errors" + "math" + "math/bits" +) + +var ( + errOverflow = errors.New("overflow") + errUnderflow = errors.New("underflow") +) + +func Clamp(value, min, max int) int { + if value < min { + return min + } + if value > max { + return max + } + + return value +} + +func Max64(a, b U64) U64 { + if a > b { + return a + } + return b +} + +func Max128(a, b U128) U128 { + if a.Gt(b) { + return a + } + return b +} + +// ff ff ff ff ff ff ff ff | 7f ff ff ff ff ff ff ff +func MaxI128() I128 { + return I128{ + U64(^uint64(0)), + U64(^uint64(0) >> 1), + } +} + +// ff ff ff ff ff ff ff ff | ff ff ff ff ff ff ff ff +func MaxU128() U128 { + return U128{ + U64(^uint64(0)), + U64(^uint64(0)), + } +} + +func Min64(a, b U64) U64 { + if a < b { + return a + } + return b +} + +func Min128(a, b U128) U128 { + if a.Lt(b) { + return a + } + return b +} + +// 00 00 00 00 00 00 00 00 | 80 00 00 00 00 00 00 00 +func MinI128() I128 { + return I128{ + U64(0), + U64(1 << 63), + } +} + +// 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 +func MinU128() U128 { + return U128{ + U64(0), + U64(0), + } +} + +func TrailingZeros128(n U128) uint { + return n.ToBigInt().TrailingZeroBits() +} + +func SaturatingAddU32(a, b U32) U32 { + sum := uint64(a) + uint64(b) + if sum > math.MaxUint32 { + return U32(math.MaxUint32) + } + return U32(sum) +} + +func SaturatingAddU64(a, b U64) U64 { + sum, carry := bits.Add64(uint64(a), uint64(b), 0) + if carry != 0 { + return U64(math.MaxUint64) + } + return U64(sum) +} + +func SaturatingSubU64(a, b U64) U64 { + diff, borrow := bits.Sub64(uint64(a), uint64(b), 0) + if borrow != 0 { + return U64(0) + } + return U64(diff) +} + +func SaturatingMulU64(a, b U64) U64 { + if a == 0 || b == 0 { + return U64(0) + } + + hi, lo := bits.Mul64(uint64(a), uint64(b)) + if hi != 0 { + return U64(math.MaxUint64) + } + return U64(lo) +} + +func CheckedAddU32(a, b U32) (U32, error) { + sum, carry := bits.Add32(uint32(a), uint32(b), 0) + if carry != 0 { + return 0, errOverflow + } + return U32(sum), nil +} + +func CheckedAddU64(a, b U64) (U64, error) { + sum, carry := bits.Add64(uint64(a), uint64(b), 0) + if carry != 0 { + return 0, errOverflow + } + return U64(sum), nil +} diff --git a/math_test.go b/math_test.go new file mode 100644 index 0000000..df07a06 --- /dev/null +++ b/math_test.go @@ -0,0 +1,270 @@ +package goscale + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Max64(t *testing.T) { + testExamples := []struct { + label string + a U64 + b U64 + expect U64 + }{ + {"Max(1, 2)", 1, 2, 2}, + {"Max(3, 1)", 3, 1, 3}, + {"Max(1, MaxU64)", 1, math.MaxUint64, math.MaxUint64}, + {"Max(MaxU64, MaxU64)", math.MaxUint64, math.MaxUint64, math.MaxUint64}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := Max64(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_Max128(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"Max(1, 2)", NewU128(1), NewU128(2), NewU128(2)}, + {"Max(2, 1)", NewU128(2), NewU128(1), NewU128(2)}, + {"Max(1, MaxU128)", NewU128(1), MaxU128(), MaxU128()}, + {"Max(MaxU128, MaxU128)", MaxU128(), MaxU128(), MaxU128()}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := Max128(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_Min64(t *testing.T) { + testExamples := []struct { + label string + a U64 + b U64 + expect U64 + }{ + {"Min(1, 2)", 1, 2, 1}, + {"Min(1, MaxU64)", 1, math.MaxUint64, 1}, + {"Min(MaxU64, MaxU64)", math.MaxUint64, math.MaxUint64, math.MaxUint64}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := Min64(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_Min128(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"Min(1, 2)", NewU128(1), NewU128(2), NewU128(1)}, + {"Min(1, MaxU128)", NewU128(1), MaxU128(), NewU128(1)}, + {"Min(MaxU128, MaxU128)", MaxU128(), MaxU128(), MaxU128()}, + {"Min(MinU128, MaxU128)", MinU128(), MaxU128(), MinU128()}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := Min128(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_Clamp(t *testing.T) { + testExamples := []struct { + label string + a int + minValue int + maxValue int + expect int + }{ + {"Clamp(5, 1, 10)", 5, 1, 10, 5}, + {"Clamp(15, 1, 10)", 15, 1, 10, 10}, + {"Clamp(0, 1, 10)", 0, 1, 10, 1}, + {"Clamp(-3, -2, 2)", -3, -2, 2, -2}, + {"Clamp(3, -2, 2)", 3, -2, 2, 2}, + {"Clamp(1, -2, 2)", 1, -2, 2, 1}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := Clamp(testExample.a, testExample.minValue, testExample.maxValue) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_TrailingZeros128(t *testing.T) { + testExamples := []struct { + label string + n U128 + expect uint + }{ + {"TrailingZeros(1)", NewU128(1), 0}, + {"TrailingZeros(2)", NewU128(2), 1}, + {"TrailingZeros(3)", NewU128(3), 0}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := TrailingZeros128(testExample.n) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_SaturatingAddU32(t *testing.T) { + testExamples := []struct { + label string + a U32 + b U32 + expect U32 + }{ + {"2 + 1", 2, 1, 3}, + {"MaxU32+1", math.MaxUint32, 1, math.MaxUint32}, + {"MaxU32+MaxU32", math.MaxUint32, math.MaxUint32, math.MaxUint32}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := SaturatingAddU32(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_SaturatingAddU64(t *testing.T) { + testExamples := []struct { + label string + a U64 + b U64 + expect U64 + }{ + {"2 + 1", 2, 1, 3}, + {"MaxU64+1", math.MaxUint64, 1, math.MaxUint64}, + {"MaxU64+MaxU64", math.MaxUint64, math.MaxUint64, math.MaxUint64}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := SaturatingAddU64(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_SaturatingSubU64(t *testing.T) { + testExamples := []struct { + label string + a U64 + b U64 + expect U64 + }{ + {"0-1", 0, 1, 0}, + {"2-1", 2, 1, 1}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := SaturatingSubU64(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_SaturatingMul(t *testing.T) { + testExamples := []struct { + label string + a U64 + b U64 + expect U64 + }{ + {"0*1", 1, 0, 0}, + {"2*0", 2, 0, 0}, + {"2*2", 2, 2, 4}, + {"2*3", 2, 3, 6}, + {"MaxU64*2", math.MaxUint64, 2, math.MaxUint64}, + {"MaxU64*MaxU64", math.MaxUint64, math.MaxUint64, math.MaxUint64}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := SaturatingMulU64(testExample.a, testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_CheckedAddU32(t *testing.T) { + testExamples := []struct { + label string + a U32 + b U32 + expect U32 + hasExpectErr bool + }{ + {"1+2", 1, 2, 3, false}, + {"MaxU32+1", math.MaxUint32, 1, 0, true}, + {"MaxU32+MaxU32", math.MaxUint32, math.MaxUint32, 0, true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result, err := CheckedAddU32(testExample.a, testExample.b) + + assert.Equal(t, testExample.expect, result) + if testExample.hasExpectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_CheckedAddU64(t *testing.T) { + testExamples := []struct { + label string + a U64 + b U64 + expect U64 + hasExpectErr bool + }{ + {"1+2", 1, 2, 3, false}, + {"MaxU64+1", math.MaxUint64, 1, 0, true}, + {"MaxU64+MaxU64", math.MaxUint64, math.MaxUint64, 0, true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result, err := CheckedAddU64(testExample.a, testExample.b) + + assert.Equal(t, testExample.expect, result) + if testExample.hasExpectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/numeric.go b/numeric.go index de7e4b2..6f39356 100644 --- a/numeric.go +++ b/numeric.go @@ -1,20 +1,11 @@ package goscale -// This could be extracted into a separate package. However, -// to define encoding/decoding methods, the type must be within -// the same package. This would require the definition of new -// types and their corresponding encoding/decoding methods, -// leading to frequent type conversions, which is not ideal. -// e.g. goscale.U8(numeric.U8(1)). - import ( "errors" "math/big" "reflect" ) -var ErrOverflow = errors.New("overflow") - // Signed integer constraint, for type safety checks type SignedPrimitiveInteger interface { int | int8 | int16 | int32 | int64 | I8 | I16 | I32 | I64 @@ -34,797 +25,6 @@ type Integer interface { SignedPrimitiveInteger | UnsignedPrimitiveInteger | Integer128 | *big.Int } -type IntegerOrString interface { - Integer | string -} - -// Go's primitive numeric types don't have a common interface. -// Numeric is an interface that all integer types implement -// which enables writing generic code that works with any type, -// including some custom types (e.g. U8, I8, U16, I16, U32, I32, U64, I64, U128, I128) -type Numeric interface { - Encodable - - Interface() Numeric - - Add(other Numeric) Numeric - Sub(other Numeric) Numeric - Mul(other Numeric) Numeric - Div(other Numeric) Numeric - Mod(other Numeric) Numeric - - SaturatingAdd(other Numeric) Numeric - SaturatingSub(other Numeric) Numeric - SaturatingMul(other Numeric) Numeric - - // TODO: https://github.com/LimeChain/goscale/issues/82 - - // SaturatingDiv(other Numeric) Numeric - // SaturatingMod(other Numeric) Numeric - - // CheckedAdd(other Numeric) (Numeric, error) - // CheckedSub(other Numeric) (Numeric, error) - // CheckedMul(other Numeric) (Numeric, error) - // CheckedDiv(other Numeric) (Numeric, error) - // CheckedMod(other Numeric) (Numeric, error) - - // And(other Numeric) Numeric - // Or(other Numeric) Numeric - // Xor(other Numeric) Numeric - // Not() Numeric - // Lsh(other Numeric) Numeric - // Rsh(other Numeric) Numeric - - TrailingZeros() Numeric - - Eq(other Numeric) bool - Ne(other Numeric) bool - Lt(other Numeric) bool - Lte(other Numeric) bool - Gt(other Numeric) bool - Gte(other Numeric) bool - - Max(other Numeric) Numeric - Min(other Numeric) Numeric - Clamp(minValue, maxValue Numeric) Numeric -} - -// Returns any Numeric type as a generic type N -func NewNumeric[N Numeric](n any) N { - switch reflect.Zero(reflect.TypeOf(*new(N))).Interface().(type) { - case U8: - switch n := n.(type) { - case uint: - return U8(n).Interface().(N) - case int: - return U8(n).Interface().(N) - case uint8: - return U8(n).Interface().(N) - case int8: - return U8(n).Interface().(N) - case uint16: - return U8(n).Interface().(N) - case int16: - return U8(n).Interface().(N) - case uint32: - return U8(n).Interface().(N) - case int32: - return U8(n).Interface().(N) - case uint64: - return U8(n).Interface().(N) - case int64: - return U8(n).Interface().(N) - case U8: - return U8(n).Interface().(N) - case I8: - return U8(n).Interface().(N) - case U16: - return U8(n).Interface().(N) - case I16: - return U8(n).Interface().(N) - case U32: - return U8(n).Interface().(N) - case I32: - return U8(n).Interface().(N) - case U64: - return U8(n).Interface().(N) - case I64: - return U8(n).Interface().(N) - case U128: - return To[U8](n).Interface().(N) - case I128: - return To[U8](n).Interface().(N) - default: - panic("unknown type for U8") - } - - case I8: - switch n := n.(type) { - case uint: - return I8(n).Interface().(N) - case int: - return I8(n).Interface().(N) - case uint8: - return I8(n).Interface().(N) - case int8: - return I8(n).Interface().(N) - case uint16: - return I8(n).Interface().(N) - case int16: - return I8(n).Interface().(N) - case uint32: - return I8(n).Interface().(N) - case int32: - return I8(n).Interface().(N) - case uint64: - return I8(n).Interface().(N) - case int64: - return I8(n).Interface().(N) - case U8: - return I8(n).Interface().(N) - case I8: - return I8(n).Interface().(N) - case U16: - return I8(n).Interface().(N) - case I16: - return I8(n).Interface().(N) - case U32: - return I8(n).Interface().(N) - case I32: - return I8(n).Interface().(N) - case U64: - return I8(n).Interface().(N) - case I64: - return I8(n).Interface().(N) - case U128: - return To[I8](n).Interface().(N) - case I128: - return To[I8](n).Interface().(N) - default: - panic("unknown type for I8") - } - - case U16: - switch n := n.(type) { - case uint: - return U16(n).Interface().(N) - case int: - return U16(n).Interface().(N) - case uint8: - return U16(n).Interface().(N) - case int8: - return U16(n).Interface().(N) - case uint16: - return U16(n).Interface().(N) - case int16: - return U16(n).Interface().(N) - case uint32: - return U16(n).Interface().(N) - case int32: - return U16(n).Interface().(N) - case uint64: - return U16(n).Interface().(N) - case int64: - return U16(n).Interface().(N) - case U8: - return U16(n).Interface().(N) - case I8: - return U16(n).Interface().(N) - case U16: - return n.Interface().(N) - case I16: - return U16(n).Interface().(N) - case U32: - return U16(n).Interface().(N) - case I32: - return U16(n).Interface().(N) - case U64: - return U16(n).Interface().(N) - case I64: - return U16(n).Interface().(N) - case U128: - return To[U16](n).Interface().(N) - case I128: - return To[U16](n).Interface().(N) - default: - panic("unknown type for U16") - } - - case I16: - switch n := n.(type) { - case uint: - return I16(n).Interface().(N) - case int: - return I16(n).Interface().(N) - case uint8: - return I16(n).Interface().(N) - case int8: - return I16(n).Interface().(N) - case uint16: - return I16(n).Interface().(N) - case int16: - return I16(n).Interface().(N) - case uint32: - return I16(n).Interface().(N) - case int32: - return I16(n).Interface().(N) - case uint64: - return I16(n).Interface().(N) - case int64: - return I16(n).Interface().(N) - case U8: - return I16(n).Interface().(N) - case I8: - return I16(n).Interface().(N) - case U16: - return I16(n).Interface().(N) - case I16: - return n.Interface().(N) - case U32: - return I16(n).Interface().(N) - case I32: - return I16(n).Interface().(N) - case U64: - return I16(n).Interface().(N) - case I64: - return I16(n).Interface().(N) - case U128: - return To[I16](n).Interface().(N) - case I128: - return To[I16](n).Interface().(N) - default: - panic("unknown type for I16") - } - - case U32: - switch n := n.(type) { - case uint: - return U32(n).Interface().(N) - case int: - return U32(n).Interface().(N) - case uint8: - return U32(n).Interface().(N) - case int8: - return U32(n).Interface().(N) - case uint16: - return U32(n).Interface().(N) - case int16: - return U32(n).Interface().(N) - case uint32: - return U32(n).Interface().(N) - case int32: - return U32(n).Interface().(N) - case uint64: - return U32(n).Interface().(N) - case int64: - return U32(n).Interface().(N) - case U8: - return U32(n).Interface().(N) - case I8: - return U32(n).Interface().(N) - case U16: - return U32(n).Interface().(N) - case I16: - return U32(n).Interface().(N) - case U32: - return U32(n).Interface().(N) - case I32: - return U32(n).Interface().(N) - case U64: - return U32(n).Interface().(N) - case I64: - return U32(n).Interface().(N) - case U128: - return To[U32](n).Interface().(N) - case I128: - return To[U32](n).Interface().(N) - default: - panic("unknown type for U32") - } - - case I32: - switch n := n.(type) { - case uint: - return I32(n).Interface().(N) - case int: - return I32(n).Interface().(N) - case uint8: - return I32(n).Interface().(N) - case int8: - return I32(n).Interface().(N) - case uint16: - return I32(n).Interface().(N) - case int16: - return I32(n).Interface().(N) - case uint32: - return I32(n).Interface().(N) - case int32: - return I32(n).Interface().(N) - case uint64: - return I32(n).Interface().(N) - case int64: - return I32(n).Interface().(N) - case U8: - return I32(n).Interface().(N) - case I8: - return I32(n).Interface().(N) - case U16: - return I32(n).Interface().(N) - case I16: - return I32(n).Interface().(N) - case U32: - return I32(n).Interface().(N) - case I32: - return I32(n).Interface().(N) - case U64: - return I32(n).Interface().(N) - case I64: - return I32(n).Interface().(N) - case U128: - return To[I32](n).Interface().(N) - case I128: - return To[I32](n).Interface().(N) - default: - panic("unknown type for I32") - } - - case U64: - switch n := n.(type) { - case uint: - return U64(n).Interface().(N) - case int: - return U64(n).Interface().(N) - case uint8: - return U64(n).Interface().(N) - case int8: - return U64(n).Interface().(N) - case uint16: - return U64(n).Interface().(N) - case int16: - return U64(n).Interface().(N) - case uint32: - return U64(n).Interface().(N) - case int32: - return U64(n).Interface().(N) - case uint64: - return U64(n).Interface().(N) - case int64: - return U64(n).Interface().(N) - case U8: - return U64(n).Interface().(N) - case I8: - return U64(n).Interface().(N) - case U16: - return U64(n).Interface().(N) - case I16: - return U64(n).Interface().(N) - case U32: - return U64(n).Interface().(N) - case I32: - return U64(n).Interface().(N) - case U64: - return U64(n).Interface().(N) - case I64: - return U64(n).Interface().(N) - case U128: - return To[U64](n).Interface().(N) - case I128: - return To[U64](n).Interface().(N) - default: - panic("unknown type for U64") - } - - case I64: - switch n := n.(type) { - case uint: - return I64(n).Interface().(N) - case int: - return I64(n).Interface().(N) - case uint8: - return I64(n).Interface().(N) - case int8: - return I64(n).Interface().(N) - case uint16: - return I64(n).Interface().(N) - case int16: - return I64(n).Interface().(N) - case uint32: - return I64(n).Interface().(N) - case int32: - return I64(n).Interface().(N) - case uint64: - return I64(n).Interface().(N) - case int64: - return I64(n).Interface().(N) - case U8: - return I64(n).Interface().(N) - case I8: - return I64(n).Interface().(N) - case U16: - return I64(n).Interface().(N) - case I16: - return I64(n).Interface().(N) - case U32: - return I64(n).Interface().(N) - case I32: - return I64(n).Interface().(N) - case U64: - return I64(n).Interface().(N) - case I64: - return I64(n).Interface().(N) - case U128: - return To[I64](n).Interface().(N) - case I128: - return To[I64](n).Interface().(N) - default: - panic("unknown primitive type for I64") - } - - case U128: - switch n := n.(type) { - case uint: - return NewU128(uint64(n)).Interface().(N) - case int: - return NewU128(uint64(n)).Interface().(N) - case uint8: - return NewU128(uint64(n)).Interface().(N) - case int8: - return NewU128(uint64(n)).Interface().(N) - case uint16: - return NewU128(uint64(n)).Interface().(N) - case int16: - return NewU128(uint64(n)).Interface().(N) - case uint32: - return NewU128(uint64(n)).Interface().(N) - case int32: - return NewU128(uint64(n)).Interface().(N) - case uint64: - return NewU128(uint64(n)).Interface().(N) - case int64: - return NewU128(uint64(n)).Interface().(N) - case U8: - return NewU128(uint64(n)).Interface().(N) - case I8: - return NewU128(uint64(n)).Interface().(N) - case U16: - return NewU128(uint64(n)).Interface().(N) - case I16: - return NewU128(uint64(n)).Interface().(N) - case U32: - return NewU128(uint64(n)).Interface().(N) - case I32: - return NewU128(uint64(n)).Interface().(N) - case U64: - return NewU128(uint64(n)).Interface().(N) - case I64: - return NewU128(uint64(n)).Interface().(N) - case U128: - return n.Interface().(N) - case I128: - return U128(n).Interface().(N) - default: - panic("unknown primitive type for U128") - } - - case I128: - switch n := n.(type) { - case uint: - return NewI128(int64(n)).Interface().(N) - case int: - return NewI128(int64(n)).Interface().(N) - case uint8: - return NewI128(int64(n)).Interface().(N) - case int8: - return NewI128(int64(n)).Interface().(N) - case uint16: - return NewI128(int64(n)).Interface().(N) - case int16: - return NewI128(int64(n)).Interface().(N) - case uint32: - return NewI128(int64(n)).Interface().(N) - case int32: - return NewI128(int64(n)).Interface().(N) - case uint64: - return NewI128(int64(n)).Interface().(N) - case int64: - return NewI128(int64(n)).Interface().(N) - case U8: - return NewI128(int64(n)).Interface().(N) - case I8: - return NewI128(int64(n)).Interface().(N) - case U16: - return NewI128(int64(n)).Interface().(N) - case I16: - return NewI128(int64(n)).Interface().(N) - case U32: - return NewI128(int64(n)).Interface().(N) - case I32: - return NewI128(int64(n)).Interface().(N) - case U64: - return NewI128(int64(n)).Interface().(N) - case I64: - return NewI128(int64(n)).Interface().(N) - case U128: - return To[I128](n).Interface().(N) - case I128: - return n.Interface().(N) - default: - panic("unknown primitive type for I128") - } - - default: - panic("unknown numeric type N in NewNumeric[N]") - } -} - -// Converts any Numeric type to any other Numeric type -func To[N Numeric](n Numeric) N { - switch reflect.Zero(reflect.TypeOf(*new(N))).Interface().(type) { - case U8: - switch n := n.(type) { - case U8: - return U8(n).Interface().(N) - case I8: - return U8(n).Interface().(N) - case U16: - return U8(n).Interface().(N) - case I16: - return U8(n).Interface().(N) - case U32: - return U8(n).Interface().(N) - case I32: - return U8(n).Interface().(N) - case U64: - return U8(n).Interface().(N) - case I64: - return U8(n).Interface().(N) - case U128: - return U8(n.ToBigInt().Uint64()).Interface().(N) - case I128: - return U8(n.ToBigInt().Uint64()).Interface().(N) - default: - panic("unknown numeric type for U8") - } - - case I8: - switch n := n.(type) { - case U8: - return I8(n).Interface().(N) - case I8: - return I8(n).Interface().(N) - case U16: - return I8(n).Interface().(N) - case I16: - return I8(n).Interface().(N) - case U32: - return I8(n).Interface().(N) - case I32: - return I8(n).Interface().(N) - case U64: - return I8(n).Interface().(N) - case I64: - return I8(n).Interface().(N) - case U128: - return I8(n.ToBigInt().Int64()).Interface().(N) - case I128: - return I8(n.ToBigInt().Int64()).Interface().(N) - default: - panic("unknown numeric type for I8") - } - - case U16: - switch n := n.(type) { - case U8: - return U16(n).Interface().(N) - case I8: - return U16(n).Interface().(N) - case U16: - return U16(n).Interface().(N) - case I16: - return U16(n).Interface().(N) - case U32: - return U16(n).Interface().(N) - case I32: - return U16(n).Interface().(N) - case U64: - return U16(n).Interface().(N) - case I64: - return U16(n).Interface().(N) - case U128: - return U16(n.ToBigInt().Uint64()).Interface().(N) - case I128: - return U16(n.ToBigInt().Uint64()).Interface().(N) - default: - panic("unknown numeric type for U16") - } - - case I16: - switch n := n.(type) { - case U8: - return I16(n).Interface().(N) - case I8: - return I16(n).Interface().(N) - case U16: - return I16(n).Interface().(N) - case I16: - return I16(n).Interface().(N) - case U32: - return I16(n).Interface().(N) - case I32: - return I16(n).Interface().(N) - case U64: - return I16(n).Interface().(N) - case I64: - return I16(n).Interface().(N) - case U128: - return I16(n.ToBigInt().Int64()).Interface().(N) - case I128: - return I16(n.ToBigInt().Int64()).Interface().(N) - default: - panic("unknown numeric type for I16") - } - - case U32: - switch n := n.(type) { - case U8: - return U32(n).Interface().(N) - case I8: - return U32(n).Interface().(N) - case U16: - return U32(n).Interface().(N) - case I16: - return U32(n).Interface().(N) - case U32: - return U32(n).Interface().(N) - case I32: - return U32(n).Interface().(N) - case U64: - return U32(n).Interface().(N) - case I64: - return U32(n).Interface().(N) - case U128: - return U32(n.ToBigInt().Uint64()).Interface().(N) - case I128: - return U32(n.ToBigInt().Uint64()).Interface().(N) - default: - panic("unknown numeric type for U32") - } - - case I32: - switch n := n.(type) { - case U8: - return I32(n).Interface().(N) - case I8: - return I32(n).Interface().(N) - case U16: - return I32(n).Interface().(N) - case I16: - return I32(n).Interface().(N) - case U32: - return I32(n).Interface().(N) - case I32: - return I32(n).Interface().(N) - case U64: - return I32(n).Interface().(N) - case I64: - return I32(n).Interface().(N) - case U128: - return I32(n.ToBigInt().Int64()).Interface().(N) - case I128: - return I32(n.ToBigInt().Int64()).Interface().(N) - default: - panic("unknown numeric type for I32") - } - - case U64: - switch n := n.(type) { - case U8: - return U64(n).Interface().(N) - case I8: - return U64(n).Interface().(N) - case U16: - return U64(n).Interface().(N) - case I16: - return U64(n).Interface().(N) - case U32: - return U64(n).Interface().(N) - case I32: - return U64(n).Interface().(N) - case U64: - return U64(n).Interface().(N) - case I64: - return U64(n).Interface().(N) - case U128: - return U64(n.ToBigInt().Uint64()).Interface().(N) - case I128: - return U64(n.ToBigInt().Uint64()).Interface().(N) - default: - panic("unknown numeric type for U64") - } - - case I64: - switch n := n.(type) { - case U8: - return I64(n).Interface().(N) - case I8: - return I64(n).Interface().(N) - case U16: - return I64(n).Interface().(N) - case I16: - return I64(n).Interface().(N) - case U32: - return I64(n).Interface().(N) - case I32: - return I64(n).Interface().(N) - case U64: - return I64(n).Interface().(N) - case I64: - return I64(n).Interface().(N) - case U128: - return I64(n.ToBigInt().Int64()).Interface().(N) - case I128: - return I64(n.ToBigInt().Int64()).Interface().(N) - default: - panic("unknown numeric type for I64") - } - - case U128: - switch n := n.(type) { - case U8: - return NewU128(uint64(n)).Interface().(N) - case I8: - return NewU128(uint64(n)).Interface().(N) - case U16: - return NewU128(uint64(n)).Interface().(N) - case I16: - return NewU128(uint64(n)).Interface().(N) - case U32: - return NewU128(uint64(n)).Interface().(N) - case I32: - return NewU128(uint64(n)).Interface().(N) - case U64: - return NewU128(uint64(n)).Interface().(N) - case I64: - return NewU128(uint64(n)).Interface().(N) - case U128: - return n.Interface().(N) - case I128: - return NewU128(n).Interface().(N) - default: - panic("unknown numeric type for U128") - } - - case I128: - switch n := n.(type) { - case U8: - return NewI128(uint64(n)).Interface().(N) - case I8: - return NewI128(int64(n)).Interface().(N) - case U16: - return NewI128(uint64(n)).Interface().(N) - case I16: - return NewI128(int64(n)).Interface().(N) - case U32: - return NewI128(uint64(n)).Interface().(N) - case I32: - return NewI128(int64(n)).Interface().(N) - case U64: - return NewI128(uint64(n)).Interface().(N) - case I64: - return NewI128(int64(n)).Interface().(N) - case U128: - return NewI128(n).Interface().(N) - case I128: - return n.Interface().(N) - default: - panic("unknown numeric type N in To[N]") - } - - default: - panic("unknown numeric type N in To[N]") - } -} - // Converts any integer value to 128 bits representation func anyIntegerTo128Bits[N Integer128](n any) N { switch n := n.(type) { @@ -845,9 +45,9 @@ func anyIntegerTo128Bits[N Integer128](n any) N { case uint32: return bigIntToGeneric[N](new(big.Int).SetUint64(uint64(n))) case int64: - return bigIntToGeneric[N](new(big.Int).SetInt64(int64(n))) + return bigIntToGeneric[N](new(big.Int).SetInt64(n)) case uint64: - return bigIntToGeneric[N](new(big.Int).SetUint64(uint64(n))) + return bigIntToGeneric[N](new(big.Int).SetUint64(n)) case I8: return bigIntToGeneric[N](new(big.Int).SetInt64(int64(n))) case U8: @@ -886,9 +86,9 @@ func stringTo128Bits[N Integer128](s string) (N, error) { func bigIntToGeneric[N Integer128](bn *big.Int) N { switch reflect.Zero(reflect.TypeOf(*new(N))).Interface().(type) { case I128: - return bigIntToI128(bn).Interface().(N) + return N(bigIntToI128(bn)) case U128: - return bigIntToU128(bn).Interface().(N) + return N(bigIntToU128(bn)) default: panic("unknown numeric type in bigIntToGeneric") } diff --git a/numeric_i128.go b/numeric_i128.go deleted file mode 100644 index 68fbb7e..0000000 --- a/numeric_i128.go +++ /dev/null @@ -1,230 +0,0 @@ -package goscale - -import ( - "encoding/binary" - "math/big" - "math/bits" -) - -// little endian byte order -// [0] least significant bits -// [1] most significant bits -type I128 [2]U64 - -func NewI128[N Integer](n N) I128 { - return anyIntegerTo128Bits[I128](n) -} - -func NewI128FromString(n string) (I128, error) { - return stringTo128Bits[I128](n) -} - -func bigIntToI128(n *big.Int) I128 { - bytes := make([]byte, 16) - n.FillBytes(bytes) - reverseSlice(bytes) - - var result = I128{ - U64(binary.LittleEndian.Uint64(bytes[:8])), - U64(binary.LittleEndian.Uint64(bytes[8:])), - } - - if n.Sign() < 0 { - result = negateI128(result) - } - - return result -} - -func (n I128) ToBigInt() *big.Int { - isNegative := n.isNegative() - - 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 -} - -// ff ff ff ff ff ff ff ff | 7f ff ff ff ff ff ff ff -func MaxI128() I128 { - return I128{ - U64(^uint64(0)), - U64(^uint64(0) >> 1), - } -} - -// 00 00 00 00 00 00 00 00 | 80 00 00 00 00 00 00 00 -func MinI128() I128 { - return I128{ - U64(0), - U64(1 << 63), - } -} - -func (n I128) Interface() Numeric { - return n -} - -func (a I128) Add(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) - return I128{U64(sumLow), U64(sumHigh)} -} - -func (a I128) Sub(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) - return I128{U64(diffLow), U64(diffHigh)} -} - -func (a I128) Mul(b Numeric) Numeric { - negA := a[1]>>(64-1) == 1 - negB := b.(I128)[1]>>(64-1) == 1 - - absA := a - if negA { - absA = negateI128(a) - } - - absB := b.(I128) - if negB { - absB = negateI128(b.(I128)) - } - - high, low := bits.Mul64(uint64(absA[0]), uint64(absB[0])) - high += uint64(absA[1])*uint64(absB[0]) + uint64(absA[0])*uint64(absB[1]) - - result := I128{U64(low), U64(high)} - - // if one of the operands is negative the result is also negative - if negA != negB { - return negateI128(result) - } - return result -} - -func (a I128) Div(b Numeric) Numeric { - return bigIntToI128( - new(big.Int).Div(a.ToBigInt(), b.(I128).ToBigInt()), - ) -} - -func (a I128) Mod(b Numeric) Numeric { - return bigIntToI128( - new(big.Int).Mod(a.ToBigInt(), b.(I128).ToBigInt()), - ) -} - -func (a I128) Eq(b Numeric) bool { - return a.ToBigInt().Cmp(b.(I128).ToBigInt()) == 0 -} - -func (a I128) Ne(b Numeric) bool { - return !a.Eq(b) -} - -func (a I128) Lt(b Numeric) bool { - return a.ToBigInt().Cmp(b.(I128).ToBigInt()) < 0 -} - -func (a I128) Lte(b Numeric) bool { - return a.ToBigInt().Cmp(b.(I128).ToBigInt()) <= 0 -} - -func (a I128) Gt(b Numeric) bool { - return a.ToBigInt().Cmp(b.(I128).ToBigInt()) > 0 -} - -func (a I128) Gte(b Numeric) bool { - return a.ToBigInt().Cmp(b.(I128).ToBigInt()) >= 0 -} - -func (a I128) Max(b Numeric) Numeric { - if a.Gt(b) { - return a - } - return b -} - -func (a I128) Min(b Numeric) Numeric { - if a.Lt(b) { - return a - } - return b -} - -func (a I128) Clamp(minValue, maxValue Numeric) Numeric { - if a.Lt(minValue) { - return minValue - } - if a.Gt(maxValue) { - return maxValue - } - return a -} - -func (a I128) TrailingZeros() Numeric { - return NewI128(a.ToBigInt().TrailingZeroBits()) -} - -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() - } - // check for underflow - if a[1]&(1<<63) != 0 && b.(I128)[1]&(1<<63) != 0 && sumHigh&(1<<63) == 0 { - return MinI128() - } - return I128{U64(sumLow), U64(sumHigh)} -} - -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() - } - // check for underflow - if a[1]&(1<<63) != 0 && b.(I128)[1]&(1<<63) == 0 && diffHigh&(1<<63) == 0 { - return MinI128() - } - return I128{U64(diffLow), U64(diffHigh)} -} - -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 - 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)) - if result.Cmp(maxI128) > 0 { - return bigIntToI128(maxI128) - } else if result.Cmp(minI128) < 0 { - return bigIntToI128(minI128) - } - return bigIntToI128(result) -} - -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)} -} diff --git a/numeric_i16.go b/numeric_i16.go deleted file mode 100644 index d847fff..0000000 --- a/numeric_i16.go +++ /dev/null @@ -1,123 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type I16 int16 - -func (n I16) Interface() Numeric { - return n -} - -func (a I16) Add(b Numeric) Numeric { - return a + b.(I16) -} - -func (a I16) Sub(b Numeric) Numeric { - return a - b.(I16) -} - -func (a I16) Mul(b Numeric) Numeric { - return a * b.(I16) -} - -func (a I16) Div(b Numeric) Numeric { - return a / b.(I16) -} - -func (a I16) Mod(b Numeric) Numeric { - return a % b.(I16) -} - -func (a I16) Eq(b Numeric) bool { - return a == b.(I16) -} - -func (a I16) Ne(b Numeric) bool { - return a != b.(I16) -} - -func (a I16) Lt(b Numeric) bool { - return a < b.(I16) -} - -func (a I16) Lte(b Numeric) bool { - return a <= b.(I16) -} - -func (a I16) Gt(b Numeric) bool { - return a > b.(I16) -} - -func (a I16) Gte(b Numeric) bool { - return a >= b.(I16) -} - -func (a I16) Max(b Numeric) Numeric { - if a > b.(I16) { - return a - } - return b -} - -func (a I16) Min(b Numeric) Numeric { - if a < b.(I16) { - return a - } - return b -} - -func (a I16) Clamp(min, max Numeric) Numeric { - if a < min.(I16) { - return min - } else if a > max.(I16) { - return max - } else { - return a - } -} - -func (a I16) TrailingZeros() Numeric { - return I16(bits.TrailingZeros(uint(a))) -} - -func (a I16) SaturatingAdd(b Numeric) Numeric { - sum := int32(a) + int32(b.(I16)) - // check for overflow and underflow - if sum > math.MaxInt16 { - return I16(math.MaxInt16) - } else if sum < math.MinInt16 { - return I16(math.MinInt16) - } - return I16(sum) -} - -func (a I16) SaturatingSub(b Numeric) Numeric { - diff := int32(a) - int32(b.(I16)) - // check for overflow - if diff > int32(math.MaxInt16) { - return I16(math.MaxInt16) - } - // check for underflow - if diff < int32(math.MinInt16) { - return I16(math.MinInt16) - } - return I16(diff) -} - -func (a I16) SaturatingMul(b Numeric) Numeric { - if a == 0 || b.(I16) == 0 { - return I16(0) - } - - product := int32(a) * int32(b.(I16)) - // check for overflow and underflow - if product > math.MaxInt16 { - return I16(math.MaxInt16) - } else if product < math.MinInt16 { - return I16(math.MinInt16) - } - return I16(product) -} diff --git a/numeric_i32.go b/numeric_i32.go deleted file mode 100644 index 3bc9e61..0000000 --- a/numeric_i32.go +++ /dev/null @@ -1,123 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type I32 int32 - -func (n I32) Interface() Numeric { - return n -} - -func (a I32) Add(b Numeric) Numeric { - return a + b.(I32) -} - -func (a I32) Sub(b Numeric) Numeric { - return a - b.(I32) -} - -func (a I32) Mul(b Numeric) Numeric { - return a * b.(I32) -} - -func (a I32) Div(b Numeric) Numeric { - return a / b.(I32) -} - -func (a I32) Mod(b Numeric) Numeric { - return a % b.(I32) -} - -func (a I32) Eq(b Numeric) bool { - return a == b.(I32) -} - -func (a I32) Ne(b Numeric) bool { - return a != b.(I32) -} - -func (a I32) Lt(b Numeric) bool { - return a < b.(I32) -} - -func (a I32) Lte(b Numeric) bool { - return a <= b.(I32) -} - -func (a I32) Gt(b Numeric) bool { - return a > b.(I32) -} - -func (a I32) Gte(b Numeric) bool { - return a >= b.(I32) -} - -func (a I32) Max(b Numeric) Numeric { - if a > b.(I32) { - return a - } - return b -} - -func (a I32) Min(b Numeric) Numeric { - if a < b.(I32) { - return a - } - return b -} - -func (a I32) Clamp(min, max Numeric) Numeric { - if a < min.(I32) { - return min - } else if a > max.(I32) { - return max - } else { - return a - } -} - -func (a I32) TrailingZeros() Numeric { - return I32(bits.TrailingZeros(uint(a))) -} - -func (a I32) SaturatingAdd(b Numeric) Numeric { - sum := int64(a) + int64(b.(I32)) - // check for overflow and underflow - if sum > math.MaxInt32 { - return I32(math.MaxInt32) - } else if sum < math.MinInt32 { - return I32(math.MinInt32) - } - return I32(sum) -} - -func (a I32) SaturatingSub(b Numeric) Numeric { - diff := int64(a) - int64(b.(I32)) - // check for overflow - if diff > int64(math.MaxInt32) { - return I32(math.MaxInt32) - } - // check for underflow - if diff < int64(math.MinInt32) { - return I32(math.MinInt32) - } - return I32(diff) -} - -func (a I32) SaturatingMul(b Numeric) Numeric { - if a == 0 || b.(I32) == 0 { - return I32(0) - } - - product := int64(a) * int64(b.(I32)) - // check for overflow and underflow - if product > math.MaxInt32 { - return I32(math.MaxInt32) - } else if product < math.MinInt32 { - return I32(math.MinInt32) - } - return I32(product) -} diff --git a/numeric_i64.go b/numeric_i64.go deleted file mode 100644 index 31cc5f3..0000000 --- a/numeric_i64.go +++ /dev/null @@ -1,128 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type I64 int64 - -func (n I64) Interface() Numeric { - return n -} - -func (a I64) Add(b Numeric) Numeric { - return a + b.(I64) -} - -func (a I64) Sub(b Numeric) Numeric { - return a - b.(I64) -} - -func (a I64) Mul(b Numeric) Numeric { - return a * b.(I64) -} - -func (a I64) Div(b Numeric) Numeric { - return a / b.(I64) -} - -func (a I64) Mod(b Numeric) Numeric { - return a % b.(I64) -} - -func (a I64) Eq(b Numeric) bool { - return a == b.(I64) -} - -func (a I64) Ne(b Numeric) bool { - return a != b.(I64) -} - -func (a I64) Lt(b Numeric) bool { - return a < b.(I64) -} - -func (a I64) Lte(b Numeric) bool { - return a <= b.(I64) -} - -func (a I64) Gt(b Numeric) bool { - return a > b.(I64) -} - -func (a I64) Gte(b Numeric) bool { - return a >= b.(I64) -} - -func (a I64) Max(b Numeric) Numeric { - if a > b.(I64) { - return a - } - return b -} - -func (a I64) Min(b Numeric) Numeric { - if a < b.(I64) { - return a - } - return b -} - -func (a I64) Clamp(min, max Numeric) Numeric { - if a < min.(I64) { - return min - } else if a > max.(I64) { - return max - } else { - return a - } -} - -func (a I64) TrailingZeros() Numeric { - return I64(bits.TrailingZeros64(uint64(a))) -} - -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 && int64(sum) < int64(a) { - return I64(math.MaxInt64) - } - // check for underflow - if a < 0 && b.(I64) < 0 && int64(sum) >= int64(a) { - return I64(math.MinInt64) - } - return I64(sum) -} - -func (a I64) SaturatingSub(b Numeric) Numeric { - 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 I64(diff) -} - -func (a I64) SaturatingMul(b Numeric) Numeric { - if uint64(a) == 0 || uint64(b.(I64)) == 0 { - return I64(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) - } - return I64(int64(lo)) -} diff --git a/numeric_i8.go b/numeric_i8.go deleted file mode 100644 index 3a22cc6..0000000 --- a/numeric_i8.go +++ /dev/null @@ -1,123 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type I8 int8 - -func (n I8) Interface() Numeric { - return n -} - -func (a I8) Add(b Numeric) Numeric { - return a + b.(I8) -} - -func (a I8) Sub(b Numeric) Numeric { - return a - b.(I8) -} - -func (a I8) Mul(b Numeric) Numeric { - return a * b.(I8) -} - -func (a I8) Div(b Numeric) Numeric { - return a / b.(I8) -} - -func (a I8) Mod(b Numeric) Numeric { - return a % b.(I8) -} - -func (a I8) Eq(b Numeric) bool { - return a == b.(I8) -} - -func (a I8) Ne(b Numeric) bool { - return a != b.(I8) -} - -func (a I8) Lt(b Numeric) bool { - return a < b.(I8) -} - -func (a I8) Lte(b Numeric) bool { - return a <= b.(I8) -} - -func (a I8) Gt(b Numeric) bool { - return a > b.(I8) -} - -func (a I8) Gte(b Numeric) bool { - return a >= b.(I8) -} - -func (a I8) Max(b Numeric) Numeric { - if a > b.(I8) { - return a - } - return b -} - -func (a I8) Min(b Numeric) Numeric { - if a < b.(I8) { - return a - } - return b -} - -func (a I8) Clamp(min, max Numeric) Numeric { - if a < min.(I8) { - return min - } else if a > max.(I8) { - return max - } else { - return a - } -} - -func (a I8) TrailingZeros() Numeric { - return I8(bits.TrailingZeros(uint(a))) -} - -func (a I8) SaturatingAdd(b Numeric) Numeric { - sum := int16(a) + int16(b.(I8)) - // check for overflow and underflow - if sum > int16(math.MaxInt8) { - return I8(math.MaxInt8) - } else if sum < int16(math.MinInt8) { - return I8(math.MinInt8) - } - return I8(sum) -} - -func (a I8) SaturatingSub(b Numeric) Numeric { - diff := int16(a) - int16(b.(I8)) - // check for overflow - if diff > int16(math.MaxInt8) { - return I8(math.MaxInt8) - } - // check for underflow - if diff < int16(math.MinInt8) { - return I8(math.MinInt8) - } - return I8(diff) -} - -func (a I8) SaturatingMul(b Numeric) Numeric { - if a == 0 || b.(I8) == 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 < int16(math.MinInt8) { - return I8(math.MinInt8) - } - return I8(product) -} diff --git a/numeric_test.go b/numeric_test.go index bea900b..860f23e 100644 --- a/numeric_test.go +++ b/numeric_test.go @@ -8,1001 +8,6 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_Add(t *testing.T) { - bn1, _ := NewU128FromString("340282366920938463463374607431768211454") - bn2, _ := NewI128FromString("170141183460469231731687303715884105726") - - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"254+1", U8(254), U8(1), U8(math.MaxUint8)}, - {"126+1", I8(126), I8(1), I8(math.MaxInt8)}, - {"65534+1", U16(65534), U16(1), U16(math.MaxUint16)}, - {"32766+1", I16(32766), I16(1), I16(math.MaxInt16)}, - {"4294967294+1", U32(4294967294), U32(1), U32(math.MaxUint32)}, - {"2147483646+1", I32(2147483646), I32(1), I32(math.MaxInt32)}, - {"18446744073709551614+1", U64(18446744073709551614), U64(1), U64(math.MaxUint64)}, - {"9223372036854775806+1", I64(9223372036854775806), I64(1), I64(math.MaxInt64)}, - {"340282366920938463463374607431768211454+1", bn1, NewU128(1), MaxU128()}, - {"170141183460469231731687303715884105726+1", bn2, NewI128(1), MaxI128()}, - {"255+1", U8(math.MaxUint8), U8(1), U8(0)}, - {"65535+1", U16(math.MaxUint16), U16(1), U16(0)}, - {"4294967295+1", U32(math.MaxUint32), U32(1), U32(0)}, - {"18446744073709551615+1", U64(math.MaxUint64), U64(1), U64(0)}, - {"340282366920938463463374607431768211455+1", MaxU128(), NewU128(1), NewU128(0)}, - {"-2+(-1)", NewI128(-2), NewI128(-1), NewI128(-3)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Add(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Sub(t *testing.T) { - u1, _ := NewU128FromString("340282366920938463463374607431657675311") - u2, _ := NewU128FromString("340282366920938463463374607421657675311") - u3, _ := NewI128FromString("340282366920938463463374607431657675311") - u4, _ := NewI128FromString("340282366920938463463374607421657675311") - u5, _ := NewI128FromString("340282366920938463463374607431657675310") - - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"2-1", U8(2), U8(1), U8(1)}, - {"0-1", U8(0), U8(1), U8(math.MaxUint8)}, - {"2-1", I8(2), I8(1), I8(1)}, - {"-128-1", I8(math.MinInt8), I8(1), I8(math.MaxInt8)}, - {"2-1", U16(2), U16(1), U16(1)}, - {"0-1", U16(0), U16(1), U16(math.MaxUint16)}, - {"2-1", I16(2), I16(1), I16(1)}, - {"-32768-1", I16(math.MinInt16), I16(1), I16(math.MaxInt16)}, - {"0-1", U32(0), U32(1), U32(math.MaxUint32)}, - {"2-1", U32(2), U32(1), U32(1)}, - {"2-1", I32(2), I32(1), I32(1)}, - {"-2147483648-1", I32(math.MinInt32), I32(1), I32(math.MaxInt32)}, - {"2-1", U64(2), U64(1), U64(1)}, - {"0-1", U64(0), U64(1), U64(math.MaxUint64)}, - {"2-1", I64(2), I64(1), I64(1)}, - {"-9223372036854775808-1", I64(math.MinInt64), I64(1), I64(math.MaxInt64)}, - {"2-1", NewU128(2), NewU128(1), NewU128(1)}, - {"0-1", NewU128(0), NewU128(1), MaxU128()}, - {"2-1", NewI128(2), NewI128(1), NewI128(1)}, - {"-2-(-1)", NewI128(-2), NewI128(-1), NewI128(-1)}, - {"-170141183460469231731687303715884105728-1", MinI128(), NewI128(1), MaxI128()}, - {"499999889463855-10000000000", NewU128(499999889463855), NewU128(10000000000), NewU128(499989889463855)}, - {"499999889463855-10000000000", NewI128(499999889463855), NewI128(10000000000), NewI128(499989889463855)}, - {"340282366920938463463374607431657675311-10000000000", u1, NewU128(10000000000), u2}, - {"340282366920938463463374607431657675311-10000000000", u3, NewI128(10000000000), u4}, - {"9889463854-10000000000", NewI128(9889463854), NewI128(10000000000), u5}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Sub(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Mul(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"2*3", U8(2), U8(3), U8(6)}, - {"MaxU8*0", U8(math.MaxUint8), U8(0), U8(0)}, - {"MaxU8*1", U8(math.MaxUint8), U8(1), U8(math.MaxUint8)}, - {"MaxU8*2", U8(math.MaxUint8), U8(2), U8(math.MaxUint8).Sub(U8(1))}, - {"MaxU8*MaxU8", U8(math.MaxUint8), U8(math.MaxUint8), U8(1)}, - - {"2*3", I8(2), I8(3), I8(6)}, - {"MaxI8*0", I8(math.MaxInt8), I8(0), I8(0)}, - {"MaxI8*1", I8(math.MaxInt8), I8(1), I8(math.MaxInt8)}, - {"MaxI8*2", I8(math.MaxInt8), I8(2), I8(-2)}, - {"MaxI8*MaxI8", I8(math.MaxInt8), I8(math.MaxInt8), I8(1)}, - {"MinI8*0", I8(math.MinInt8), I8(0), I8(0)}, - {"MinI8*1", I8(math.MinInt8), I8(1), I8(math.MinInt8)}, - {"MinI8*2", I8(math.MinInt8), I8(2), I8(0)}, - {"MinI8*MinI8", I8(math.MinInt8), I8(math.MinInt8), I8(0)}, - - {"2*3", U16(2), U16(3), U16(6)}, - {"MaxU16*0", U16(math.MaxUint16), U16(0), U16(0)}, - {"MaxU16*1", U16(math.MaxUint16), U16(1), U16(math.MaxUint16)}, - {"MaxU16*2", U16(math.MaxUint16), U16(2), U16(math.MaxUint16).Sub(U16(1))}, - {"MaxU16*MaxU16", U16(math.MaxUint16), U16(math.MaxUint16), U16(1)}, - - {"2*3", I16(2), I16(3), I16(6)}, - {"MaxI16*0", I16(math.MaxInt16), I16(0), I16(0)}, - {"MaxI16*1", I16(math.MaxInt16), I16(1), I16(math.MaxInt16)}, - {"MaxI16*2", I16(math.MaxInt16), I16(2), I16(-2)}, - {"MaxI16*MaxI16", I16(math.MaxInt16), I16(math.MaxInt16), I16(1)}, - {"MinI16*0", I16(math.MinInt16), I16(0), I16(0)}, - {"MinI16*1", I16(math.MinInt16), I16(1), I16(math.MinInt16)}, - {"MinI16*2", I16(math.MinInt16), I16(2), I16(0)}, - {"MinI16*MinI16", I16(math.MinInt16), I16(math.MinInt16), I16(0)}, - - {"2*3", U32(2), U32(3), U32(6)}, - {"MaxU32*0", U32(math.MaxUint32), U32(0), U32(0)}, - {"MaxU32*1", U32(math.MaxUint32), U32(1), U32(math.MaxUint32)}, - {"MaxU32*2", U32(math.MaxUint32), U32(2), U32(math.MaxUint32).Sub(U32(1))}, - {"MaxU32*MaxU32", U32(math.MaxUint32), U32(math.MaxUint32), U32(1)}, - - {"2*3", I32(2), I32(3), I32(6)}, - {"MaxI32*0", I32(math.MaxInt32), I32(0), I32(0)}, - {"MaxI32*1", I32(math.MaxInt32), I32(1), I32(math.MaxInt32)}, - {"MaxI32*2", I32(math.MaxInt32), I32(2), I32(-2)}, - {"MaxI32*MaxI32", I32(math.MaxInt32), I32(math.MaxInt32), I32(1)}, - {"MinI32*0", I32(math.MinInt32), I32(0), I32(0)}, - {"MinI32*1", I32(math.MinInt32), I32(1), I32(math.MinInt32)}, - {"MinI32*2", I32(math.MinInt32), I32(2), I32(0)}, - {"MinI32*MinI32", I32(math.MinInt32), I32(math.MinInt32), I32(0)}, - - {"2*3", U64(2), U64(3), U64(6)}, - {"MaxU64*0", U64(math.MaxUint64), U64(0), U64(0)}, - {"MaxU64*1", U64(math.MaxUint64), U64(1), U64(math.MaxUint64)}, - {"MaxU64*2", U64(math.MaxUint64), U64(2), U64(math.MaxUint64).Sub(U64(1))}, - {"MaxU64*MaxU64", U64(math.MaxUint64), U64(math.MaxUint64), U64(1)}, - - {"2*3", I64(2), I64(3), I64(6)}, - {"MaxI64*0", I64(math.MaxInt64), I64(0), I64(0)}, - {"MaxI64*1", I64(math.MaxInt64), I64(1), I64(math.MaxInt64)}, - {"MaxI64*2", I64(math.MaxInt64), I64(2), I64(-2)}, - {"MaxI64*MaxI64", I64(math.MaxInt64), I64(math.MaxInt64), I64(1)}, - {"MinI64*0", I64(math.MinInt64), I64(0), I64(0)}, - {"MinI64*1", I64(math.MinInt64), I64(1), I64(math.MinInt64)}, - {"MinI64*2", I64(math.MinInt64), I64(2), I64(0)}, - {"MinI64*MinI64", I64(math.MinInt64), I64(math.MinInt64), I64(0)}, - - {"2*3", NewU128(2), NewU128(3), NewU128(6)}, - {"MaxU128*0", MaxU128(), NewU128(0), NewU128(0)}, - {"MaxU128*1", MaxU128(), NewU128(1), MaxU128()}, - {"MaxU128*2", MaxU128(), NewU128(2), MaxU128().Sub(NewU128(1))}, - {"MaxU128*MaxU128", MaxU128(), MaxU128(), NewU128(1)}, - - {"MaxI128*0", MaxI128(), NewI128(0), NewI128(0)}, - {"MaxI128*1", MaxI128(), NewI128(1), MaxI128()}, - {"MaxI128*2", MaxI128(), NewI128(2), NewI128(-2)}, - {"MaxI128*MaxI128", MaxI128(), MaxI128(), NewI128(1)}, - - {"-2*3", NewI128(-2), NewI128(3), NewI128(-6)}, - {"-2*-3", NewI128(-2), NewI128(-3), NewI128(6)}, - {"1*0", NewI128(1), NewI128(0), NewI128(0)}, - {"-1*0", NewI128(-1), NewI128(0), NewI128(0)}, - {"1*1", NewI128(1), NewI128(1), NewI128(1)}, - {"-1*1", NewI128(-1), NewI128(1), NewI128(-1)}, - {"-1*-1", NewI128(-1), NewI128(-1), NewI128(1)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Mul(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Div(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"18446744073709551615/1", U64(math.MaxUint64), U64(1), U64(math.MaxUint64)}, - - {"0/1", NewU128(0), NewU128(1), NewU128(0)}, - {"1/1", NewU128(1), NewU128(1), NewU128(1)}, - - {"0/-1", NewI128(0), NewI128(-1), NewI128(0)}, - {"-1/1", NewI128(-1), NewI128(1), NewI128(-1)}, - {"-1/-1", NewI128(-1), NewI128(-1), NewI128(1)}, - - {"6/3", NewU128(6), NewU128(3), NewU128(2)}, - {"-6/2", NewI128(-6), NewI128(2), NewI128(-3)}, - - {"32/4", U64(32), U64(4), U64(8)}, - {"-32/4", I64(-32), I64(4), I64(-8)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Div(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Mod(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"1%1", U8(1), U8(1), U8(0)}, - {"1%2", U8(1), U8(2), U8(1)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Mod(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Max(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"Max(1, 2)", U8(1), U8(2), U8(2)}, - {"Max(1, MaxU8)", U8(1), U8(math.MaxUint8), U8(math.MaxUint8)}, - {"Max(MaxU8, MaxU8)", U8(math.MaxUint8), U8(math.MaxUint8), U8(math.MaxUint8)}, - {"Max(-2, -1)", I8(-2), I8(-1), I8(-1)}, - {"Max(-2, MaxI8)", I8(-2), I8(math.MaxInt8), I8(math.MaxInt8)}, - {"Max(MaxI8, MaxI8)", I8(math.MaxInt8), I8(math.MaxInt8), I8(math.MaxInt8)}, - {"Max(1, 2)", U16(1), U16(2), U16(2)}, - {"Max(1, MaxU16)", U16(1), U16(math.MaxUint16), U16(math.MaxUint16)}, - {"Max(MaxU16, MaxU16)", U16(math.MaxUint16), U16(math.MaxUint16), U16(math.MaxUint16)}, - {"Max(-2, -1)", I16(-2), I16(-1), I16(-1)}, - {"Max(-2, MaxI16)", I16(-2), I16(math.MaxInt16), I16(math.MaxInt16)}, - {"Max(MaxI16, MaxI16)", I16(math.MaxInt16), I16(math.MaxInt16), I16(math.MaxInt16)}, - {"Max(1, 2)", U32(1), U32(2), U32(2)}, - {"Max(1, MaxU32)", U32(1), U32(math.MaxUint32), U32(math.MaxUint32)}, - {"Max(MaxU32, MaxU32)", U32(math.MaxUint32), U32(math.MaxUint32), U32(math.MaxUint32)}, - {"Max(-2, -1)", I32(-2), I32(-1), I32(-1)}, - {"Max(-2, MaxI32)", I32(-2), I32(math.MaxInt32), I32(math.MaxInt32)}, - {"Max(MaxI32, MaxI32)", I32(math.MaxInt32), I32(math.MaxInt32), I32(math.MaxInt32)}, - {"Max(1, 2)", U64(1), U64(2), U64(2)}, - {"Max(1, MaxU64)", U64(1), U64(math.MaxUint64), U64(math.MaxUint64)}, - {"Max(MaxU64, MaxU64)", U64(math.MaxUint64), U64(math.MaxUint64), U64(math.MaxUint64)}, - {"Max(-2, -1)", I64(-2), I64(-1), I64(-1)}, - {"Max(-2, MaxI64)", I64(-2), I64(math.MaxInt64), I64(math.MaxInt64)}, - {"Max(MaxI64, MaxI64)", I64(math.MaxInt64), I64(math.MaxInt64), I64(math.MaxInt64)}, - {"Max(1, 2)", NewU128(1), NewU128(2), NewU128(2)}, - {"Max(1, MaxU128)", NewU128(1), MaxU128(), MaxU128()}, - {"Max(MaxU128, MaxU128)", MaxU128(), MaxU128(), MaxU128()}, - {"Max(-2, -1)", NewI128(-2), NewI128(-1), NewI128(-1)}, - {"Max(-2, MaxI128)", NewI128(-2), MaxI128(), MaxI128()}, - {"Max(MaxI128, MaxI128)", MaxI128(), MaxI128(), MaxI128()}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Max(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Min(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"Min(1, 2)", U8(1), U8(2), U8(1)}, - {"Min(1, MaxU8)", U8(1), U8(math.MaxUint8), U8(1)}, - {"Min(MaxU8, MaxU8)", U8(math.MaxUint8), U8(math.MaxUint8), U8(math.MaxUint8)}, - {"Min(-2, -1)", I8(-2), I8(-1), I8(-2)}, - {"Min(-2, MaxI8)", I8(-2), I8(math.MaxInt8), I8(-2)}, - {"Min(1, 2)", U16(1), U16(2), U16(1)}, - {"Min(1, MaxU16)", U16(1), U16(math.MaxUint16), U16(1)}, - {"Min(MaxU16, MaxU16)", U16(math.MaxUint16), U16(math.MaxUint16), U16(math.MaxUint16)}, - {"Min(-2, -1)", I16(-2), I16(-1), I16(-2)}, - {"Min(-2, MaxI16)", I16(-2), I16(math.MaxInt16), I16(-2)}, - {"Min(1, 2)", U32(1), U32(2), U32(1)}, - {"Min(1, MaxU32)", U32(1), U32(math.MaxUint32), U32(1)}, - {"Min(MaxU32, MaxU32)", U32(math.MaxUint32), U32(math.MaxUint32), U32(math.MaxUint32)}, - {"Min(-2, -1)", I32(-2), I32(-1), I32(-2)}, - {"Min(-2, MaxI32)", I32(-2), I32(math.MaxInt32), I32(-2)}, - {"Min(1, 2)", U64(1), U64(2), U64(1)}, - {"Min(1, MaxU64)", U64(1), U64(math.MaxUint64), U64(1)}, - {"Min(MaxU64, MaxU64)", U64(math.MaxUint64), U64(math.MaxUint64), U64(math.MaxUint64)}, - {"Min(-2, -1)", I64(-2), I64(-1), I64(-2)}, - {"Min(-2, MaxI64)", I64(-2), I64(math.MaxInt64), I64(-2)}, - {"Min(1, 2)", NewU128(1), NewU128(2), NewU128(1)}, - {"Min(1, MaxU128)", NewU128(1), MaxU128(), NewU128(1)}, - {"Min(MaxU128, MaxU128)", MaxU128(), MaxU128(), MaxU128()}, - {"Min(-2, -1)", NewI128(-2), NewI128(-1), NewI128(-2)}, - {"Min(-2, MaxI128)", NewI128(-2), MaxI128(), NewI128(-2)}, - {"Min(MinU128, MaxU128)", MinU128(), MaxU128(), MinU128()}, - {"Min(MinI128, MaxI128)", MinI128(), MaxI128(), MinI128()}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Min(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_TrailingZeros(t *testing.T) { - testExamples := []struct { - label string - a Numeric - expectation Numeric - }{ - {"TrailingZeros(1)", U8(1), U8(0)}, - {"TrailingZeros(2)", U8(2), U8(1)}, - {"TrailingZeros(3)", U8(3), U8(0)}, - {"TrailingZeros(1)", I8(1), I8(0)}, - {"TrailingZeros(2)", I8(2), I8(1)}, - {"TrailingZeros(3)", I8(3), I8(0)}, - {"TrailingZeros(1)", U16(1), U16(0)}, - {"TrailingZeros(2)", U16(2), U16(1)}, - {"TrailingZeros(3)", U16(3), U16(0)}, - {"TrailingZeros(1)", I16(1), I16(0)}, - {"TrailingZeros(2)", I16(2), I16(1)}, - {"TrailingZeros(3)", I16(3), I16(0)}, - {"TrailingZeros(1)", U32(1), U32(0)}, - {"TrailingZeros(2)", U32(2), U32(1)}, - {"TrailingZeros(3)", U32(3), U32(0)}, - {"TrailingZeros(1)", I32(1), I32(0)}, - {"TrailingZeros(2)", I32(2), I32(1)}, - {"TrailingZeros(3)", I32(3), I32(0)}, - {"TrailingZeros(1)", U64(1), U64(0)}, - {"TrailingZeros(2)", U64(2), U64(1)}, - {"TrailingZeros(3)", U64(3), U64(0)}, - {"TrailingZeros(1)", I64(1), I64(0)}, - {"TrailingZeros(2)", I64(2), I64(1)}, - {"TrailingZeros(3)", I64(3), I64(0)}, - {"TrailingZeros(1)", NewU128(1), NewU128(0)}, - {"TrailingZeros(2)", NewU128(2), NewU128(1)}, - {"TrailingZeros(3)", NewU128(3), NewU128(0)}, - {"TrailingZeros(1)", NewI128(1), NewI128(0)}, - {"TrailingZeros(2)", NewI128(2), NewI128(1)}, - {"TrailingZeros(3)", NewI128(3), NewI128(0)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.TrailingZeros() - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Clamp(t *testing.T) { - testExamples := []struct { - label string - a Numeric - minValue Numeric - maxValue Numeric - expectation Numeric - }{ - {"Clamp(5, 1, 10)", U8(5), U8(1), U8(10), U8(5)}, - {"Clamp(15, 1, 10)", U8(15), U8(1), U8(10), U8(10)}, - {"Clamp(0, 1, 10)", U8(0), U8(1), U8(10), U8(1)}, - {"Clamp(-3, -2, 2)", I8(-3), I8(-2), I8(2), I8(-2)}, - {"Clamp(3, -2, 2)", I8(3), I8(-2), I8(2), I8(2)}, - {"Clamp(1, -2, 2)", I8(1), I8(-2), I8(2), I8(1)}, - {"Clamp(5, 1, 10)", U16(5), U16(1), U16(10), U16(5)}, - {"Clamp(15, 1, 10)", U16(15), U16(1), U16(10), U16(10)}, - {"Clamp(0, 1, 10)", U16(0), U16(1), U16(10), U16(1)}, - {"Clamp(-3, -2, 2)", I16(-3), I16(-2), I16(2), I16(-2)}, - {"Clamp(3, -2, 2)", I16(3), I16(-2), I16(2), I16(2)}, - {"Clamp(1, -2, 2)", I16(1), I16(-2), I16(2), I16(1)}, - {"Clamp(5, 1, 10)", U32(5), U32(1), U32(10), U32(5)}, - {"Clamp(15, 1, 10)", U32(15), U32(1), U32(10), U32(10)}, - {"Clamp(0, 1, 10)", U32(0), U32(1), U32(10), U32(1)}, - {"Clamp(-3, -2, 2)", I32(-3), I32(-2), I32(2), I32(-2)}, - {"Clamp(3, -2, 2)", I32(3), I32(-2), I32(2), I32(2)}, - {"Clamp(1, -2, 2)", I32(1), I32(-2), I32(2), I32(1)}, - {"Clamp(5, 1, 10)", U64(5), U64(1), U64(10), U64(5)}, - {"Clamp(15, 1, 10)", U64(15), U64(1), U64(10), U64(10)}, - {"Clamp(0, 1, 10)", U64(0), U64(1), U64(10), U64(1)}, - {"Clamp(-3, -2, 2)", I64(-3), I64(-2), I64(2), I64(-2)}, - {"Clamp(3, -2, 2)", I64(3), I64(-2), I64(2), I64(2)}, - {"Clamp(1, -2, 2)", I64(1), I64(-2), I64(2), I64(1)}, - {"Clamp(5, 1, 10)", NewU128(5), NewU128(1), NewU128(10), NewU128(5)}, - {"Clamp(15, 1, 10)", NewU128(15), NewU128(1), NewU128(10), NewU128(10)}, - {"Clamp(0, 1, 10)", NewU128(0), NewU128(1), NewU128(10), NewU128(1)}, - {"Clamp(-3, -2, 2)", NewI128(-3), NewI128(-2), NewI128(2), NewI128(-2)}, - {"Clamp(3, -2, 2)", NewI128(3), NewI128(-2), NewI128(2), NewI128(2)}, - {"Clamp(1, -2, 2)", NewI128(1), NewI128(-2), NewI128(2), NewI128(1)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Clamp(testExample.minValue, testExample.maxValue) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_SaturatingAdd(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"MaxU8+1", U8(math.MaxUint8), U8(1), U8(math.MaxUint8)}, - {"MaxU8+MaxU8", U8(math.MaxUint8), U8(math.MaxUint8), U8(math.MaxUint8)}, - {"MaxI8+1", I8(math.MaxInt8), I8(1), I8(math.MaxInt8)}, - {"MaxI8+MaxI8", I8(math.MaxInt8), I8(math.MaxInt8), I8(math.MaxInt8)}, - {"MaxU16+1", U16(math.MaxUint16), U16(1), U16(math.MaxUint16)}, - {"MaxU16+MaxU16", U16(math.MaxUint16), U16(math.MaxUint16), U16(math.MaxUint16)}, - {"MaxI16+1", I16(math.MaxInt16), I16(1), I16(math.MaxInt16)}, - {"MaxI16+MaxI16", I16(math.MaxInt16), I16(math.MaxInt16), I16(math.MaxInt16)}, - {"MaxU32+1", U32(math.MaxUint32), U32(1), U32(math.MaxUint32)}, - {"MaxU32+MaxU32", U32(math.MaxUint32), U32(math.MaxUint32), U32(math.MaxUint32)}, - {"MaxI32+1", I32(math.MaxInt32), I32(1), I32(math.MaxInt32)}, - {"MaxI32+MaxI32", I32(math.MaxInt32), I32(math.MaxInt32), I32(math.MaxInt32)}, - {"MaxU64+1", U64(math.MaxUint64), U64(1), U64(math.MaxUint64)}, - {"MaxU64+MaxU64", U64(math.MaxUint64), U64(math.MaxUint64), U64(math.MaxUint64)}, - {"MaxI64+1", I64(math.MaxInt64), I64(1), I64(math.MaxInt64)}, - {"MaxI64+MaxI64", I64(math.MaxInt64), I64(math.MaxInt64), I64(math.MaxInt64)}, - {"MaxU128+1", MaxU128(), NewU128(1), MaxU128()}, - {"MaxU128+MaxU128", MaxU128(), MaxU128(), MaxU128()}, - {"MaxI128+1", MaxI128(), NewI128(1), MaxI128()}, - {"MaxI128+MaxI128", MaxI128(), MaxI128(), MaxI128()}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.SaturatingAdd(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_SaturatingSub(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"0-1", U8(0), U8(1), U8(0)}, - {"0-MaxU8", U8(0), U8(math.MaxUint8), U8(0)}, - {"0-1", I8(0), I8(1), I8(-1)}, - {"MinU8-1", I8(math.MinInt8), I8(1), I8(math.MinInt8)}, - {"0-1", U16(0), U16(1), U16(0)}, - {"0-MaxU16", U16(0), U16(math.MaxUint16), U16(0)}, - {"0-1", I16(0), I16(1), I16(-1)}, - {"MinU16-1", I16(math.MinInt16), I16(1), I16(math.MinInt16)}, - {"0-1", U32(0), U32(1), U32(0)}, - {"0-MaxU32", U32(0), U32(math.MaxUint32), U32(0)}, - {"0-1", I32(0), I32(1), I32(-1)}, - {"MinU32-1", I32(math.MinInt32), I32(1), I32(math.MinInt32)}, - {"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)}, - {"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)}, - {"MinU128-1", MinI128(), NewI128(1), MinI128()}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.SaturatingSub(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_SaturatingMul(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation Numeric - }{ - {"MaxU8*2", U8(math.MaxUint8), U8(2), U8(math.MaxUint8)}, - {"MaxU8*MaxU8", U8(math.MaxUint8), U8(math.MaxUint8), U8(math.MaxUint8)}, - {"MaxI8*2", I8(math.MaxInt8), I8(2), I8(math.MaxInt8)}, - {"MinI8*2", I8(math.MinInt8), I8(2), I8(math.MinInt8)}, - {"MaxU16*2", U16(math.MaxUint16), U16(2), U16(math.MaxUint16)}, - {"MaxU16*MaxU16", U16(math.MaxUint16), U16(math.MaxUint16), U16(math.MaxUint16)}, - {"MaxI16*2", I16(math.MaxInt16), I16(2), I16(math.MaxInt16)}, - {"MinI16*2", I16(math.MinInt16), I16(2), I16(math.MinInt16)}, - {"MaxU32*2", U32(math.MaxUint32), U32(2), U32(math.MaxUint32)}, - {"MaxU32*MaxU32", U32(math.MaxUint32), U32(math.MaxUint32), U32(math.MaxUint32)}, - {"MaxI32*2", I32(math.MaxInt32), I32(2), I32(math.MaxInt32)}, - {"MinI32*2", I32(math.MinInt32), I32(2), I32(math.MinInt32)}, - {"MaxU64*2", U64(math.MaxUint64), U64(2), U64(math.MaxUint64)}, - {"MaxU64*MaxU64", U64(math.MaxUint64), U64(math.MaxUint64), U64(math.MaxUint64)}, - {"MaxI64*2", I64(math.MaxInt64), I64(2), I64(math.MaxInt64)}, - {"MinI64*2", I64(math.MinInt64), I64(2), I64(math.MinInt64)}, - {"MaxU128*2", MaxU128(), NewU128(2), MaxU128()}, - {"MaxU128*MaxU128", MaxU128(), MaxU128(), MaxU128()}, - {"MaxI128*2", MaxI128(), NewI128(2), MaxI128()}, - {"MinI128*2", MinI128(), NewI128(2), MinI128()}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.SaturatingMul(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_CheckedAdd(t *testing.T) { - testExamples := []struct { - label string - a U64 - b U64 - expectation U64 - expectationErr bool - }{ - {"1+2", U64(1), U64(2), U64(3), false}, - {"MaxU64+1", U64(math.MaxUint64), U64(1), U64(0), true}, - {"MaxU64+MaxU64", U64(math.MaxUint64), U64(math.MaxUint64), U64(0), true}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result, err := testExample.a.CheckedAdd(testExample.b) - assert.Equal(t, testExample.expectation, result) - if testExample.expectationErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func Test_Eq(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation bool - }{ - {"1==1", U8(1), U8(1), true}, - {"1==2", U8(1), U8(2), false}, - - {"1==1", I8(1), I8(1), true}, - {"1==2", I8(1), I8(2), false}, - {"-1==1", I8(-1), I8(1), false}, - {"-1==-1", I8(-1), I8(-1), true}, - - {"1==1", U16(1), U16(1), true}, - {"1==2", U16(1), U16(2), false}, - - {"1==1", I16(1), I16(1), true}, - {"1==2", I16(1), I16(2), false}, - {"-1==1", I16(-1), I16(1), false}, - {"-1==-1", I16(-1), I16(-1), true}, - - {"1==1", U32(1), U32(1), true}, - {"1==2", U32(1), U32(2), false}, - - {"1==1", I32(1), I32(1), true}, - {"1==2", I32(1), I32(2), false}, - {"-1==1", I32(-1), I32(1), false}, - {"-1==-1", I32(-1), I32(-1), true}, - - {"1==1", U64(1), U64(1), true}, - {"1==2", U64(1), U64(2), false}, - - {"1==1", I64(1), I64(1), true}, - {"1==2", I64(1), I64(2), false}, - {"-1==1", I64(-1), I64(1), false}, - {"-1==-1", I64(-1), I64(-1), true}, - - {"1==1", NewU128(1), NewU128(1), true}, - {"1==2", NewU128(1), NewU128(2), false}, - - {"1==1", NewI128(1), NewI128(1), true}, - {"1==2", NewI128(1), NewI128(2), false}, - {"-1==1", NewI128(-1), NewI128(1), false}, - {"-1==-1", NewI128(-1), NewI128(-1), true}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Eq(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Lt(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation bool - }{ - {"1<1", U8(1), U8(1), false}, - {"1<1", U8(1), U8(1), false}, - {"1<2", U8(1), U8(2), true}, - - {"1<1", I8(1), I8(1), false}, - {"1<2", I8(1), I8(2), true}, - {"-1<1", I8(-1), I8(1), true}, - {"-1<-1", I8(-1), I8(-1), false}, - - {"1<1", U16(1), U16(1), false}, - {"1<2", U16(1), U16(2), true}, - - {"1<1", I16(1), I16(1), false}, - {"1<2", I16(1), I16(2), true}, - {"-1<1", I16(-1), I16(1), true}, - {"-1<-1", I16(-1), I16(-1), false}, - - {"1<1", U32(1), U32(1), false}, - {"1<2", U32(1), U32(2), true}, - - {"1<1", I32(1), I32(1), false}, - {"1<2", I32(1), I32(2), true}, - {"-1<1", I32(-1), I32(1), true}, - {"-1<-1", I32(-1), I32(-1), false}, - - {"1<1", U64(1), U64(1), false}, - {"1<2", U64(1), U64(2), true}, - - {"1<1", I64(1), I64(1), false}, - {"1<2", I64(1), I64(2), true}, - {"-1<1", I64(-1), I64(1), true}, - {"-1<-1", I64(-1), I64(-1), false}, - - {"1<1", NewU128(1), NewU128(1), false}, - {"1<2", NewU128(1), NewU128(2), true}, - - {"1<1", NewI128(1), NewI128(1), false}, - {"1<2", NewI128(1), NewI128(2), true}, - {"-1<1", NewI128(-1), NewI128(1), true}, - {"-1<-1", NewI128(-1), NewI128(-1), false}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Lt(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Lte(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation bool - }{ - {"1<=1", U8(1), U8(1), true}, - {"1<=2", U8(1), U8(2), true}, - {"1<=0", U8(1), U8(0), false}, - - {"1<=1", I8(1), I8(1), true}, - {"1<=2", I8(1), I8(2), true}, - {"-1<=1", I8(-1), I8(1), true}, - {"-1<=-1", I8(-1), I8(-1), true}, - {"-1<=-2", I8(-1), I8(-2), false}, - - {"1<=1", U16(1), U16(1), true}, - {"1<=2", U16(1), U16(2), true}, - {"1<=0", U16(1), U16(0), false}, - - {"1<=1", I16(1), I16(1), true}, - {"1<=2", I16(1), I16(2), true}, - {"-1<=1", I16(-1), I16(1), true}, - {"-1<=-1", I16(-1), I16(-1), true}, - {"-1<=-2", I16(-1), I16(-2), false}, - - {"1<=1", U32(1), U32(1), true}, - {"1<=2", U32(1), U32(2), true}, - {"1<=0", U32(1), U32(0), false}, - - {"1<=1", I32(1), I32(1), true}, - {"1<=2", I32(1), I32(2), true}, - {"-1<=1", I32(-1), I32(1), true}, - {"-1<=-1", I32(-1), I32(-1), true}, - {"-1<=-2", I32(-1), I32(-2), false}, - - {"1<=1", U64(1), U64(1), true}, - {"1<=2", U64(1), U64(2), true}, - {"1<=0", U64(1), U64(0), false}, - - {"1<=1", I64(1), I64(1), true}, - {"1<=2", I64(1), I64(2), true}, - {"-1<=1", I64(-1), I64(1), true}, - {"-1<=-1", I64(-1), I64(-1), true}, - {"-1<=-2", I64(-1), I64(-2), false}, - - {"1<=1", NewU128(1), NewU128(1), true}, - {"1<=2", NewU128(1), NewU128(2), true}, - {"1<=0", NewU128(1), NewU128(0), false}, - - {"1<=1", NewI128(1), NewI128(1), true}, - {"1<=2", NewI128(1), NewI128(2), true}, - {"-1<=1", NewI128(-1), NewI128(1), true}, - {"-1<=-1", NewI128(-1), NewI128(-1), true}, - {"-1<=-2", NewI128(-1), NewI128(-2), false}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Lte(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Gt(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation bool - }{ - {"1>1", U8(1), U8(1), false}, - {"1>2", U8(1), U8(2), false}, - {"2>1", U8(2), U8(1), true}, - - {"1>1", I8(1), I8(1), false}, - {"1>2", I8(1), I8(2), false}, - {"1>-1", I8(1), I8(-1), true}, - {"-1>-1", I8(-1), I8(-1), false}, - - {"1>1", U16(1), U16(1), false}, - {"1>2", U16(1), U16(2), false}, - {"2>1", U16(2), U16(1), true}, - - {"1>1", I16(1), I16(1), false}, - {"1>2", I16(1), I16(2), false}, - {"1>-1", I16(1), I16(-1), true}, - {"-1>-1", I16(-1), I16(-1), false}, - - {"1>1", U32(1), U32(1), false}, - {"1>2", U32(1), U32(2), false}, - {"2>1", U32(2), U32(1), true}, - - {"1>1", I32(1), I32(1), false}, - {"1>2", I32(1), I32(2), false}, - {"1>-1", I32(1), I32(-1), true}, - {"-1>-1", I32(-1), I32(-1), false}, - - {"1>1", U64(1), U64(1), false}, - {"1>2", U64(1), U64(2), false}, - {"2>1", U64(2), U64(1), true}, - - {"1>1", I64(1), I64(1), false}, - {"1>2", I64(1), I64(2), false}, - {"1>-1", I64(1), I64(-1), true}, - {"-1>-1", I64(-1), I64(-1), false}, - - {"1>1", NewU128(1), NewU128(1), false}, - {"1>2", NewU128(1), NewU128(2), false}, - {"2>1", NewU128(2), NewU128(1), true}, - - {"1>1", NewI128(1), NewI128(1), false}, - {"1>2", NewI128(1), NewI128(2), false}, - {"1>-1", NewI128(1), NewI128(-1), true}, - {"-1>-1", NewI128(-1), NewI128(-1), false}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Gt(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_Gte(t *testing.T) { - testExamples := []struct { - label string - a Numeric - b Numeric - expectation bool - }{ - {"1>=1", U8(1), U8(1), true}, - {"1>=2", U8(1), U8(2), false}, - {"2>=1", U8(2), U8(1), true}, - - {"1>=1", I8(1), I8(1), true}, - {"1>=2", I8(1), I8(2), false}, - {"1>=-1", I8(1), I8(-1), true}, - {"-1>=-1", I8(-1), I8(-1), true}, - - {"1>=1", U16(1), U16(1), true}, - {"1>=2", U16(1), U16(2), false}, - {"2>=1", U16(2), U16(1), true}, - - {"1>=1", I16(1), I16(1), true}, - {"1>=2", I16(1), I16(2), false}, - {"1>=-1", I16(1), I16(-1), true}, - {"-1>=-1", I16(-1), I16(-1), true}, - - {"1>=1", U32(1), U32(1), true}, - {"1>=2", U32(1), U32(2), false}, - {"2>=1", U32(2), U32(1), true}, - - {"1>=1", I32(1), I32(1), true}, - {"1>=2", I32(1), I32(2), false}, - {"1>=-1", I32(1), I32(-1), true}, - {"-1>=-1", I32(-1), I32(-1), true}, - - {"1>=1", U64(1), U64(1), true}, - {"1>=2", U64(1), U64(2), false}, - {"2>=1", U64(2), U64(1), true}, - - {"1>=1", I64(1), I64(1), true}, - {"1>=2", I64(1), I64(2), false}, - {"1>=-1", I64(1), I64(-1), true}, - {"-1>=-1", I64(-1), I64(-1), true}, - - {"1>=1", NewU128(1), NewU128(1), true}, - {"1>=2", NewU128(1), NewU128(2), false}, - {"2>=1", NewU128(2), NewU128(1), true}, - - {"1>=1", NewI128(1), NewI128(1), true}, - {"1>=2", NewI128(1), NewI128(2), false}, - {"1>=-1", NewI128(1), NewI128(-1), true}, - {"-1>=-1", NewI128(-1), NewI128(-1), true}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := testExample.a.Gte(testExample.b) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_ToU64(t *testing.T) { - testExamples := []struct { - label string - input Numeric - expectation U64 - }{ - {"U8(255))=>U64(255)", U8(math.MaxUint8), U64(math.MaxUint8)}, - {"I8(127)=>U64(127)", I8(math.MaxInt8), U64(math.MaxInt8)}, - {"U16(65535)=>U64(65535)", U16(math.MaxUint16), U64(math.MaxUint16)}, - {"I16(32767)=>U64(32767)", I16(math.MaxInt16), U64(math.MaxInt16)}, - {"U32(4294967295)=>U64(4294967295)", U32(math.MaxUint32), U64(math.MaxUint32)}, - {"I32(2147483647)=>U64(2147483647)", I32(math.MaxInt32), U64(math.MaxInt32)}, - {"U64(18446744073709551615)=>U64(18446744073709551615)", U64(math.MaxUint64), U64(math.MaxUint64)}, - {"I64(9223372036854775807)=>U64(9223372036854775807)", I64(math.MaxInt64), U64(math.MaxInt64)}, - {"U128(340282366920938463463374607431768211455)=>U64(18446744073709551615)", MaxU128(), U64(math.MaxUint64)}, - {"I128(170141183460469231731687303715884105727)=>U64(9223372036854775807)", MaxI128(), U64(math.MaxUint64)}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := To[U64](testExample.input) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_ToI64(t *testing.T) { - testExamples := []struct { - label string - input Numeric - expectation I64 - }{} - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := To[I64](testExample.input) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_ToU128(t *testing.T) { - u1, _ := NewU128FromString("170141183460469231731687303715884105727") - - testExamples := []struct { - label string - input Numeric - expectation U128 - }{ - {"U8(255))=>U128(255)", U8(math.MaxUint8), NewU128(math.MaxUint8)}, - {"I8(127)=>U128(127)", I8(math.MaxInt8), NewU128(math.MaxInt8)}, - {"U16(65535)=>U128(65535)", U16(math.MaxUint16), NewU128(math.MaxUint16)}, - {"I16(32767)=>U128(32767)", I16(math.MaxInt16), NewU128(math.MaxInt16)}, - {"U32(4294967295)=>U128(4294967295)", U32(math.MaxUint32), NewU128(math.MaxUint32)}, - {"I32(2147483647)=>U128(2147483647)", I32(math.MaxInt32), NewU128(math.MaxInt32)}, - {"U64(18446744073709551615)=>U128(18446744073709551615)", U64(math.MaxUint64), NewU128(uint64(math.MaxUint64))}, - {"I64(9223372036854775807)=>U128(9223372036854775807)", I64(math.MaxInt64), NewU128(math.MaxInt64)}, - {"U128(340282366920938463463374607431768211455)=>U128(340282366920938463463374607431768211455)", MaxU128(), MaxU128()}, - {"I128(170141183460469231731687303715884105727)=>U128(170141183460469231731687303715884105727)", MaxI128(), u1}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := To[U128](testExample.input) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_ToI128(t *testing.T) { - u1, _ := NewU128FromString("170141183460469231731687303715884105727") - i1, _ := NewI128FromString("170141183460469231731687303715884105727") - - testExamples := []struct { - label string - input Numeric - expectation I128 - }{ - {"U8(255))=>I128(255)", U8(math.MaxUint8), NewI128(math.MaxUint8)}, - {"I8(127)=>I128(127)", I8(math.MinInt8), NewI128(math.MinInt8)}, - {"U16(65535)=>I128(65535)", U16(math.MaxUint16), NewI128(math.MaxUint16)}, - {"I16(32767)=>I128(32767)", I16(math.MinInt16), NewI128(math.MinInt16)}, - {"U32(4294967295)=>I128(4294967295)", U32(math.MaxUint32), NewI128(math.MaxUint32)}, - {"I32(2147483647)=>I128(2147483647)", I32(math.MinInt32), NewI128(math.MinInt32)}, - {"U64(18446744073709551615)=>I128(18446744073709551615)", U64(math.MaxUint64), NewI128(uint64(math.MaxUint64))}, - {"I64(9223372036854775807)=>I128(9223372036854775807)", I64(math.MinInt64), NewI128(math.MinInt64)}, - {"U128(170141183460469231731687303715884105727)=>I128(170141183460469231731687303715884105727)", u1, i1}, - {"I128(170141183460469231731687303715884105727)=>I128(170141183460469231731687303715884105727)", i1, i1}, - } - - for _, testExample := range testExamples { - t.Run(testExample.label, func(t *testing.T) { - result := To[I128](testExample.input) - assert.Equal(t, testExample.expectation, result) - }) - } -} - -func Test_U128ToBigInt(t *testing.T) { - bnMaxU128Value, _ := new(big.Int).SetString("340282366920938463463374607431768211455", 10) - - testExamples := []struct { - label string - input U128 - expectation *big.Int - }{ - {"340282366920938463463374607431768211455", MaxU128(), bnMaxU128Value}, - } - - 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) - bnMinI128Value, _ := new(big.Int).SetString("-170141183460469231731687303715884105728", 10) - - 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) - }) - } -} - func Test_anyIntegerToU128(t *testing.T) { u1, _ := NewU128FromString("340282366920938463463374607431768211455") @@ -1050,3 +55,16 @@ func Test_anyIntegerToI128(t *testing.T) { }) } } + +func Test_anyIntegerToI128_Panics(t *testing.T) { + assert.PanicsWithValue(t, + "unknown type in anyIntegerTo128Bits", + func() { + anyIntegerTo128Bits[I128]("test") + }) +} + +func Test_stringTo128Bits_InvalidValue(t *testing.T) { + _, err := stringTo128Bits[I128]("test") + assert.Error(t, err, "can not convert string to big.Int") +} diff --git a/numeric_u128.go b/numeric_u128.go deleted file mode 100644 index 844afef..0000000 --- a/numeric_u128.go +++ /dev/null @@ -1,172 +0,0 @@ -package goscale - -import ( - "encoding/binary" - "math/big" - "math/bits" -) - -// little endian byte order -// [0] least significant bits -// [1] most significant bits -type U128 [2]U64 - -func NewU128[N Integer](n N) U128 { - return anyIntegerTo128Bits[U128](n) -} - -func NewU128FromString(n string) (U128, error) { - return stringTo128Bits[U128](n) -} - -func bigIntToU128(n *big.Int) U128 { - bytes := make([]byte, 16) - n.FillBytes(bytes) - reverseSlice(bytes) - - return U128{ - U64(binary.LittleEndian.Uint64(bytes[:8])), - U64(binary.LittleEndian.Uint64(bytes[8:])), - } -} - -func (n U128) ToBigInt() *big.Int { - bytes := make([]byte, 16) - binary.BigEndian.PutUint64(bytes[:8], uint64(n[1])) - binary.BigEndian.PutUint64(bytes[8:], uint64(n[0])) - return big.NewInt(0).SetBytes(bytes) -} - -// ff ff ff ff ff ff ff ff | ff ff ff ff ff ff ff ff -func MaxU128() U128 { - return U128{ - U64(^uint64(0)), - U64(^uint64(0)), - } -} - -// 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 -func MinU128() U128 { - return U128{ - U64(0), - U64(0), - } -} - -func (n U128) Interface() Numeric { - return n -} - -func (a U128) Add(b Numeric) Numeric { - sumLow, carry := bits.Add64(uint64(a[0]), uint64(b.(U128)[0]), 0) - sumHigh, _ := bits.Add64(uint64(a[1]), uint64(b.(U128)[1]), carry) - return U128{U64(sumLow), U64(sumHigh)} -} - -func (a U128) Sub(b Numeric) Numeric { - diffLow, borrow := bits.Sub64(uint64(a[0]), uint64(b.(U128)[0]), 0) - diffHigh, _ := bits.Sub64(uint64(a[1]), uint64(b.(U128)[1]), borrow) - return U128{U64(diffLow), U64(diffHigh)} -} - -func (a U128) Mul(b Numeric) Numeric { - high, low := bits.Mul64(uint64(a[0]), uint64(b.(U128)[0])) - high += uint64(a[1])*uint64(b.(U128)[0]) + uint64(a[0])*uint64(b.(U128)[1]) - return U128{U64(low), U64(high)} -} - -func (a U128) Div(b Numeric) Numeric { - return bigIntToU128( - new(big.Int).Div(a.ToBigInt(), b.(U128).ToBigInt()), - ) -} - -func (a U128) Mod(b Numeric) Numeric { - return bigIntToU128( - new(big.Int).Mod(a.ToBigInt(), b.(U128).ToBigInt()), - ) -} - -func (a U128) Eq(b Numeric) bool { - return a.ToBigInt().Cmp(b.(U128).ToBigInt()) == 0 -} - -func (a U128) Ne(b Numeric) bool { - return !a.Eq(b) -} - -func (a U128) Lt(b Numeric) bool { - return a.ToBigInt().Cmp(b.(U128).ToBigInt()) < 0 -} - -func (a U128) Lte(b Numeric) bool { - return a.ToBigInt().Cmp(b.(U128).ToBigInt()) <= 0 -} - -func (a U128) Gt(b Numeric) bool { - return a.ToBigInt().Cmp(b.(U128).ToBigInt()) > 0 -} - -func (a U128) Gte(b Numeric) bool { - return a.ToBigInt().Cmp(b.(U128).ToBigInt()) >= 0 -} - -func (a U128) Max(b Numeric) Numeric { - if a.Gt(b) { - return a - } - return b -} - -func (a U128) Min(b Numeric) Numeric { - if a.Lt(b) { - return a - } - return b -} - -func (a U128) Clamp(minValue, maxValue Numeric) Numeric { - if a.Lt(minValue) { - return minValue - } - if a.Gt(maxValue) { - return maxValue - } - return a -} - -func (a U128) TrailingZeros() Numeric { - return NewU128(a.ToBigInt().TrailingZeroBits()) -} - -func (a U128) SaturatingAdd(b Numeric) Numeric { - sumLow, carry := bits.Add64(uint64(a[0]), uint64(b.(U128)[0]), 0) - sumHigh, overflow := bits.Add64(uint64(a[1]), uint64(b.(U128)[1]), carry) - // check for overflow - if overflow == 1 || (carry == 1 && sumHigh <= uint64(a[1]) && sumHigh <= uint64(b.(U128)[1])) { - return MaxU128() - } - return U128{U64(sumLow), U64(sumHigh)} -} - -func (a U128) SaturatingSub(b Numeric) Numeric { - low, borrow := bits.Sub64(uint64(a[0]), uint64(b.(U128)[0]), 0) - high, _ := bits.Sub64(uint64(a[1]), uint64(b.(U128)[1]), borrow) - // check for underflow - if borrow == 1 || high > uint64(a[1]) { - return U128{0, 0} - } - return U128{U64(low), U64(high)} -} - -func (a U128) SaturatingMul(b Numeric) Numeric { - result := new(big.Int).Mul(a.ToBigInt(), b.(U128).ToBigInt()) - // check for overflow - maxValue := new(big.Int) - maxValue.Lsh(big.NewInt(1), 128) - maxValue.Sub(maxValue, big.NewInt(1)) - if result.Cmp(maxValue) > 0 { - result.Set(maxValue) - } - return bigIntToU128(result) -} diff --git a/numeric_u16.go b/numeric_u16.go deleted file mode 100644 index 84d517c..0000000 --- a/numeric_u16.go +++ /dev/null @@ -1,113 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type U16 uint16 - -func (n U16) Interface() Numeric { - return n -} - -func (a U16) Add(b Numeric) Numeric { - return a + b.(U16) -} - -func (a U16) Sub(b Numeric) Numeric { - return a - b.(U16) -} - -func (a U16) Mul(b Numeric) Numeric { - return a * b.(U16) -} - -func (a U16) Div(b Numeric) Numeric { - return a / b.(U16) -} - -func (a U16) Mod(b Numeric) Numeric { - return a % b.(U16) -} - -func (a U16) Eq(b Numeric) bool { - return a == b.(U16) -} - -func (a U16) Ne(b Numeric) bool { - return a != b.(U16) -} - -func (a U16) Lt(b Numeric) bool { - return a < b.(U16) -} - -func (a U16) Lte(b Numeric) bool { - return a <= b.(U16) -} - -func (a U16) Gt(b Numeric) bool { - return a > b.(U16) -} - -func (a U16) Gte(b Numeric) bool { - return a >= b.(U16) -} - -func (a U16) Max(b Numeric) Numeric { - if a > b.(U16) { - return a - } - return b -} - -func (a U16) Min(b Numeric) Numeric { - if a < b.(U16) { - return a - } - return b -} - -func (a U16) Clamp(min, max Numeric) Numeric { - if a < min.(U16) { - return min - } else if a > max.(U16) { - return max - } else { - return a - } -} - -func (a U16) TrailingZeros() Numeric { - return U16(bits.TrailingZeros(uint(a))) -} - -func (a U16) SaturatingAdd(b Numeric) Numeric { - sum := uint32(a) + uint32(b.(U16)) - // check for overflow - if sum > math.MaxUint16 { - return U16(math.MaxUint16) - } - return U16(sum) -} - -func (a U16) SaturatingSub(b Numeric) Numeric { - // check for underflow - if a < b.(U16) { - return U16(0) - } - return a.Sub(b) -} - -func (a U16) SaturatingMul(b Numeric) Numeric { - if a == 0 || b.(U16) == 0 { - return U16(0) - } - // check for overflow - product := uint32(a) * uint32(b.(U16)) - if product > math.MaxUint16 { - return U16(math.MaxUint16) - } - return U16(product) -} diff --git a/numeric_u32.go b/numeric_u32.go deleted file mode 100644 index 1c84b0d..0000000 --- a/numeric_u32.go +++ /dev/null @@ -1,112 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type U32 uint32 - -func (n U32) Interface() Numeric { - return n -} - -func (a U32) Add(b Numeric) Numeric { - return a + b.(U32) -} - -func (a U32) Sub(b Numeric) Numeric { - return a - b.(U32) -} - -func (a U32) Mul(b Numeric) Numeric { - return a * b.(U32) -} - -func (a U32) Div(b Numeric) Numeric { - return a / b.(U32) -} - -func (a U32) Mod(b Numeric) Numeric { - return a % b.(U32) -} - -func (a U32) Eq(b Numeric) bool { - return a == b.(U32) -} - -func (a U32) Ne(b Numeric) bool { - return a != b.(U32) -} - -func (a U32) Lt(b Numeric) bool { - return a < b.(U32) -} - -func (a U32) Lte(b Numeric) bool { - return a <= b.(U32) -} - -func (a U32) Gt(b Numeric) bool { - return a > b.(U32) -} - -func (a U32) Gte(b Numeric) bool { - return a >= b.(U32) -} - -func (a U32) Max(b Numeric) Numeric { - if a > b.(U32) { - return a - } - return b -} - -func (a U32) Min(b Numeric) Numeric { - if a < b.(U32) { - return a - } - return b -} - -func (a U32) Clamp(min, max Numeric) Numeric { - if a < min.(U32) { - return min - } else if a > max.(U32) { - return max - } else { - return a - } -} - -func (a U32) TrailingZeros() Numeric { - return U32(bits.TrailingZeros(uint(a))) -} - -func (a U32) SaturatingAdd(b Numeric) Numeric { - sum := uint64(a) + uint64(b.(U32)) - if sum > math.MaxUint32 { - return U32(math.MaxUint32) - } - return U32(sum) -} - -func (a U32) SaturatingSub(b Numeric) Numeric { - if a < b.(U32) { - return U32(0) - } - return a.Sub(b) -} - -func (a U32) SaturatingMul(b Numeric) Numeric { - if a == 0 || b.(U32) == 0 { - return U32(0) - } - - product := uint64(a) * uint64(b.(U32)) - if product > math.MaxUint32 { - return U32(math.MaxUint32) - } - - return U32(product) -} diff --git a/numeric_u64.go b/numeric_u64.go deleted file mode 100644 index 5008e92..0000000 --- a/numeric_u64.go +++ /dev/null @@ -1,120 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type U64 uint64 - -func (n U64) Interface() Numeric { - return n -} - -func (a U64) Add(b Numeric) Numeric { - return a + b.(U64) -} - -func (a U64) Sub(b Numeric) Numeric { - return a - b.(U64) -} - -func (a U64) Mul(b Numeric) Numeric { - return a * b.(U64) -} - -func (a U64) Div(b Numeric) Numeric { - return a / b.(U64) -} - -func (a U64) Mod(b Numeric) Numeric { - return a % b.(U64) -} - -func (a U64) Eq(b Numeric) bool { - return a == b.(U64) -} - -func (a U64) Ne(b Numeric) bool { - return a != b.(U64) -} - -func (a U64) Lt(b Numeric) bool { - return a < b.(U64) -} - -func (a U64) Lte(b Numeric) bool { - return a <= b.(U64) -} - -func (a U64) Gt(b Numeric) bool { - return a > b.(U64) -} - -func (a U64) Gte(b Numeric) bool { - return a >= b.(U64) -} - -func (a U64) Max(b Numeric) Numeric { - if a > b.(U64) { - return a - } - return b -} - -func (a U64) Min(b Numeric) Numeric { - if a < b.(U64) { - return a - } - return b -} - -func (a U64) Clamp(min, max Numeric) Numeric { - if a < min.(U64) { - return min - } else if a > max.(U64) { - return max - } else { - return a - } -} - -func (a U64) TrailingZeros() Numeric { - return U64(bits.TrailingZeros64(uint64(a))) -} - -func (a U64) SaturatingAdd(b Numeric) Numeric { - sum, carry := bits.Add64(uint64(a), uint64(b.(U64)), 0) - if carry != 0 { - return U64(math.MaxUint64) - } - return U64(sum) -} - -func (a U64) SaturatingSub(b Numeric) Numeric { - diff, borrow := bits.Sub64(uint64(a), uint64(b.(U64)), 0) - if borrow != 0 { - return U64(0) - } - return U64(diff) -} - -func (a U64) SaturatingMul(b Numeric) Numeric { - if a == 0 || b.(U64) == 0 { - return U64(0) - } - - hi, lo := bits.Mul64(uint64(a), uint64(b.(U64))) - if hi != 0 { - return U64(math.MaxUint64) - } - return U64(lo) -} - -func (a U64) CheckedAdd(b Numeric) (Numeric, error) { - sum, carry := bits.Add64(uint64(a), uint64(b.(U64)), 0) - if carry != 0 { - return U64(0), ErrOverflow - } - return U64(sum), nil -} diff --git a/numeric_u8.go b/numeric_u8.go deleted file mode 100644 index 481de3b..0000000 --- a/numeric_u8.go +++ /dev/null @@ -1,115 +0,0 @@ -package goscale - -import ( - "math" - "math/bits" -) - -type U8 uint8 - -func (n U8) Interface() Numeric { - return n -} - -func (a U8) Add(b Numeric) Numeric { - return a + b.(U8) -} - -func (a U8) Sub(b Numeric) Numeric { - return a - b.(U8) -} - -func (a U8) Mul(b Numeric) Numeric { - return a * b.(U8) -} - -func (a U8) Div(b Numeric) Numeric { - return a / b.(U8) -} - -func (a U8) Mod(b Numeric) Numeric { - return a % b.(U8) -} - -func (a U8) Eq(b Numeric) bool { - return a == b.(U8) -} - -func (a U8) Ne(b Numeric) bool { - return a != b.(U8) -} - -func (a U8) Lt(b Numeric) bool { - return a < b.(U8) -} - -func (a U8) Lte(b Numeric) bool { - return a <= b.(U8) -} - -func (a U8) Gt(b Numeric) bool { - return a > b.(U8) -} - -func (a U8) Gte(b Numeric) bool { - return a >= b.(U8) -} - -func (a U8) Max(b Numeric) Numeric { - if a > b.(U8) { - return a - } - return b -} - -func (a U8) Min(b Numeric) Numeric { - if a < b.(U8) { - return a - } - return b -} - -func (a U8) Clamp(min, max Numeric) Numeric { - if a < min.(U8) { - return min - } else if a > max.(U8) { - return max - } else { - return a - } -} - -func (a U8) TrailingZeros() Numeric { - return U8(bits.TrailingZeros(uint(a))) -} - -func (a U8) SaturatingAdd(b Numeric) Numeric { - sum := uint16(a) + uint16(b.(U8)) - // check for overflow - if sum > uint16(math.MaxUint8) { - return U8(math.MaxUint8) - } - return U8(sum) -} - -func (a U8) SaturatingSub(b Numeric) Numeric { - diff := uint16(a) - uint16(b.(U8)) - // check for underflow - if diff > uint16(math.MaxUint8) { - return U8(0) - } - return U8(diff) -} - -func (a U8) SaturatingMul(b Numeric) Numeric { - if a == 0 || b.(U8) == 0 { - return U8(0) - } - - product := uint16(a) * uint16(b.(U8)) - // check for overflow - if product > uint16(math.MaxUint8) { - return U8(math.MaxUint8) - } - return U8(product) -} diff --git a/option_test.go b/option_test.go index 96b7a53..68fa8c0 100644 --- a/option_test.go +++ b/option_test.go @@ -38,6 +38,14 @@ func Test_NewOption(t *testing.T) { } } +func Test_NewOption_Panics(t *testing.T) { + assert.PanicsWithValue(t, + "invalid value type for Option[T]", + func() { + NewOption[U32](U64(5)) + }) +} + type testEncodable struct { } @@ -48,10 +56,6 @@ func (testEncodable) Bytes() []byte { return []byte{} } -func (testEncodable) String() string { - return "" -} - func Test_EncodeOptionBoolGeneric(t *testing.T) { var examples = []struct { label string diff --git a/u128.go b/u128.go new file mode 100644 index 0000000..286b0b3 --- /dev/null +++ b/u128.go @@ -0,0 +1,145 @@ +package goscale + +import ( + "bytes" + "encoding/binary" + "math/big" + "math/bits" +) + +// little endian byte order +// [0] least significant bits +// [1] most significant bits +type U128 [2]U64 + +func NewU128[N Integer](n N) U128 { + return anyIntegerTo128Bits[U128](n) +} + +func NewU128FromString(n string) (U128, error) { + return stringTo128Bits[U128](n) +} + +func (n U128) Encode(buffer *bytes.Buffer) { + n[0].Encode(buffer) + n[1].Encode(buffer) +} + +func (n U128) Bytes() []byte { + return append(n[0].Bytes(), n[1].Bytes()...) +} + +func DecodeU128(buffer *bytes.Buffer) U128 { + decoder := Decoder{Reader: buffer} + buf := make([]byte, 16) + decoder.Read(buf) + + return U128{ + U64(binary.LittleEndian.Uint64(buf[:8])), + U64(binary.LittleEndian.Uint64(buf[8:])), + } +} + +func (n U128) ToBigInt() *big.Int { + bytes := make([]byte, 16) + binary.BigEndian.PutUint64(bytes[:8], uint64(n[1])) + binary.BigEndian.PutUint64(bytes[8:], uint64(n[0])) + return big.NewInt(0).SetBytes(bytes) +} + +func (n U128) Add(other U128) U128 { + sumLow, carry := bits.Add64(uint64(n[0]), uint64(other[0]), 0) + sumHigh, _ := bits.Add64(uint64(n[1]), uint64(other[1]), carry) + return U128{U64(sumLow), U64(sumHigh)} +} + +func (n U128) Sub(other U128) U128 { + diffLow, borrow := bits.Sub64(uint64(n[0]), uint64(other[0]), 0) + diffHigh, _ := bits.Sub64(uint64(n[1]), uint64(other[1]), borrow) + return U128{U64(diffLow), U64(diffHigh)} +} + +func (n U128) Mul(other U128) U128 { + high, low := bits.Mul64(uint64(n[0]), uint64(other[0])) + high += uint64(n[1])*uint64(other[0]) + uint64(n[0])*uint64(other[1]) + return U128{U64(low), U64(high)} +} + +func (n U128) Div(other U128) U128 { + return bigIntToU128( + new(big.Int).Div(n.ToBigInt(), other.ToBigInt()), + ) +} + +func (n U128) Mod(other U128) U128 { + return bigIntToU128( + new(big.Int).Mod(n.ToBigInt(), other.ToBigInt()), + ) +} + +func (n U128) Eq(other U128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) == 0 +} + +func (n U128) Ne(other U128) bool { + return !n.Eq(other) +} + +func (n U128) Lt(other U128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) < 0 +} + +func (n U128) Lte(other U128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) <= 0 +} + +func (n U128) Gt(other U128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) > 0 +} + +func (n U128) Gte(other U128) bool { + return n.ToBigInt().Cmp(other.ToBigInt()) >= 0 +} + +//func (n U128) SaturatingAdd(other U128) U128 { +// sumLow, carry := bits.Add64(uint64(n[0]), uint64(other[0]), 0) +// sumHigh, overflow := bits.Add64(uint64(n[1]), uint64(other[1]), carry) +// // check for overflow +// if overflow == 1 || (carry == 1 && sumHigh <= uint64(n[1]) && sumHigh <= uint64(other[1])) { +// return MaxU128() +// } +// return U128{U64(sumLow), U64(sumHigh)} +//} +// +//func (n U128) SaturatingSub(other U128) U128 { +// low, borrow := bits.Sub64(uint64(n[0]), uint64(other[0]), 0) +// high, _ := bits.Sub64(uint64(n[1]), uint64(other[1]), borrow) +// // check for underflow +// if borrow == 1 || high > uint64(n[1]) { +// return U128{0, 0} +// } +// return U128{U64(low), U64(high)} +//} +// +//func (n U128) SaturatingMul(other U128) U128 { +// result := new(big.Int).Mul(n.ToBigInt(), other.ToBigInt()) +// // check for overflow +// maxValue := new(big.Int) +// maxValue.Lsh(big.NewInt(1), 128) +// maxValue.Sub(maxValue, big.NewInt(1)) +// if result.Cmp(maxValue) > 0 { +// result.Set(maxValue) +// } +// return bigIntToU128(result) +//} + +func bigIntToU128(n *big.Int) U128 { + bytes := make([]byte, 16) + n.FillBytes(bytes) + reverseSlice(bytes) + + return U128{ + U64(binary.LittleEndian.Uint64(bytes[:8])), + U64(binary.LittleEndian.Uint64(bytes[8:])), + } +} diff --git a/u128_test.go b/u128_test.go new file mode 100644 index 0000000..90b5f06 --- /dev/null +++ b/u128_test.go @@ -0,0 +1,303 @@ +package goscale + +import ( + "bytes" + "math" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_U128_Encode(t *testing.T) { + var examples = []struct { + label string + input string + expect []byte + }{ + {label: "Encode U128 - (0)", input: "0", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {label: "Encode U128 - (42)", input: "42", expect: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {label: "Encode U128 - (const.MaxInt64 + 1)", input: "9223372036854775808", expect: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {label: "Encode U128 - (0x9cFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)", input: "340282366920938463463374607431768211356", expect: []byte{0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {label: "Encode U128 - (MaxInt128)", input: "340282366920938463463374607431768211455", expect: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + } + + for _, e := range examples { + t.Run(e.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + value, ok := new(big.Int).SetString(e.input, 10) + if !ok { + panic("not ok") + } + input := NewU128(value) + + input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), e.expect) + assert.Equal(t, input.Bytes(), e.expect) + }) + } +} + +func Test_U128_Decode(t *testing.T) { + var examples = []struct { + label string + input []byte + expect U128 + stringValue string + }{ + {label: "Decode U128 - (0)", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: U128{U64(0), U64(0)}, stringValue: "0"}, + {label: "Decode U128 - (42)", input: []byte{0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: U128{42, 0}, stringValue: "42"}, + {label: "Decode U128 - (math.MaxInt64 + 1)", input: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, expect: U128{U64(math.MaxInt64 + 1), 0}, stringValue: "9223372036854775808"}, + {label: "Decode U128 - (0x9cFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)", input: []byte{0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: U128{18446744073709551516, 18446744073709551615}, stringValue: "340282366920938463463374607431768211356"}, + {label: "Decode U128 - (MaxInt128)", input: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, expect: U128{math.MaxUint64, math.MaxUint64}, stringValue: "340282366920938463463374607431768211455"}, + } + + for _, e := range examples { + t.Run(e.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(e.input) + + result := DecodeU128(buffer) + bigInt := result.ToBigInt() + + assert.Equal(t, result, e.expect) + assert.Equal(t, bigInt.String(), e.stringValue) + }) + } +} + +func Test_NewU128FromBigIntPanic(t *testing.T) { + t.Run("Exceeds U128", func(t *testing.T) { + value, ok := new(big.Int).SetString("340282366920938463463374607431768211456", 10) // MaxU128 + 1 + if !ok { + panic("not ok") + } + + assert.Panics(t, func() { NewU128(value) }) + }) +} + +func Test_U128_Add(t *testing.T) { + bn1, _ := NewU128FromString("340282366920938463463374607431768211454") + + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"340282366920938463463374607431768211454+1", bn1, NewU128(1), MaxU128()}, + {"340282366920938463463374607431768211455+1", MaxU128(), NewU128(1), NewU128(0)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Add(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Sub(t *testing.T) { + u1, _ := NewU128FromString("340282366920938463463374607431657675311") + u2, _ := NewU128FromString("340282366920938463463374607421657675311") + + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"2-1", NewU128(2), NewU128(1), NewU128(1)}, + {"0-1", NewU128(0), NewU128(1), MaxU128()}, + {"499999889463855-10000000000", NewU128(499999889463855), NewU128(10000000000), NewU128(499989889463855)}, + {"340282366920938463463374607431657675311-10000000000", u1, NewU128(10000000000), u2}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Sub(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Mul(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"2*3", NewU128(2), NewU128(3), NewU128(6)}, + {"MaxU128*0", MaxU128(), NewU128(0), NewU128(0)}, + {"MaxU128*1", MaxU128(), NewU128(1), MaxU128()}, + {"MaxU128*2", MaxU128(), NewU128(2), MaxU128().Sub(NewU128(1))}, + {"MaxU128*MaxU128", MaxU128(), MaxU128(), NewU128(1)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Mul(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Div(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect U128 + }{ + {"0/1", NewU128(0), NewU128(1), NewU128(0)}, + {"1/1", NewU128(1), NewU128(1), NewU128(1)}, + {"6/3", NewU128(6), NewU128(3), NewU128(2)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Div(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Eq(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect bool + }{ + {"1==1", NewU128(1), NewU128(1), true}, + {"1==2", NewU128(1), NewU128(2), false}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Eq(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Ne(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect bool + }{ + {"1!=1", NewU128(1), NewU128(1), false}, + {"1!=2", NewU128(1), NewU128(2), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Ne(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Lt(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect bool + }{ + {"1<1", NewU128(1), NewU128(1), false}, + {"1<2", NewU128(1), NewU128(2), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Lt(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Lte(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect bool + }{ + {"1<=1", NewU128(1), NewU128(1), true}, + {"1<=2", NewU128(1), NewU128(2), true}, + {"1<=0", NewU128(1), NewU128(0), false}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Lte(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Gt(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect bool + }{ + {"1>1", NewU128(1), NewU128(1), false}, + {"1>2", NewU128(1), NewU128(2), false}, + {"2>1", NewU128(2), NewU128(1), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Gt(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_Gte(t *testing.T) { + testExamples := []struct { + label string + a U128 + b U128 + expect bool + }{ + {"1>=1", NewU128(1), NewU128(1), true}, + {"1>=2", NewU128(1), NewU128(2), false}, + {"2>=1", NewU128(2), NewU128(1), true}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.a.Gte(testExample.b) + assert.Equal(t, testExample.expect, result) + }) + } +} + +func Test_U128_ToBigInt(t *testing.T) { + bnMaxU128Value, _ := new(big.Int).SetString("340282366920938463463374607431768211455", 10) + + testExamples := []struct { + label string + input U128 + expect *big.Int + }{ + {"340282366920938463463374607431768211455", MaxU128(), bnMaxU128Value}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + result := testExample.input.ToBigInt() + assert.Equal(t, testExample.expect, result) + }) + } +} diff --git a/u16.go b/u16.go new file mode 100644 index 0000000..e33b93b --- /dev/null +++ b/u16.go @@ -0,0 +1,27 @@ +package goscale + +import ( + "bytes" + "encoding/binary" +) + +type U16 uint16 + +func (value U16) Encode(buffer *bytes.Buffer) { + encoder := Encoder{Writer: buffer} + encoder.Write(value.Bytes()) +} + +func (value U16) Bytes() []byte { + result := make([]byte, 2) + binary.LittleEndian.PutUint16(result, uint16(value)) + + return result +} + +func DecodeU16(buffer *bytes.Buffer) U16 { + decoder := Decoder{Reader: buffer} + result := make([]byte, 2) + decoder.Read(result) + return U16(binary.LittleEndian.Uint16(result)) +} diff --git a/u16_test.go b/u16_test.go new file mode 100644 index 0000000..958b4b3 --- /dev/null +++ b/u16_test.go @@ -0,0 +1,51 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeU16(t *testing.T) { + var testExamples = []struct { + label string + input U16 + expectation []byte + }{ + {label: "uint16(127)", input: U16(127), expectation: []byte{0x7f, 0x00}}, + {label: "uint16(42)", input: U16(42), expectation: []byte{0x2a, 0x00}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeU16(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation U16 + }{ + {label: "(0x2a00)", input: []byte{0x2a, 0x00}, expectation: U16(42)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeU16(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +} diff --git a/u32.go b/u32.go new file mode 100644 index 0000000..b861950 --- /dev/null +++ b/u32.go @@ -0,0 +1,27 @@ +package goscale + +import ( + "bytes" + "encoding/binary" +) + +type U32 uint32 + +func (value U32) Encode(buffer *bytes.Buffer) { + encoder := Encoder{Writer: buffer} + encoder.Write(value.Bytes()) +} + +func (value U32) Bytes() []byte { + result := make([]byte, 4) + binary.LittleEndian.PutUint32(result, uint32(value)) + + return result +} + +func DecodeU32(buffer *bytes.Buffer) U32 { + decoder := Decoder{Reader: buffer} + result := make([]byte, 4) + decoder.Read(result) + return U32(binary.LittleEndian.Uint32(result)) +} diff --git a/u32_test.go b/u32_test.go new file mode 100644 index 0000000..208f3e5 --- /dev/null +++ b/u32_test.go @@ -0,0 +1,50 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeU32(t *testing.T) { + var testExamples = []struct { + label string + input U32 + expectation []byte + }{ + {label: "uint32(127)", input: U32(127), expectation: []byte{0x7f, 0x00, 0x00, 0x00}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeU32(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation U32 + }{ + {label: "(0x7f000000)", input: []byte{0x7f, 0x00, 0x00, 0x00}, expectation: U32(127)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeU32(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +} diff --git a/u64.go b/u64.go new file mode 100644 index 0000000..d083ae1 --- /dev/null +++ b/u64.go @@ -0,0 +1,27 @@ +package goscale + +import ( + "bytes" + "encoding/binary" +) + +type U64 uint64 + +func (value U64) Encode(buffer *bytes.Buffer) { + encoder := Encoder{Writer: buffer} + encoder.Write(value.Bytes()) +} + +func (value U64) Bytes() []byte { + result := make([]byte, 8) + binary.LittleEndian.PutUint64(result, uint64(value)) + + return result +} + +func DecodeU64(buffer *bytes.Buffer) U64 { + decoder := Decoder{Reader: buffer} + result := make([]byte, 8) + decoder.Read(result) + return U64(binary.LittleEndian.Uint64(result)) +} diff --git a/u64_test.go b/u64_test.go new file mode 100644 index 0000000..93b7a8f --- /dev/null +++ b/u64_test.go @@ -0,0 +1,50 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeU64(t *testing.T) { + var testExamples = []struct { + label string + input U64 + expectation []byte + }{ + {label: "uint64(127)", input: U64(127), expectation: []byte{0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeU64(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation U64 + }{ + {label: "(0x7f00000000000000)", input: []byte{0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, expectation: U64(127)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeU64(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +} diff --git a/u8.go b/u8.go new file mode 100644 index 0000000..0ba4662 --- /dev/null +++ b/u8.go @@ -0,0 +1,22 @@ +package goscale + +import "bytes" + +type U8 uint8 + +func (value U8) Encode(buffer *bytes.Buffer) { + // do not use value.Bytes() here: https://github.com/LimeChain/goscale/issues/77 + encoder := Encoder{Writer: buffer} + encoder.EncodeByte(byte(value)) +} + +func (value U8) Bytes() []byte { + return []byte{byte(value)} +} + +func DecodeU8(buffer *bytes.Buffer) U8 { + decoder := Decoder{Reader: buffer} + result := make([]byte, 1) + decoder.Read(result) + return U8(result[0]) +} diff --git a/u8_test.go b/u8_test.go new file mode 100644 index 0000000..d20c9a8 --- /dev/null +++ b/u8_test.go @@ -0,0 +1,51 @@ +package goscale + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_EncodeU8(t *testing.T) { + var testExamples = []struct { + label string + input U8 + expectation []byte + }{ + {label: "uint8(255)", input: U8(255), expectation: []byte{0xff}}, + {label: "uint8(0)", input: U8(0), expectation: []byte{0x00}}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + + testExample.input.Encode(buffer) + + assert.Equal(t, buffer.Bytes(), testExample.expectation) + assert.Equal(t, testExample.input.Bytes(), testExample.expectation) + }) + } +} + +func Test_DecodeU8(t *testing.T) { + var testExamples = []struct { + label string + input []byte + expectation U8 + }{ + {label: "(0xff)", input: []byte{0xff}, expectation: U8(255)}, + } + + for _, testExample := range testExamples { + t.Run(testExample.label, func(t *testing.T) { + buffer := &bytes.Buffer{} + buffer.Write(testExample.input) + + result := DecodeU8(buffer) + + assert.Equal(t, result, testExample.expectation) + }) + } +}