From 2d86902ba796b331aa99ead459085158d1cc7235 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 6 Nov 2021 16:07:04 -0500 Subject: [PATCH] uint256: Add general comparison support. This adds the ability to compare a uint256 to another uint256 or a uint64 along with associated tests to ensure proper functionality. The comparison logic is the usual logic where -1 is returned when the uint256 is less than the value being compared to, 0 when it is equal, and 1 when it is greater. This is part of a series of commits to fully implement the uint256 package. --- .../staging/primitives/uint256/uint256.go | 55 ++++++++++++++++--- .../primitives/uint256/uint256_test.go | 38 +++++++++++-- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/internal/staging/primitives/uint256/uint256.go b/internal/staging/primitives/uint256/uint256.go index 2de09e0c..7ac2a6d4 100644 --- a/internal/staging/primitives/uint256/uint256.go +++ b/internal/staging/primitives/uint256/uint256.go @@ -19,16 +19,15 @@ var ( // callers may rely on "wrap around" semantics. // // It currently implements support for comparison operations (equals, less, -// greater), interpreting and producing big and little endian bytes, and other -// convenience methods such as whether or not the value can be represented as a -// uint64 without loss of precision. +// greater, cmp), interpreting and producing big and little endian bytes, and +// other convenience methods such as whether or not the value can be represented +// as a uint64 without loss of precision. // // Future commits will implement the primary arithmetic operations (addition, // subtraction, multiplication, squaring, division, negation), bitwise -// operations (lsh, rsh, not, or, and, xor), comparison operations (cmp), and -// other convenience methods such as determining the minimum number of bits -// required to represent the current value and text formatting with base -// conversion. +// operations (lsh, rsh, not, or, and, xor), and other convenience methods such +// as determining the minimum number of bits required to represent the current +// value and text formatting with base conversion. type Uint256 struct { // The uint256 is represented as 4 unsigned 64-bit integers in base 2^64. // @@ -422,3 +421,45 @@ func (n *Uint256) GtEq(n2 *Uint256) bool { func (n *Uint256) GtEqUint64(n2 uint64) bool { return !n.LtUint64(n2) } + +// Cmp compares the two uint256s and returns -1, 0, or 1 depending on whether +// the uint256 is less than, equal to, or greater than the given one. +// +// That is, it returns: +// +// -1 when n < n2 +// 0 when n == n2 +// +1 when n > n2 +func (n *Uint256) Cmp(n2 *Uint256) int { + var borrow uint64 + r0, borrow := bits.Sub64(n.n[0], n2.n[0], borrow) + r1, borrow := bits.Sub64(n.n[1], n2.n[1], borrow) + r2, borrow := bits.Sub64(n.n[2], n2.n[2], borrow) + r3, borrow := bits.Sub64(n.n[3], n2.n[3], borrow) + if borrow != 0 { + return -1 + } + if r0|r1|r2|r3 == 0 { + return 0 + } + return 1 +} + +// CmpUint64 compares the uint256 with the given uint64 and returns -1, 0, or 1 +// depending on whether the uint256 is less than, equal to, or greater than the +// uint64. +// +// That is, it returns: +// +// -1 when n < n2 +// 0 when n == n2 +// +1 when n > n2 +func (n *Uint256) CmpUint64(n2 uint64) int { + if n.LtUint64(n2) { + return -1 + } + if n.GtUint64(n2) { + return 1 + } + return 0 +} diff --git a/internal/staging/primitives/uint256/uint256_test.go b/internal/staging/primitives/uint256/uint256_test.go index 403fea57..816f16b5 100644 --- a/internal/staging/primitives/uint256/uint256_test.go +++ b/internal/staging/primitives/uint256/uint256_test.go @@ -848,9 +848,17 @@ func TestUint256Comparison(t *testing.T) { }} for _, test := range tests { - // Ensure comparing the numbers produces the expected == result. + // Ensure comparing the numbers produces the expected cmp result. n1 := hexToUint256(test.n1) n2 := hexToUint256(test.n2) + gotCmp := n1.Cmp(n2) + if gotCmp != test.wantCmp { + t.Errorf("%q: incorrect cmp result -- got: %v, want: %v", test.name, + gotCmp, test.wantCmp) + continue + } + + // Ensure comparing the numbers produces the expected == result. isEq := n1.Eq(n2) wantEq := test.wantCmp == 0 if isEq != wantEq { @@ -924,8 +932,15 @@ func TestUint256ComparisonRandom(t *testing.T) { t.Fatalf("failed equality check -- n2: %x", n2) } - // Ensure comparing the numbers produces the expected == result. + // Ensure the uint256 comparison result matches the one using big ints. bigCmpResult := bigN1.Cmp(bigN2) + cmpResult := n1.Cmp(n2) + if cmpResult != bigCmpResult { + t.Errorf("incorrect cmp result n1: %x, n2: %x -- got %v, want %v", + n1, n2, cmpResult, bigCmpResult) + } + + // Ensure comparing the numbers produces the expected == result. isEq := n1.Eq(n2) wantEq := bigCmpResult == 0 if isEq != wantEq { @@ -1060,8 +1075,16 @@ func TestUint256ComparisonUint64(t *testing.T) { }} for _, test := range tests { - // Ensure comparing the numbers produces the expected == result. + // Ensure comparing the numbers produces the expected cmp result. n1 := hexToUint256(test.n1) + gotCmp := n1.CmpUint64(test.n2) + if gotCmp != test.wantCmp { + t.Errorf("%q: incorrect cmp result -- got: %v, want: %v", test.name, + gotCmp, test.wantCmp) + continue + } + + // Ensure comparing the numbers produces the expected == result. isEq := n1.EqUint64(test.n2) wantEq := test.wantCmp == 0 if isEq != wantEq { @@ -1137,8 +1160,15 @@ func TestUint256ComparisonUint64Random(t *testing.T) { t.Fatalf("failed equality check -- n: %x", truncatedN1) } - // Ensure comparing the numbers produces the expected == result. + // Ensure the uint256 comparison result matches the one using big ints. bigCmpResult := bigN1.Cmp(bigN2) + cmpResult := n1.CmpUint64(n2) + if cmpResult != bigCmpResult { + t.Errorf("incorrect cmp result n1: %x, n2: %x -- got %v, want %v", + n1, n2, cmpResult, bigCmpResult) + } + + // Ensure comparing the numbers produces the expected == result. isEq := n1.EqUint64(n2) wantEq := bigCmpResult == 0 if isEq != wantEq {