diff --git a/bold/challenge-manager/challenge-tree/ancestors.go b/bold/challenge-manager/challenge-tree/ancestors.go index 3568ca5a50..a1e26af62c 100644 --- a/bold/challenge-manager/challenge-tree/ancestors.go +++ b/bold/challenge-manager/challenge-tree/ancestors.go @@ -10,10 +10,10 @@ import ( "github.com/pkg/errors" - "github.com/offchainlabs/nitro/bold/chain-abstraction" + protocol "github.com/offchainlabs/nitro/bold/chain-abstraction" "github.com/offchainlabs/nitro/bold/containers" "github.com/offchainlabs/nitro/bold/containers/threadsafe" - "github.com/offchainlabs/nitro/bold/math" + "github.com/offchainlabs/nitro/util/arbmath" ) var ( @@ -169,7 +169,7 @@ func (ht *RoyalChallengeTree) findHonestAncestorsWithinChallengeLevel( currStart, _ := cursor.StartCommitment() currEnd, _ := cursor.EndCommitment() - bisectTo, err := math.Bisect(uint64(currStart), uint64(currEnd)) + bisectTo, err := arbmath.Bisect(uint64(currStart), uint64(currEnd)) if err != nil { return nil, nil, errors.Wrapf(err, "could not bisect start=%d, end=%d", currStart, currEnd) } diff --git a/bold/challenge-manager/edge-tracker/tracker.go b/bold/challenge-manager/edge-tracker/tracker.go index e3adcf749b..b4f8dfb815 100644 --- a/bold/challenge-manager/edge-tracker/tracker.go +++ b/bold/challenge-manager/edge-tracker/tracker.go @@ -18,16 +18,16 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/offchainlabs/nitro/bold/chain-abstraction" - "github.com/offchainlabs/nitro/bold/challenge-manager/challenge-tree" + protocol "github.com/offchainlabs/nitro/bold/chain-abstraction" + challengetree "github.com/offchainlabs/nitro/bold/challenge-manager/challenge-tree" "github.com/offchainlabs/nitro/bold/containers" "github.com/offchainlabs/nitro/bold/containers/events" "github.com/offchainlabs/nitro/bold/containers/fsm" "github.com/offchainlabs/nitro/bold/containers/option" - "github.com/offchainlabs/nitro/bold/layer2-state-provider" - "github.com/offchainlabs/nitro/bold/math" + l2stateprovider "github.com/offchainlabs/nitro/bold/layer2-state-provider" "github.com/offchainlabs/nitro/bold/state-commitments/history" utilTime "github.com/offchainlabs/nitro/bold/time" + "github.com/offchainlabs/nitro/util/arbmath" ) var ( @@ -530,7 +530,7 @@ func (et *Tracker) DetermineBisectionHistoryWithProof( ) (history.History, []byte, error) { startHeight, _ := et.edge.StartCommitment() endHeight, _ := et.edge.EndCommitment() - bisectTo, err := math.Bisect(uint64(startHeight), uint64(endHeight)) + bisectTo, err := arbmath.Bisect(uint64(startHeight), uint64(endHeight)) if err != nil { return history.History{}, nil, errors.Wrapf(err, "determining bisection point errored for %d and %d", startHeight, endHeight) } diff --git a/bold/math/intlog2.go b/bold/math/intlog2.go deleted file mode 100644 index 4c0e2d42b5..0000000000 --- a/bold/math/intlog2.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2024, Offchain Labs, Inc. -// For license information, see: -// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md - -package math - -import "math/bits" - -// Log2Floor returns the integer logarithm base 2 of u (rounded down). -func Log2Floor(u uint64) int { - if u == 0 { - panic("log2 undefined for non-positive values") - } - return bits.Len64(u) - 1 -} - -// Log2Ceil returns the integer logarithm base 2 of u (rounded up). -func Log2Ceil(u uint64) int { - r := Log2Floor(u) - if isPowerOfTwo(u) { - return r - } - return r + 1 -} - -func isPowerOfTwo(u uint64) bool { - return u&(u-1) == 0 -} diff --git a/bold/math/intlog2_test.go b/bold/math/intlog2_test.go deleted file mode 100644 index dbeb5a2efb..0000000000 --- a/bold/math/intlog2_test.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2024, Offchain Labs, Inc. -// For license information, see: -// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md - -package math - -import ( - "fmt" - "math" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/offchainlabs/nitro/bold/testing/casttest" -) - -var benchResult int - -func TestUnsingedIntegerLog2Floor(t *testing.T) { - type log2TestCase struct { - input uint64 - expected int - } - - testCases := []log2TestCase{ - {input: 1, expected: 0}, - {input: 2, expected: 1}, - {input: 4, expected: 2}, - {input: 6, expected: 2}, - {input: 8, expected: 3}, - {input: 24601, expected: 14}, - } - for _, tc := range testCases { - t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { - res := Log2Floor(tc.input) - require.Equal(t, tc.expected, res) - }) - } -} - -func TestUnsingedIntegerLog2FloorPanicsOnZero(t *testing.T) { - require.Panics(t, func() { - Log2Floor(0) - }) -} - -func FuzzUnsingedIntegerLog2Floor(f *testing.F) { - testcases := []uint64{0, 2, 4, 6, 8} - for _, tc := range testcases { - f.Add(tc) - } - f.Fuzz(func(t *testing.T, input uint64) { - if input == 0 { - require.Panics(t, func() { - Log2Floor(input) - }) - t.Skip() - } - r := Log2Floor(input) - fr := math.Log2(float64(input)) - require.Equal(t, int(math.Floor(fr)), r) - }) -} - -func BenchmarkUnsingedIntegerLog2Floor(b *testing.B) { - var r int - for i := 1; i < b.N; i++ { - r = Log2Floor(casttest.ToUint64(b, i)) - } - benchResult = r -} - -func BenchmarkMathLog2Floor(b *testing.B) { - var r int - for i := 1; i < b.N; i++ { - r = int(math.Log2(float64(i))) - } - benchResult = r -} - -func TestUnsingedIntegerLog2Ceil(t *testing.T) { - type log2TestCase struct { - input uint64 - expected int - } - - testCases := []log2TestCase{ - {input: 1, expected: 0}, - {input: 2, expected: 1}, - {input: 4, expected: 2}, - {input: 6, expected: 3}, - {input: 8, expected: 3}, - {input: 24601, expected: 15}, - } - for _, tc := range testCases { - t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { - res := Log2Ceil(tc.input) - require.Equal(t, tc.expected, res) - }) - } -} - -func TestUnsingedIntegerLog2CeilPanicsOnZero(t *testing.T) { - require.Panics(t, func() { - Log2Ceil(0) - }) -} - -func FuzzUnsingedIntegerLog2Ceil(f *testing.F) { - testcases := []uint64{0, 2, 4, 6, 8} - for _, tc := range testcases { - f.Add(tc) - } - f.Fuzz(func(t *testing.T, input uint64) { - if input == 0 { - require.Panics(t, func() { - Log2Ceil(input) - }) - t.Skip() - } - r := Log2Ceil(input) - fr := math.Log2(float64(input)) - require.Equal(t, int(math.Ceil(fr)), r) - }) -} - -func BenchmarkUnsingedIntegerLog2Ceil(b *testing.B) { - var r int - for i := 1; i < b.N; i++ { - r = Log2Ceil(casttest.ToUint64(b, i)) - } - benchResult = r -} - -func BenchmarkMathLog2Ceil(b *testing.B) { - var r int - for i := 1; i < b.N; i++ { - r = int(math.Ceil(math.Log2(float64(i)))) - } - benchResult = r -} diff --git a/bold/math/math.go b/bold/math/math.go deleted file mode 100644 index feafae8eff..0000000000 --- a/bold/math/math.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2023-2024, Offchain Labs, Inc. -// For license information, see: -// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md - -// Package math defines utilities for performing operations critical to the -// computations performed during a challenge in BOLD. -package math - -import ( - "errors" - "math" - "math/bits" -) - -var ErrUnableToBisect = errors.New("unable to bisect") - -// Unsigned is a generic constraint for all unsigned numeric primitives. -type Unsigned interface { - ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 -} - -func Bisect(pre, post uint64) (uint64, error) { - if pre+2 > post { - return 0, ErrUnableToBisect - } - if pre+2 == post { - return pre + 1, nil - } - matchingBits := bits.LeadingZeros64((post - 1) ^ pre) - mask := uint64(math.MaxUint64) << (63 - matchingBits) - return (post - 1) & mask, nil -} diff --git a/bold/math/math_test.go b/bold/math/math_test.go deleted file mode 100644 index 05fc3325c7..0000000000 --- a/bold/math/math_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2023-2024, Offchain Labs, Inc. -// For license information, see: -// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md - -package math - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBisectionPoint(t *testing.T) { - type bpTestCase struct { - pre uint64 - post uint64 - expected uint64 - } - - errorTestCases := []bpTestCase{ - {12, 13, 0}, - {13, 9, 0}, - } - for _, testCase := range errorTestCases { - _, err := Bisect(testCase.pre, testCase.post) - require.ErrorIs(t, err, ErrUnableToBisect, testCase) - } - testCases := []bpTestCase{ - {0, 2, 1}, - {1, 3, 2}, - {31, 33, 32}, - {32, 34, 33}, - {13, 15, 14}, - {0, 9, 8}, - {0, 13, 8}, - {0, 15, 8}, - {13, 17, 16}, - {13, 31, 16}, - {15, 31, 16}, - } - for _, testCase := range testCases { - res, err := Bisect(testCase.pre, testCase.post) - require.NoError(t, err, testCase) - require.Equal(t, testCase.expected, res) - } -} diff --git a/bold/state-commitments/history/history_commitment.go b/bold/state-commitments/history/history_commitment.go index de78ee8d1f..42f0705cf0 100644 --- a/bold/state-commitments/history/history_commitment.go +++ b/bold/state-commitments/history/history_commitment.go @@ -48,7 +48,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/offchainlabs/nitro/bold/math" + "github.com/offchainlabs/nitro/util/arbmath" ) var ( @@ -237,11 +237,11 @@ func (h *historyCommitter) computeRoot(leaves []common.Hash, virtual uint64) (co } hashed := h.hashLeaves(leaves) limit := nextPowerOf2(virtual) - depth, err := safecast.ToUint(math.Log2Floor(limit)) + depth, err := safecast.ToUint(arbmath.Log2Floor(limit)) if err != nil { return emptyHash, err } - n, err := safecast.ToUint(math.Log2Ceil(virtual)) + n, err := safecast.ToUint(arbmath.Log2Ceil(virtual)) if err != nil { return emptyHash, err } @@ -313,7 +313,7 @@ func (h *historyCommitter) partialRoot(leaves []common.Hash, virtual, limit uint if limit < virtual { return emptyHash, fmt.Errorf("limit %d should be >= virtual %d", limit, virtual) } - minFillers := math.Log2Ceil(uint64(virtual)) + minFillers := arbmath.Log2Ceil(uint64(virtual)) if len(h.fillers) < minFillers { return emptyHash, fmt.Errorf("insufficient fillers, want %d, got %d", minFillers, len(h.fillers)) } @@ -360,7 +360,7 @@ func (h *historyCommitter) partialRoot(leaves []common.Hash, virtual, limit uint // made purely of virtual nodes, and it is a complete tree. // So, the root of the subtree will be the precomputed filler // at the current layer. - right = h.fillers[math.Log2Floor(uint64(mid))] + right = h.fillers[arbmath.Log2Floor(uint64(mid))] h.handle(right) } else { var rLeaves []common.Hash @@ -524,7 +524,7 @@ func (h *historyCommitter) prefixAndProof(index uint64, leaves []common.Hash, vi if index+1 > virtual { return nil, nil, fmt.Errorf("index %d + 1 should be <= virtual %d", index, virtual) } - logVirtual, err := safecast.ToUint(math.Log2Floor(virtual) + 1) + logVirtual, err := safecast.ToUint(arbmath.Log2Floor(virtual) + 1) if err != nil { return nil, nil, err } @@ -574,7 +574,7 @@ func lastLeafProofPositions(virtual uint64) ([]treePosition, error) { return []treePosition{}, nil } limit := nextPowerOf2(uint64(virtual)) - depth := math.Log2Floor(limit) + depth := arbmath.Log2Floor(limit) positions := make([]treePosition, depth) idx := uint64(virtual) - 1 for l := range positions { diff --git a/util/arbmath/math.go b/util/arbmath/math.go index 792c82d2cc..f0597ed5be 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -4,6 +4,7 @@ package arbmath import ( + "errors" "math" "math/big" "math/bits" @@ -33,6 +34,43 @@ func Log2ceil(value uint64) uint64 { return uint64(64 - bits.LeadingZeros64(value)) } +// Log2Floor returns the integer logarithm base 2 of u (rounded down). +// Returns 0 for u == 0 to maintain consistency with arbmath.Log2ceil. +func Log2Floor(u uint64) int { + if u == 0 { + return 0 + } + return bits.Len64(u) - 1 +} + +// Log2Ceil returns the integer logarithm base 2 of u (rounded up). +// Returns 0 for u == 0 to maintain consistency with arbmath.Log2ceil. +func Log2Ceil(u uint64) int { + r := Log2Floor(u) + if u == 0 || isPowerOfTwo(u) { + return r + } + return r + 1 +} + +func isPowerOfTwo(u uint64) bool { + return u&(u-1) == 0 +} + +var ErrUnableToBisect = errors.New("unable to bisect") + +func Bisect(pre, post uint64) (uint64, error) { + if pre+2 > post { + return 0, ErrUnableToBisect + } + if pre+2 == post { + return pre + 1, nil + } + matchingBits := bits.LeadingZeros64((post - 1) ^ pre) + mask := uint64(math.MaxUint64) << (63 - matchingBits) + return (post - 1) & mask, nil +} + type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } diff --git a/util/arbmath/math_bench_test.go b/util/arbmath/math_bench_test.go new file mode 100644 index 0000000000..22e7ecfa11 --- /dev/null +++ b/util/arbmath/math_bench_test.go @@ -0,0 +1,112 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package arbmath + +import ( + "math/rand" + "testing" +) + +// Benchmark tests for Log2Floor and Log2Ceil functions +func BenchmarkLog2Floor(b *testing.B) { + // Test with various input sizes + testCases := []struct { + name string + gen func() uint64 + }{ + {"Small", func() uint64 { return uint64(rand.Intn(1000)) }}, + {"Medium", func() uint64 { return uint64(rand.Intn(1000000)) }}, + {"Large", func() uint64 { return uint64(rand.Intn(1000000000)) }}, + {"VeryLarge", func() uint64 { return rand.Uint64() }}, + {"PowersOfTwo", func() uint64 { return 1 << uint(rand.Intn(64)) }}, + {"Zero", func() uint64 { return 0 }}, + {"One", func() uint64 { return 1 }}, + {"MaxUint64", func() uint64 { return ^uint64(0) }}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + // Pre-generate test values to avoid timing the random generation + values := make([]uint64, b.N) + for i := range values { + values[i] = tc.gen() + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Log2Floor(values[i]) + } + }) + } +} + +func BenchmarkLog2Ceil(b *testing.B) { + // Test with various input sizes + testCases := []struct { + name string + gen func() uint64 + }{ + {"Small", func() uint64 { return uint64(rand.Intn(1000)) }}, + {"Medium", func() uint64 { return uint64(rand.Intn(1000000)) }}, + {"Large", func() uint64 { return uint64(rand.Intn(1000000000)) }}, + {"VeryLarge", func() uint64 { return rand.Uint64() }}, + {"PowersOfTwo", func() uint64 { return 1 << uint(rand.Intn(64)) }}, + {"Zero", func() uint64 { return 0 }}, + {"One", func() uint64 { return 1 }}, + {"MaxUint64", func() uint64 { return ^uint64(0) }}, + } + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + // Pre-generate test values to avoid timing the random generation + values := make([]uint64, b.N) + for i := range values { + values[i] = tc.gen() + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Log2Ceil(values[i]) + } + }) + } +} + +// Benchmark comparison between Log2Floor and Log2Ceil +func BenchmarkLog2FloorVsCeil(b *testing.B) { + values := make([]uint64, b.N) + for i := range values { + values[i] = rand.Uint64() + } + + b.Run("Log2Floor", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Log2Floor(values[i]) + } + }) + + b.Run("Log2Ceil", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Log2Ceil(values[i]) + } + }) +} + +// Benchmark the relationship between Log2Floor and Log2Ceil +func BenchmarkLog2FloorAndCeil(b *testing.B) { + values := make([]uint64, b.N) + for i := range values { + values[i] = rand.Uint64() + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + floor := Log2Floor(values[i]) + ceil := Log2Ceil(values[i]) + _ = floor + _ = ceil + } +} diff --git a/util/arbmath/math_fuzz_test.go b/util/arbmath/math_fuzz_test.go index 7c2acf5084..a54666b93d 100644 --- a/util/arbmath/math_fuzz_test.go +++ b/util/arbmath/math_fuzz_test.go @@ -110,3 +110,101 @@ func FuzzSaturatingNegInt32(f *testing.F) { func FuzzSaturatingNegInt64(f *testing.F) { fuzzSaturatingNeg[int64](f) } + +// Fuzzing tests for Log2Floor and Log2Ceil functions +func FuzzLog2Floor(f *testing.F) { + // Add some seed values to help the fuzzer + seedValues := []uint64{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 15, 16, 17, 31, 32, 33, 63, 64, 65, + 127, 128, 129, 255, 256, 257, + 1023, 1024, 1025, 2047, 2048, 2049, + 65535, 65536, 65537, + 4294967295, 4294967296, 4294967297, + 18446744073709551615, // max uint64 + } + for _, val := range seedValues { + f.Add(val) + } + + f.Fuzz(func(t *testing.T, u uint64) { + result := Log2Floor(u) + + // Verify the result is non-negative + if result < 0 { + t.Errorf("Log2Floor(%d) = %d, expected non-negative result", u, result) + } + + // For u == 0, result should be 0 + if u == 0 { + if result != 0 { + t.Errorf("Log2Floor(0) = %d, expected 0", result) + } + return + } + + // For u > 0, verify that 2^result <= u < 2^(result+1) + lowerBound := uint64(1) << result + upperBound := uint64(1) << (result + 1) + + if u < lowerBound || u >= upperBound { + t.Errorf("Log2Floor(%d) = %d, but 2^%d = %d and 2^%d = %d", + u, result, result, lowerBound, result+1, upperBound) + } + }) +} + +func FuzzLog2Ceil(f *testing.F) { + // Add some seed values to help the fuzzer + seedValues := []uint64{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 15, 16, 17, 31, 32, 33, 63, 64, 65, + 127, 128, 129, 255, 256, 257, + 1023, 1024, 1025, 2047, 2048, 2049, + 65535, 65536, 65537, + 4294967295, 4294967296, 4294967297, + 18446744073709551615, // max uint64 + } + for _, val := range seedValues { + f.Add(val) + } + + f.Fuzz(func(t *testing.T, u uint64) { + result := Log2Ceil(u) + + // Verify the result is non-negative + if result < 0 { + t.Errorf("Log2Ceil(%d) = %d, expected non-negative result", u, result) + } + + // For u == 0, result should be 0 + if u == 0 { + if result != 0 { + t.Errorf("Log2Ceil(0) = %d, expected 0", result) + } + return + } + + // For u > 0, verify that 2^(result-1) < u <= 2^result + if result > 0 { + lowerBound := uint64(1) << (result - 1) + upperBound := uint64(1) << result + + if u <= lowerBound || u > upperBound { + t.Errorf("Log2Ceil(%d) = %d, but 2^%d = %d and 2^%d = %d", + u, result, result-1, lowerBound, result, upperBound) + } + } + + // Verify that Log2Ceil(u) >= Log2Floor(u) + floorResult := Log2Floor(u) + if result < floorResult { + t.Errorf("Log2Ceil(%d) = %d < Log2Floor(%d) = %d", u, result, u, floorResult) + } + + // Verify that Log2Ceil(u) <= Log2Floor(u) + 1 + if result > floorResult+1 { + t.Errorf("Log2Ceil(%d) = %d > Log2Floor(%d) + 1 = %d", u, result, u, floorResult+1) + } + }) +} diff --git a/util/arbmath/math_test.go b/util/arbmath/math_test.go index 5441b1de75..d2d4ed7323 100644 --- a/util/arbmath/math_test.go +++ b/util/arbmath/math_test.go @@ -253,6 +253,111 @@ func TestApproxExpBasisPoints(t *testing.T) { } } +func TestLog2Floor(t *testing.T) { + type log2TestCase struct { + input uint64 + expected int + } + + testCases := []log2TestCase{ + {input: 0, expected: 0}, + {input: 1, expected: 0}, + {input: 2, expected: 1}, + {input: 4, expected: 2}, + {input: 6, expected: 2}, + {input: 8, expected: 3}, + {input: 24601, expected: 14}, + } + for _, tc := range testCases { + t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { + res := Log2Floor(tc.input) + if res != tc.expected { + t.Errorf("Log2Floor(%d) = %d; want %d", tc.input, res, tc.expected) + } + }) + } +} + +func TestLog2FloorReturnsZeroForZero(t *testing.T) { + result := Log2Floor(0) + if result != 0 { + t.Errorf("Log2Floor(0) = %d; want 0", result) + } +} + +func TestLog2Ceil(t *testing.T) { + type log2TestCase struct { + input uint64 + expected int + } + + testCases := []log2TestCase{ + {input: 0, expected: 0}, + {input: 1, expected: 0}, + {input: 2, expected: 1}, + {input: 4, expected: 2}, + {input: 6, expected: 3}, + {input: 8, expected: 3}, + {input: 24601, expected: 15}, + } + for _, tc := range testCases { + t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { + res := Log2Ceil(tc.input) + if res != tc.expected { + t.Errorf("Log2Ceil(%d) = %d; want %d", tc.input, res, tc.expected) + } + }) + } +} + +func TestLog2CeilReturnsZeroForZero(t *testing.T) { + result := Log2Ceil(0) + if result != 0 { + t.Errorf("Log2Ceil(0) = %d; want 0", result) + } +} + +func TestBisectionPoint(t *testing.T) { + type bpTestCase struct { + pre uint64 + post uint64 + expected uint64 + } + + errorTestCases := []bpTestCase{ + {12, 13, 0}, + {13, 9, 0}, + } + for _, testCase := range errorTestCases { + _, err := Bisect(testCase.pre, testCase.post) + if err != ErrUnableToBisect { + t.Errorf("Bisect(%d, %d) should return ErrUnableToBisect", testCase.pre, testCase.post) + } + } + testCases := []bpTestCase{ + {0, 2, 1}, + {1, 3, 2}, + {31, 33, 32}, + {32, 34, 33}, + {13, 15, 14}, + {0, 9, 8}, + {0, 13, 8}, + {0, 15, 8}, + {13, 17, 16}, + {13, 31, 16}, + {15, 31, 16}, + } + for _, testCase := range testCases { + res, err := Bisect(testCase.pre, testCase.post) + if err != nil { + t.Errorf("Bisect(%d, %d) returned error: %v", testCase.pre, testCase.post, err) + } + if res != testCase.expected { + t.Errorf("Bisect(%d, %d) = %d; want %d", testCase.pre, testCase.post, res, testCase.expected) + } + } +} + func Fail(t *testing.T, printables ...interface{}) { t.Helper() testhelpers.FailImpl(t, printables...)