From dd0c5a7c2ca253cac493991dac438806ae2527b2 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 19 Sep 2022 23:46:03 -0400 Subject: [PATCH 01/14] feat: osmomath log2 approximation --- osmomath/decimal.go | 63 ++++++++++++++++++++ osmomath/decimal_test.go | 115 +++++++++++++++++++++++++++--------- osmomath/log2_bench_test.go | 34 +++++++++++ 3 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 osmomath/log2_bench_test.go diff --git a/osmomath/decimal.go b/osmomath/decimal.go index 3125d64fabd..d45197c8a3c 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -39,6 +39,8 @@ var ( zeroInt = big.NewInt(0) oneInt = big.NewInt(1) tenInt = big.NewInt(10) + + log2LookupTable map[uint8]BigDec ) // Decimal errors @@ -54,6 +56,27 @@ func init() { for i := 0; i <= Precision; i++ { precisionMultipliers[i] = calcPrecisionMultiplier(int64(i)) } + + log2LookupTable = buildLog2LookupTable() +} + +// buildLog2LookupTable returns a lookup table for log values +// ranging from [1, 2) +// the keys are multiplied by 10 to simplify the rounding logic +// in the log function.s +func buildLog2LookupTable() map[uint8]BigDec { + return map[uint8]BigDec{ + 10: ZeroDec(), + 11: MustNewDecFromStr("0.137503523749934908329043617236402782"), + 12: MustNewDecFromStr("0.263034405833793833583419514458426332"), + 13: MustNewDecFromStr("0.378511623253729812526493224767304557"), + 14: MustNewDecFromStr("0.485426827170241759571649887742440632"), + 15: MustNewDecFromStr("0.584962500721156181453738943947816508"), + 16: MustNewDecFromStr("0.678071905112637652129680570510609824"), + 17: MustNewDecFromStr("0.765534746362977060383746581321014178"), + 18: MustNewDecFromStr("0.847996906554950015037158458406242841"), + 19: MustNewDecFromStr("0.925999418556223145923199993417444246"), + } } func precisionInt() *big.Int { @@ -836,3 +859,43 @@ func DecApproxEq(t *testing.T, d1 BigDec, d2 BigDec, tol BigDec) (*testing.T, bo diff := d1.Sub(d2).Abs() return t, diff.LTE(tol), "expected |d1 - d2| <:\t%v\ngot |d1 - d2| = \t\t%v", tol.String(), diff.String() } + +// ApproxLog2 returns the approximation of log_2 {x}. +// Rounds down by truncating and right shifting during +// calculations. +func (x BigDec) ApproxLog2() BigDec { + if x.LT(OneDec()) { + panic(fmt.Sprintf("only supporting values >= 1, given (%s)", x)) + } + + // Normalize x to be 1 < x <= 2 + + // y is the exponent that results in a whole multiple of 2. + y := int64(0) + + // invariant: x >= 1 + // while x < 1 + for x.LT(OneDec()) { + x.i = x.i.Lsh(x.i, 1) + y = y - 1 + } + + // invariant: x < 2 + // while x >= 2 + twoDec := NewBigDec(2) + for x.GTE(twoDec) { + x.i = x.i.Rsh(x.i, 1) + y = y + 1 + } + + lookupKey := x.MulInt64(10).TruncateInt() + if lookupKey.GTE(NewInt(20)) || lookupKey.LT(NewInt(10)) { + panic(fmt.Sprintf("invalid lookup key (%s), must be 10 <= lookup key < 2", lookupKey)) + } + + tableValue, found := log2LookupTable[uint8(lookupKey.Int64())] + if !found { + panic(fmt.Sprintf("no matching value for key (%s) in the lookup table", lookupKey)) + } + return NewBigDec(y).Add(tableValue) +} diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index 09a419e414c..614ab5d7568 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -11,6 +11,8 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "gopkg.in/yaml.v2" + + "github.com/osmosis-labs/osmosis/v12/app/apptesting/osmoassert" ) type decimalTestSuite struct { @@ -152,8 +154,8 @@ func (s *decimalTestSuite) TestDecFloat64() { func (s *decimalTestSuite) TestSdkDec() { tests := []struct { - d BigDec - want sdk.Dec + d BigDec + want sdk.Dec expPanic bool }{ {NewBigDec(0), sdk.MustNewDecFromStr("0.000000000000000000"), false}, @@ -177,8 +179,8 @@ func (s *decimalTestSuite) TestSdkDec() { func (s *decimalTestSuite) TestBigDecFromSdkDec() { tests := []struct { - d sdk.Dec - want BigDec + d sdk.Dec + want BigDec expPanic bool }{ {sdk.MustNewDecFromStr("0.000000000000000000"), NewBigDec(0), false}, @@ -415,14 +417,14 @@ func (s *decimalTestSuite) TestDecCeil() { input BigDec expected BigDec }{ - {MustNewDecFromStr("0.001"), NewBigDec(1)}, // 0.001 => 1.0 - {MustNewDecFromStr("-0.001"), ZeroDec()}, // -0.001 => 0.0 - {ZeroDec(), ZeroDec()}, // 0.0 => 0.0 - {MustNewDecFromStr("0.9"), NewBigDec(1)}, // 0.9 => 1.0 + {MustNewDecFromStr("0.001"), NewBigDec(1)}, // 0.001 => 1.0 + {MustNewDecFromStr("-0.001"), ZeroDec()}, // -0.001 => 0.0 + {ZeroDec(), ZeroDec()}, // 0.0 => 0.0 + {MustNewDecFromStr("0.9"), NewBigDec(1)}, // 0.9 => 1.0 {MustNewDecFromStr("4.001"), NewBigDec(5)}, // 4.001 => 5.0 {MustNewDecFromStr("-4.001"), NewBigDec(-4)}, // -4.001 => -4.0 - {MustNewDecFromStr("4.7"), NewBigDec(5)}, // 4.7 => 5.0 - {MustNewDecFromStr("-4.7"), NewBigDec(-4)}, // -4.7 => -4.0 + {MustNewDecFromStr("4.7"), NewBigDec(5)}, // 4.7 => 5.0 + {MustNewDecFromStr("-4.7"), NewBigDec(-4)}, // -4.7 => -4.0 } for i, tc := range testCases { @@ -437,11 +439,11 @@ func (s *decimalTestSuite) TestPower() { power uint64 expected BigDec }{ - {OneDec(), 10, OneDec()}, // 1.0 ^ (10) => 1.0 - {NewDecWithPrec(5, 1), 2, NewDecWithPrec(25, 2)}, // 0.5 ^ 2 => 0.25 - {NewDecWithPrec(2, 1), 2, NewDecWithPrec(4, 2)}, // 0.2 ^ 2 => 0.04 - {NewDecFromInt(NewInt(3)), 3, NewDecFromInt(NewInt(27))}, // 3 ^ 3 => 27 - {NewDecFromInt(NewInt(-3)), 4, NewDecFromInt(NewInt(81))}, // -3 ^ 4 = 81 + {OneDec(), 10, OneDec()}, // 1.0 ^ (10) => 1.0 + {NewDecWithPrec(5, 1), 2, NewDecWithPrec(25, 2)}, // 0.5 ^ 2 => 0.25 + {NewDecWithPrec(2, 1), 2, NewDecWithPrec(4, 2)}, // 0.2 ^ 2 => 0.04 + {NewDecFromInt(NewInt(3)), 3, NewDecFromInt(NewInt(27))}, // 3 ^ 3 => 27 + {NewDecFromInt(NewInt(-3)), 4, NewDecFromInt(NewInt(81))}, // -3 ^ 4 = 81 {MustNewDecFromStr("1.414213562373095048801688724209698079"), 2, NewDecFromInt(NewInt(2))}, // 1.414213562373095048801688724209698079 ^ 2 = 2 } @@ -457,14 +459,14 @@ func (s *decimalTestSuite) TestApproxRoot() { root uint64 expected BigDec }{ - {OneDec(), 10, OneDec()}, // 1.0 ^ (0.1) => 1.0 - {NewDecWithPrec(25, 2), 2, NewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5 - {NewDecWithPrec(4, 2), 2, NewDecWithPrec(2, 1)}, // 0.04 ^ (0.5) => 0.2 - {NewDecFromInt(NewInt(27)), 3, NewDecFromInt(NewInt(3))}, // 27 ^ (1/3) => 3 - {NewDecFromInt(NewInt(-81)), 4, NewDecFromInt(NewInt(-3))}, // -81 ^ (0.25) => -3 - {NewDecFromInt(NewInt(2)), 2, MustNewDecFromStr("1.414213562373095048801688724209698079")}, // 2 ^ (0.5) => 1.414213562373095048801688724209698079 + {OneDec(), 10, OneDec()}, // 1.0 ^ (0.1) => 1.0 + {NewDecWithPrec(25, 2), 2, NewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5 + {NewDecWithPrec(4, 2), 2, NewDecWithPrec(2, 1)}, // 0.04 ^ (0.5) => 0.2 + {NewDecFromInt(NewInt(27)), 3, NewDecFromInt(NewInt(3))}, // 27 ^ (1/3) => 3 + {NewDecFromInt(NewInt(-81)), 4, NewDecFromInt(NewInt(-3))}, // -81 ^ (0.25) => -3 + {NewDecFromInt(NewInt(2)), 2, MustNewDecFromStr("1.414213562373095048801688724209698079")}, // 2 ^ (0.5) => 1.414213562373095048801688724209698079 {NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153903837946258002096839")}, // 1.005 ^ (1/31536000) ≈ 1.000000000158153903837946258002096839 - {SmallestDec(), 2, NewDecWithPrec(1, 18)}, // 1e-36 ^ (0.5) => 1e-18 + {SmallestDec(), 2, NewDecWithPrec(1, 18)}, // 1e-36 ^ (0.5) => 1e-18 {SmallestDec(), 3, MustNewDecFromStr("0.000000000001000000000000000002431786")}, // 1e-36 ^ (1/3) => 1e-12 {NewDecWithPrec(1, 8), 3, MustNewDecFromStr("0.002154434690031883721759293566519280")}, // 1e-8 ^ (1/3) ≈ 0.002154434690031883721759293566519 } @@ -485,11 +487,11 @@ func (s *decimalTestSuite) TestApproxSqrt() { input BigDec expected BigDec }{ - {OneDec(), OneDec()}, // 1.0 => 1.0 - {NewDecWithPrec(25, 2), NewDecWithPrec(5, 1)}, // 0.25 => 0.5 - {NewDecWithPrec(4, 2), NewDecWithPrec(2, 1)}, // 0.09 => 0.3 - {NewDecFromInt(NewInt(9)), NewDecFromInt(NewInt(3))}, // 9 => 3 - {NewDecFromInt(NewInt(-9)), NewDecFromInt(NewInt(-3))}, // -9 => -3 + {OneDec(), OneDec()}, // 1.0 => 1.0 + {NewDecWithPrec(25, 2), NewDecWithPrec(5, 1)}, // 0.25 => 0.5 + {NewDecWithPrec(4, 2), NewDecWithPrec(2, 1)}, // 0.09 => 0.3 + {NewDecFromInt(NewInt(9)), NewDecFromInt(NewInt(3))}, // 9 => 3 + {NewDecFromInt(NewInt(-9)), NewDecFromInt(NewInt(-3))}, // -9 => -3 {NewDecFromInt(NewInt(2)), MustNewDecFromStr("1.414213562373095048801688724209698079")}, // 2 => 1.414213562373095048801688724209698079 } @@ -624,3 +626,62 @@ func BenchmarkMarshalTo(b *testing.B) { } } } + +func (s *decimalTestSuite) TestLog2() { + + tests := map[string]struct { + initialValue BigDec + expected BigDec + + expectedPanic bool + }{ + "log_2{0.99}; not supported; panic": { + initialValue: NewDecWithPrec(99, 2), + + expectedPanic: true, + }, + "log_2{1} = 0": { + initialValue: NewBigDec(1), + expected: NewBigDec(0), + }, + "log_2{2} = 1": { + initialValue: NewBigDec(2), + expected: NewBigDec(1), + }, + "log_2{512} = 9": { + initialValue: NewBigDec(512), + expected: NewBigDec(9), + }, + "log_2{600} = 9": { + initialValue: NewBigDec(580), + // TODO: true value is: 9.179909090014934468590092754117374938 + // Need better lookup table. + expected: MustNewDecFromStr("9.137503523749934908329043617236402782"), + }, + "log_2{1024} = 10": { + initialValue: NewBigDec(1024), + expected: NewBigDec(10), + }, + "log_2{1024.987654321} = 10": { + initialValue: NewDecWithPrec(1024987654321, 9), + // TODO: true value is: 10.001390817654141324352719749259888355 + // Need better lookup table + expected: MustNewDecFromStr("10.000000000000000000000000000000000000"), + }, + "log_2{912648174127941279170121098210.92821920190204131121} = 99.525973560175362367047484597337715868": { + initialValue: MustNewDecFromStr("912648174127941279170121098210.92821920190204131121"), + // TODO: true value is: 99.525973560175362367047484597337715868 + // Need better lookup table + expected: MustNewDecFromStr("99.485426827170241759571649887742440632"), + }, + } + + for name, tc := range tests { + s.Run(name, func() { + osmoassert.ConditionalPanic(s.T(), tc.expectedPanic, func() { + res := tc.initialValue.ApproxLog2() + s.Require().Equal(tc.expected, res) + }) + }) + } +} diff --git a/osmomath/log2_bench_test.go b/osmomath/log2_bench_test.go new file mode 100644 index 00000000000..1a723be1643 --- /dev/null +++ b/osmomath/log2_bench_test.go @@ -0,0 +1,34 @@ +package osmomath + +import ( + "testing" +) + +func BenchmarkLog2(b *testing.B) { + tests := []struct { + value BigDec + }{ + // TODO: Choose selection here more robustly + { + value: MustNewDecFromStr("1.2"), + }, + { + value: MustNewDecFromStr("1.234"), + }, + { + value: MustNewDecFromStr("1024"), + }, + { + value: NewBigDec(2048 * 2048 * 2048 * 2048 * 2048), + }, + { + value: MustNewDecFromStr("999999999999999999999999999999999999999999999999999999.9122181273612911"), + }, + } + + for i := 0; i < b.N; i++ { + for _, test := range tests { + test.value.ApproxLog2() + } + } +} From 07d8ce6bc8fbad5c8a422ac9f0270688105c34f3 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Sep 2022 00:15:16 -0400 Subject: [PATCH 02/14] lint --- osmomath/decimal.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index d45197c8a3c..f5a902042c4 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -234,7 +234,8 @@ func (d BigDec) GTE(d2 BigDec) bool { return (d.i).Cmp(d2.i) >= 0 } / func (d BigDec) LT(d2 BigDec) bool { return (d.i).Cmp(d2.i) < 0 } // less than func (d BigDec) LTE(d2 BigDec) bool { return (d.i).Cmp(d2.i) <= 0 } // less than or equal func (d BigDec) Neg() BigDec { return BigDec{new(big.Int).Neg(d.i)} } // reverse the decimal sign -func (d BigDec) Abs() BigDec { return BigDec{new(big.Int).Abs(d.i)} } // absolute value +// nolint: stylecheck +func (d BigDec) Abs() BigDec { return BigDec{new(big.Int).Abs(d.i)} } // absolute value // BigInt returns a copy of the underlying big.Int. func (d BigDec) BigInt() *big.Int { From c50fd9dd14888d566e1008778b9332a2ca390173 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Sep 2022 00:18:18 -0400 Subject: [PATCH 03/14] fix comment --- osmomath/decimal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index f5a902042c4..cd64893e152 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -869,7 +869,7 @@ func (x BigDec) ApproxLog2() BigDec { panic(fmt.Sprintf("only supporting values >= 1, given (%s)", x)) } - // Normalize x to be 1 < x <= 2 + // Normalize x to be 1 <= x < 2 // y is the exponent that results in a whole multiple of 2. y := int64(0) From e4d196f685d8466e5d3e1a406fb968ef6857da1a Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Sep 2022 00:34:03 -0400 Subject: [PATCH 04/14] comment --- osmomath/decimal.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index cd64893e152..f5a8b1b2bbf 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -889,6 +889,8 @@ func (x BigDec) ApproxLog2() BigDec { y = y + 1 } + // exponentiate to simplify truncation necessary for + // looking up values in the table. lookupKey := x.MulInt64(10).TruncateInt() if lookupKey.GTE(NewInt(20)) || lookupKey.LT(NewInt(10)) { panic(fmt.Sprintf("invalid lookup key (%s), must be 10 <= lookup key < 2", lookupKey)) From 551201789ee7638bfeaa534a4166073031b1b86d Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Sep 2022 00:34:50 -0400 Subject: [PATCH 05/14] Update osmomath/decimal.go --- osmomath/decimal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index f5a8b1b2bbf..261ec814efc 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -63,7 +63,7 @@ func init() { // buildLog2LookupTable returns a lookup table for log values // ranging from [1, 2) // the keys are multiplied by 10 to simplify the rounding logic -// in the log function.s +// in the log function func buildLog2LookupTable() map[uint8]BigDec { return map[uint8]BigDec{ 10: ZeroDec(), From 5dbf722bb0eb8d044ff1e94bf3b3ba06937ebbd7 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 22 Oct 2022 03:10:02 -0400 Subject: [PATCH 06/14] improve accuracy with narrower range --- osmomath/decimal.go | 49 ++++++++++++++++++++++--------------- osmomath/decimal_test.go | 24 +++++++++--------- osmomath/log2_bench_test.go | 2 ++ 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index 224e6fc14ff..9ae08ad2e8e 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -40,7 +40,8 @@ var ( oneInt = big.NewInt(1) tenInt = big.NewInt(10) - log2LookupTable map[uint8]BigDec + log2LookupTable map[uint32]BigDec + upperBoundLog = MustNewDecFromStr("0.000144262291094538391070900057479701") ) // Decimal errors @@ -64,18 +65,18 @@ func init() { // ranging from [1, 2) // the keys are multiplied by 10 to simplify the rounding logic // in the log function -func buildLog2LookupTable() map[uint8]BigDec { - return map[uint8]BigDec{ - 10: ZeroDec(), - 11: MustNewDecFromStr("0.137503523749934908329043617236402782"), - 12: MustNewDecFromStr("0.263034405833793833583419514458426332"), - 13: MustNewDecFromStr("0.378511623253729812526493224767304557"), - 14: MustNewDecFromStr("0.485426827170241759571649887742440632"), - 15: MustNewDecFromStr("0.584962500721156181453738943947816508"), - 16: MustNewDecFromStr("0.678071905112637652129680570510609824"), - 17: MustNewDecFromStr("0.765534746362977060383746581321014178"), - 18: MustNewDecFromStr("0.847996906554950015037158458406242841"), - 19: MustNewDecFromStr("0.925999418556223145923199993417444246"), +func buildLog2LookupTable() map[uint32]BigDec { + return map[uint32]BigDec{ + 100000: ZeroDec(), + 100001: MustNewDecFromStr("0.000014426878274461848365683118054200"), + 100002: MustNewDecFromStr("0.000019999800000000000000000000000000"), + 100003: MustNewDecFromStr("0.000029999600000000000000000000000000"), + 100004: MustNewDecFromStr("0.000039999200000000000000000000000000"), + 100005: MustNewDecFromStr("0.000049998800000000000000000000000000"), + 100006: MustNewDecFromStr("0.000059998200000000000000000000000000"), + 100007: MustNewDecFromStr("0.000069997600000000000000000000000000"), + 100008: MustNewDecFromStr("0.000079996800000000000000000000000000"), + 100009: MustNewDecFromStr("0.000089996000000000000000000000000000"), } } @@ -882,8 +883,7 @@ func DecApproxEq(t *testing.T, d1 BigDec, d2 BigDec, tol BigDec) (*testing.T, bo } // ApproxLog2 returns the approximation of log_2 {x}. -// Rounds down by truncating and right shifting during -// calculations. +// Rounds down by truncations during division and right shifting. func (x BigDec) ApproxLog2() BigDec { if x.LT(OneDec()) { panic(fmt.Sprintf("only supporting values >= 1, given (%s)", x)) @@ -909,16 +909,25 @@ func (x BigDec) ApproxLog2() BigDec { y = y + 1 } + // invariant: x < 1.0001 + // while x >= 1.0001 + z := int64(0) + upperBound := NewDecWithPrec(10001, 4) + for x.GTE(upperBound) { + z = z + 1 + x = x.Quo(upperBound) + } + // exponentiate to simplify truncation necessary for // looking up values in the table. - lookupKey := x.MulInt64(10).TruncateInt() - if lookupKey.GTE(NewInt(20)) || lookupKey.LT(NewInt(10)) { - panic(fmt.Sprintf("invalid lookup key (%s), must be 10 <= lookup key < 2", lookupKey)) + lookupKey := x.MulInt64(100000).TruncateInt() + if lookupKey.GTE(NewInt(100010)) || lookupKey.LT(NewInt(100000)) { + panic(fmt.Sprintf("invalid lookup key (%s), must be 100000 <= lookup key < 100001", lookupKey)) } - tableValue, found := log2LookupTable[uint8(lookupKey.Int64())] + tableValue, found := log2LookupTable[uint32(lookupKey.Int64())] if !found { panic(fmt.Sprintf("no matching value for key (%s) in the lookup table", lookupKey)) } - return NewBigDec(y).Add(tableValue) + return NewBigDec(y).Add(upperBoundLog.MulInt64(z)).Add(tableValue) } diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index 92b886d1d9e..3ec6cc7a954 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -204,8 +204,8 @@ func (s *decimalTestSuite) TestBigDecFromSdkDec() { func (s *decimalTestSuite) TestBigDecFromSdkDecSlice() { tests := []struct { - d []sdk.Dec - want []BigDec + d []sdk.Dec + want []BigDec expPanic bool }{ {[]sdk.Dec{sdk.MustNewDecFromStr("0.000000000000000000")}, []BigDec{NewBigDec(0)}, false}, @@ -674,15 +674,19 @@ func (s *decimalTestSuite) TestLog2() { initialValue: NewBigDec(2), expected: NewBigDec(1), }, + "log_2{4} = 1": { + initialValue: NewBigDec(7), + // TODO: need taylor expansion. True value: 2.80735492205760410744196931723183080 + expected: MustNewDecFromStr("2.807331780165036836432756721656406796"), + }, "log_2{512} = 9": { initialValue: NewBigDec(512), expected: NewBigDec(9), }, "log_2{600} = 9": { initialValue: NewBigDec(580), - // TODO: true value is: 9.179909090014934468590092754117374938 - // Need better lookup table. - expected: MustNewDecFromStr("9.137503523749934908329043617236402782"), + // TODO: need taylor expansion. True value is: 9.179909090014934468590092754117374938 + expected: MustNewDecFromStr("9.179895076994889373665412371677187147"), }, "log_2{1024} = 10": { initialValue: NewBigDec(1024), @@ -690,15 +694,13 @@ func (s *decimalTestSuite) TestLog2() { }, "log_2{1024.987654321} = 10": { initialValue: NewDecWithPrec(1024987654321, 9), - // TODO: true value is: 10.001390817654141324352719749259888355 - // Need better lookup table - expected: MustNewDecFromStr("10.000000000000000000000000000000000000"), + // TODO: need taylor expansion. True value is: 10.001390817654141324352719749259888355 + expected: MustNewDecFromStr("10.001358358819850845519638100517317309"), }, "log_2{912648174127941279170121098210.92821920190204131121} = 99.525973560175362367047484597337715868": { initialValue: MustNewDecFromStr("912648174127941279170121098210.92821920190204131121"), - // TODO: true value is: 99.525973560175362367047484597337715868 - // Need better lookup table - expected: MustNewDecFromStr("99.485426827170241759571649887742440632"), + // TODO: need taylor expansion. True value is: 99.525973560175362367047484597337715868 + expected: MustNewDecFromStr("99.525926047039592435453430709513510145"), }, } diff --git a/osmomath/log2_bench_test.go b/osmomath/log2_bench_test.go index 1a723be1643..75389769102 100644 --- a/osmomath/log2_bench_test.go +++ b/osmomath/log2_bench_test.go @@ -4,6 +4,8 @@ import ( "testing" ) +// Benchmark that scales x between 1 <= x < 2 - 417597 ns/op +// Benchmark that scales x between 1 <= x < 1.0001 - 3372629 ns/op func BenchmarkLog2(b *testing.B) { tests := []struct { value BigDec From e7eb2b3714dad30fbcceaf3e91043cffa27e5663 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 22 Oct 2022 03:11:59 -0400 Subject: [PATCH 07/14] comment --- osmomath/decimal.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index 9ae08ad2e8e..b1b869bb153 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -909,6 +909,8 @@ func (x BigDec) ApproxLog2() BigDec { y = y + 1 } + // Normalize x to be 1 <= x < 1.0001 + // invariant: x < 1.0001 // while x >= 1.0001 z := int64(0) From 66a6c53e9e2fc7c1e951190c307658e4d21f02dc Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 22 Oct 2022 21:36:58 -0400 Subject: [PATCH 08/14] implement and test precise log for x >= 1 --- osmomath/decimal.go | 86 +++++++++++++++---------------------- osmomath/decimal_test.go | 27 ++++++------ osmomath/log2_bench_test.go | 7 +-- 3 files changed, 52 insertions(+), 68 deletions(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index b1b869bb153..8937d930152 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -30,6 +30,9 @@ const ( // max number of iterations in ApproxRoot function maxApproxRootIterations = 100 + + // max number of iterations in Log2 function + maxLog2Iterations = 50 ) var ( @@ -40,8 +43,9 @@ var ( oneInt = big.NewInt(1) tenInt = big.NewInt(10) - log2LookupTable map[uint32]BigDec - upperBoundLog = MustNewDecFromStr("0.000144262291094538391070900057479701") + // initialized in init() since requires + // precision to be defined. + twoBigDec BigDec ) // Decimal errors @@ -58,26 +62,7 @@ func init() { precisionMultipliers[i] = calcPrecisionMultiplier(int64(i)) } - log2LookupTable = buildLog2LookupTable() -} - -// buildLog2LookupTable returns a lookup table for log values -// ranging from [1, 2) -// the keys are multiplied by 10 to simplify the rounding logic -// in the log function -func buildLog2LookupTable() map[uint32]BigDec { - return map[uint32]BigDec{ - 100000: ZeroDec(), - 100001: MustNewDecFromStr("0.000014426878274461848365683118054200"), - 100002: MustNewDecFromStr("0.000019999800000000000000000000000000"), - 100003: MustNewDecFromStr("0.000029999600000000000000000000000000"), - 100004: MustNewDecFromStr("0.000039999200000000000000000000000000"), - 100005: MustNewDecFromStr("0.000049998800000000000000000000000000"), - 100006: MustNewDecFromStr("0.000059998200000000000000000000000000"), - 100007: MustNewDecFromStr("0.000069997600000000000000000000000000"), - 100008: MustNewDecFromStr("0.000079996800000000000000000000000000"), - 100009: MustNewDecFromStr("0.000089996000000000000000000000000000"), - } + twoBigDec = NewBigDec(2) } func precisionInt() *big.Int { @@ -882,9 +867,12 @@ func DecApproxEq(t *testing.T, d1 BigDec, d2 BigDec, tol BigDec) (*testing.T, bo return t, diff.LTE(tol), "expected |d1 - d2| <:\t%v\ngot |d1 - d2| = \t\t%v", tol.String(), diff.String() } -// ApproxLog2 returns the approximation of log_2 {x}. +// LogBase2 returns log_2 {x}. // Rounds down by truncations during division and right shifting. -func (x BigDec) ApproxLog2() BigDec { +// Accurate up to 32 precision digits. +// Implementation is based on: +// https://stm32duinoforum.com/forum/dsp/BinaryLogarithm.pdf +func (x BigDec) LogBase2() BigDec { if x.LT(OneDec()) { panic(fmt.Sprintf("only supporting values >= 1, given (%s)", x)) } @@ -892,44 +880,38 @@ func (x BigDec) ApproxLog2() BigDec { // Normalize x to be 1 <= x < 2 // y is the exponent that results in a whole multiple of 2. - y := int64(0) + y := ZeroDec() // invariant: x >= 1 // while x < 1 for x.LT(OneDec()) { x.i = x.i.Lsh(x.i, 1) - y = y - 1 + y = y.Sub(OneDec()) } // invariant: x < 2 // while x >= 2 - twoDec := NewBigDec(2) - for x.GTE(twoDec) { + for x.GTE(twoBigDec) { x.i = x.i.Rsh(x.i, 1) - y = y + 1 - } - - // Normalize x to be 1 <= x < 1.0001 - - // invariant: x < 1.0001 - // while x >= 1.0001 - z := int64(0) - upperBound := NewDecWithPrec(10001, 4) - for x.GTE(upperBound) { - z = z + 1 - x = x.Quo(upperBound) - } - - // exponentiate to simplify truncation necessary for - // looking up values in the table. - lookupKey := x.MulInt64(100000).TruncateInt() - if lookupKey.GTE(NewInt(100010)) || lookupKey.LT(NewInt(100000)) { - panic(fmt.Sprintf("invalid lookup key (%s), must be 100000 <= lookup key < 100001", lookupKey)) + y = y.Add(OneDec()) + } + + b := OneDec().Quo(twoBigDec) + + // N.B. At this point x is a positive real number representing + // mantissa of the log. We estimate it using the following + // algorithm: + // https://stm32duinoforum.com/forum/dsp/BinaryLogarithm.pdf + // This has shown precision of 32 digits relative + // to Wolfram Alpha in tests + for i := 0; i < maxLog2Iterations; i++ { + x = x.Mul(x) + if x.GTE(twoBigDec) { + x.i = x.i.Rsh(x.i, 1) + y = y.Add(b) + } + b.i = b.i.Rsh(b.i, 1) } - tableValue, found := log2LookupTable[uint32(lookupKey.Int64())] - if !found { - panic(fmt.Sprintf("no matching value for key (%s) in the lookup table", lookupKey)) - } - return NewBigDec(y).Add(upperBoundLog.MulInt64(z)).Add(tableValue) + return y } diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index 3ec6cc7a954..a92739f67e1 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -654,6 +654,7 @@ func BenchmarkMarshalTo(b *testing.B) { } func (s *decimalTestSuite) TestLog2() { + var expectedErrTolerance = MustNewDecFromStr("0.000000000000000000000000000000000100") tests := map[string]struct { initialValue BigDec @@ -674,41 +675,41 @@ func (s *decimalTestSuite) TestLog2() { initialValue: NewBigDec(2), expected: NewBigDec(1), }, - "log_2{4} = 1": { + "log_2{7} = 2.807354922057604107441969317231830809": { initialValue: NewBigDec(7), - // TODO: need taylor expansion. True value: 2.80735492205760410744196931723183080 - expected: MustNewDecFromStr("2.807331780165036836432756721656406796"), + // From: https://www.wolframalpha.com/input?i=log+base+2+of+7+37+digits + expected: MustNewDecFromStr("2.807354922057604107441969317231830809"), }, "log_2{512} = 9": { initialValue: NewBigDec(512), expected: NewBigDec(9), }, - "log_2{600} = 9": { + "log_2{580} = 9.179909090014934468590092754117374938": { initialValue: NewBigDec(580), - // TODO: need taylor expansion. True value is: 9.179909090014934468590092754117374938 - expected: MustNewDecFromStr("9.179895076994889373665412371677187147"), + // From: https://www.wolframalpha.com/input?i=log+base+2+of+600+37+digits + expected: MustNewDecFromStr("9.179909090014934468590092754117374938"), }, "log_2{1024} = 10": { initialValue: NewBigDec(1024), expected: NewBigDec(10), }, - "log_2{1024.987654321} = 10": { + "log_2{1024.987654321} = 10.001390817654141324352719749259888355": { initialValue: NewDecWithPrec(1024987654321, 9), - // TODO: need taylor expansion. True value is: 10.001390817654141324352719749259888355 - expected: MustNewDecFromStr("10.001358358819850845519638100517317309"), + // From: https://www.wolframalpha.com/input?i=log+base+2+of+1024.987654321+38+digits + expected: MustNewDecFromStr("10.001390817654141324352719749259888355"), }, "log_2{912648174127941279170121098210.92821920190204131121} = 99.525973560175362367047484597337715868": { initialValue: MustNewDecFromStr("912648174127941279170121098210.92821920190204131121"), - // TODO: need taylor expansion. True value is: 99.525973560175362367047484597337715868 - expected: MustNewDecFromStr("99.525926047039592435453430709513510145"), + // From: https://www.wolframalpha.com/input?i=log+base+2+of+912648174127941279170121098210.92821920190204131121+38+digits + expected: MustNewDecFromStr("99.525973560175362367047484597337715868"), }, } for name, tc := range tests { s.Run(name, func() { osmoassert.ConditionalPanic(s.T(), tc.expectedPanic, func() { - res := tc.initialValue.ApproxLog2() - s.Require().Equal(tc.expected, res) + res := tc.initialValue.LogBase2() + DecApproxEq(s.T(), tc.expected, res, expectedErrTolerance) }) }) } diff --git a/osmomath/log2_bench_test.go b/osmomath/log2_bench_test.go index 75389769102..045f6862e7d 100644 --- a/osmomath/log2_bench_test.go +++ b/osmomath/log2_bench_test.go @@ -4,8 +4,9 @@ import ( "testing" ) -// Benchmark that scales x between 1 <= x < 2 - 417597 ns/op -// Benchmark that scales x between 1 <= x < 1.0001 - 3372629 ns/op +// Benchmark that scales x between 1 <= x < 2 + lookup table - 417597 ns/op +// Benchmark that scales x between 1 <= x < 1.0001 + lookup table - 3372629 ns/op +// Benchmark that sscales x between 1 <= x < 2 + mantissa calcuation - 75088 ns/op func BenchmarkLog2(b *testing.B) { tests := []struct { value BigDec @@ -30,7 +31,7 @@ func BenchmarkLog2(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range tests { - test.value.ApproxLog2() + test.value.LogBase2() } } } From bfc5c16dee390ca77ebcb2911fe838c4abdc164d Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 22 Oct 2022 22:16:09 -0400 Subject: [PATCH 09/14] tests for negative values --- osmomath/decimal.go | 6 +++--- osmomath/decimal_test.go | 26 ++++++++++++++++++++++---- osmomath/rounding_direction_test.go | 1 - 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index 8937d930152..2a3ac3730df 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -32,7 +32,7 @@ const ( maxApproxRootIterations = 100 // max number of iterations in Log2 function - maxLog2Iterations = 50 + maxLog2Iterations = 300 ) var ( @@ -873,8 +873,8 @@ func DecApproxEq(t *testing.T, d1 BigDec, d2 BigDec, tol BigDec) (*testing.T, bo // Implementation is based on: // https://stm32duinoforum.com/forum/dsp/BinaryLogarithm.pdf func (x BigDec) LogBase2() BigDec { - if x.LT(OneDec()) { - panic(fmt.Sprintf("only supporting values >= 1, given (%s)", x)) + if x.LTE(ZeroDec()) { + panic(fmt.Sprintf("log is not defined at <= 0, given (%s)", x)) } // Normalize x to be 1 <= x < 2 diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index a92739f67e1..9093c283514 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -662,11 +662,29 @@ func (s *decimalTestSuite) TestLog2() { expectedPanic bool }{ - "log_2{0.99}; not supported; panic": { - initialValue: NewDecWithPrec(99, 2), - + "log_2{-1}; invalid; panic": { + initialValue: OneDec().Neg(), + expectedPanic: true, + }, + "log_2{0}; invalid; panic": { + initialValue: ZeroDec(), expectedPanic: true, }, + "log_2{0.001} = -9.965784284662087043610958288468170528": { + initialValue: MustNewDecFromStr("0.001"), + // From: https://www.wolframalpha.com/input?i=log+base+2+of+0.999912345+with+33+digits + expected: MustNewDecFromStr("-9.965784284662087043610958288468170528"), + }, + "log_2{0.56171821941421412902170941} = -0.832081497183140708984033250637831402": { + initialValue: MustNewDecFromStr("0.56171821941421412902170941"), + // From: https://www.wolframalpha.com/input?i=log+base+2+of+0.56171821941421412902170941+with+36+digits + expected: MustNewDecFromStr("-0.832081497183140708984033250637831402"), + }, + "log_2{0.999912345} = -0.000126464976533858080645902722235833": { + initialValue: MustNewDecFromStr("0.999912345"), + // From: https://www.wolframalpha.com/input?i=log+base+2+of+0.999912345+with+37+digits + expected: MustNewDecFromStr("-0.000126464976533858080645902722235833"), + }, "log_2{1} = 0": { initialValue: NewBigDec(1), expected: NewBigDec(0), @@ -709,7 +727,7 @@ func (s *decimalTestSuite) TestLog2() { s.Run(name, func() { osmoassert.ConditionalPanic(s.T(), tc.expectedPanic, func() { res := tc.initialValue.LogBase2() - DecApproxEq(s.T(), tc.expected, res, expectedErrTolerance) + require.True(DecApproxEq(s.T(), tc.expected, res, expectedErrTolerance)) }) }) } diff --git a/osmomath/rounding_direction_test.go b/osmomath/rounding_direction_test.go index 8668c84c677..3fac4b62c9d 100644 --- a/osmomath/rounding_direction_test.go +++ b/osmomath/rounding_direction_test.go @@ -42,7 +42,6 @@ func TestDivIntByU64ToBigDec(t *testing.T) { addTCForAllRoundingModes("odd divided by 2", sdk.NewInt(5), 2, NewDecWithPrec(25, 1)) for name, tt := range tests { - fmt.Println("start") t.Run(name, func(t *testing.T) { got, err := DivIntByU64ToBigDec(tt.i, tt.u, tt.round) require.Equal(t, tt.want, got) From d6324f4867570af2c42910c64ec1f41bfcaf82bd Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 23 Oct 2022 03:28:55 -0400 Subject: [PATCH 10/14] make non-mutative and test --- osmomath/decimal.go | 22 +++++++++++++--------- osmomath/decimal_test.go | 7 +++++++ osmomath/log2_bench_test.go | 37 ++++++++++++------------------------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index 2a3ac3730df..77dfb3115c1 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -873,8 +873,12 @@ func DecApproxEq(t *testing.T, d1 BigDec, d2 BigDec, tol BigDec) (*testing.T, bo // Implementation is based on: // https://stm32duinoforum.com/forum/dsp/BinaryLogarithm.pdf func (x BigDec) LogBase2() BigDec { - if x.LTE(ZeroDec()) { - panic(fmt.Sprintf("log is not defined at <= 0, given (%s)", x)) + // create a new decimal to avoid mutating + // the receiver's int buffer. + xCopy := ZeroDec() + xCopy.i = new(big.Int).Set(x.i) + if xCopy.LTE(ZeroDec()) { + panic(fmt.Sprintf("log is not defined at <= 0, given (%s)", xCopy)) } // Normalize x to be 1 <= x < 2 @@ -884,15 +888,15 @@ func (x BigDec) LogBase2() BigDec { // invariant: x >= 1 // while x < 1 - for x.LT(OneDec()) { - x.i = x.i.Lsh(x.i, 1) + for xCopy.LT(OneDec()) { + xCopy.i = xCopy.i.Lsh(xCopy.i, 1) y = y.Sub(OneDec()) } // invariant: x < 2 // while x >= 2 - for x.GTE(twoBigDec) { - x.i = x.i.Rsh(x.i, 1) + for xCopy.GTE(twoBigDec) { + xCopy.i = xCopy.i.Rsh(xCopy.i, 1) y = y.Add(OneDec()) } @@ -905,9 +909,9 @@ func (x BigDec) LogBase2() BigDec { // This has shown precision of 32 digits relative // to Wolfram Alpha in tests for i := 0; i < maxLog2Iterations; i++ { - x = x.Mul(x) - if x.GTE(twoBigDec) { - x.i = x.i.Rsh(x.i, 1) + xCopy = xCopy.Mul(xCopy) + if xCopy.GTE(twoBigDec) { + xCopy.i = xCopy.i.Rsh(xCopy.i, 1) y = y.Add(b) } b.i = b.i.Rsh(b.i, 1) diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index 9093c283514..cabf1409c87 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -726,8 +726,15 @@ func (s *decimalTestSuite) TestLog2() { for name, tc := range tests { s.Run(name, func() { osmoassert.ConditionalPanic(s.T(), tc.expectedPanic, func() { + // Create a copy to test that the original was not modified. + // That is, that LogbBase2() is non-mutative. + initialCopy := ZeroDec() + initialCopy.i.Set(tc.initialValue.i) + + // system under test. res := tc.initialValue.LogBase2() require.True(DecApproxEq(s.T(), tc.expected, res, expectedErrTolerance)) + require.Equal(s.T(), initialCopy, tc.initialValue) }) }) } diff --git a/osmomath/log2_bench_test.go b/osmomath/log2_bench_test.go index 045f6862e7d..c1e852594e4 100644 --- a/osmomath/log2_bench_test.go +++ b/osmomath/log2_bench_test.go @@ -1,37 +1,24 @@ package osmomath import ( + "math/rand" "testing" ) -// Benchmark that scales x between 1 <= x < 2 + lookup table - 417597 ns/op -// Benchmark that scales x between 1 <= x < 1.0001 + lookup table - 3372629 ns/op -// Benchmark that sscales x between 1 <= x < 2 + mantissa calcuation - 75088 ns/op func BenchmarkLog2(b *testing.B) { - tests := []struct { - value BigDec - }{ - // TODO: Choose selection here more robustly - { - value: MustNewDecFromStr("1.2"), - }, - { - value: MustNewDecFromStr("1.234"), - }, - { - value: MustNewDecFromStr("1024"), - }, - { - value: NewBigDec(2048 * 2048 * 2048 * 2048 * 2048), - }, - { - value: MustNewDecFromStr("999999999999999999999999999999999999999999999999999999.9122181273612911"), - }, + tests := []BigDec{ + MustNewDecFromStr("1.2"), + MustNewDecFromStr("1.234"), + MustNewDecFromStr("1024"), + NewBigDec(2048 * 2048 * 2048 * 2048 * 2048), + MustNewDecFromStr("999999999999999999999999999999999999999999999999999999.9122181273612911"), + MustNewDecFromStr("0.563289239121902491248219047129047129"), } for i := 0; i < b.N; i++ { - for _, test := range tests { - test.value.LogBase2() - } + b.StopTimer() + test := tests[rand.Int63n(int64(len(tests)))] + b.StartTimer() + test.LogBase2() } } From 003b0c178dfe7431b7f303bdcb0aa83d87e27251 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 23 Oct 2022 03:46:43 -0400 Subject: [PATCH 11/14] bench --- osmomath/log2_bench_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osmomath/log2_bench_test.go b/osmomath/log2_bench_test.go index c1e852594e4..9b4ab3c3358 100644 --- a/osmomath/log2_bench_test.go +++ b/osmomath/log2_bench_test.go @@ -19,6 +19,6 @@ func BenchmarkLog2(b *testing.B) { b.StopTimer() test := tests[rand.Int63n(int64(len(tests)))] b.StartTimer() - test.LogBase2() + _ = test.LogBase2() } } From d09e451816e0eb4935b09e5aeb7e7c89a240f15e Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 23 Oct 2022 03:51:53 -0400 Subject: [PATCH 12/14] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38fcdbd2794..ee35bcc74d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +* [#2788](https://github.com/osmosis-labs/osmosis/pull/2788) Add logarithm base 2 implementation. * [#2739](https://github.com/osmosis-labs/osmosis/pull/2739) Add pool type query ### Bug fixes From cdbc726291355994ef51a86b670de7be71401f8c Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 24 Oct 2022 00:51:38 -0400 Subject: [PATCH 13/14] remove redundant assignments --- osmomath/decimal.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index 77dfb3115c1..f635bd60910 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -889,14 +889,14 @@ func (x BigDec) LogBase2() BigDec { // invariant: x >= 1 // while x < 1 for xCopy.LT(OneDec()) { - xCopy.i = xCopy.i.Lsh(xCopy.i, 1) + xCopy.i.Lsh(xCopy.i, 1) y = y.Sub(OneDec()) } // invariant: x < 2 // while x >= 2 for xCopy.GTE(twoBigDec) { - xCopy.i = xCopy.i.Rsh(xCopy.i, 1) + xCopy.i.Rsh(xCopy.i, 1) y = y.Add(OneDec()) } @@ -911,10 +911,10 @@ func (x BigDec) LogBase2() BigDec { for i := 0; i < maxLog2Iterations; i++ { xCopy = xCopy.Mul(xCopy) if xCopy.GTE(twoBigDec) { - xCopy.i = xCopy.i.Rsh(xCopy.i, 1) + xCopy.i.Rsh(xCopy.i, 1) y = y.Add(b) } - b.i = b.i.Rsh(b.i, 1) + b.i.Rsh(b.i, 1) } return y From 5aaa278afb888aa1c7c333f0d60bbca92d8b0a97 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 24 Oct 2022 00:53:56 -0400 Subject: [PATCH 14/14] improve comments --- osmomath/decimal.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osmomath/decimal.go b/osmomath/decimal.go index f635bd60910..38172998876 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -881,20 +881,18 @@ func (x BigDec) LogBase2() BigDec { panic(fmt.Sprintf("log is not defined at <= 0, given (%s)", xCopy)) } - // Normalize x to be 1 <= x < 2 + // Normalize x to be 1 <= x < 2. // y is the exponent that results in a whole multiple of 2. y := ZeroDec() - // invariant: x >= 1 - // while x < 1 + // repeat until: x >= 1. for xCopy.LT(OneDec()) { xCopy.i.Lsh(xCopy.i, 1) y = y.Sub(OneDec()) } - // invariant: x < 2 - // while x >= 2 + // repeat until: x < 2. for xCopy.GTE(twoBigDec) { xCopy.i.Rsh(xCopy.i, 1) y = y.Add(OneDec()) @@ -907,7 +905,7 @@ func (x BigDec) LogBase2() BigDec { // algorithm: // https://stm32duinoforum.com/forum/dsp/BinaryLogarithm.pdf // This has shown precision of 32 digits relative - // to Wolfram Alpha in tests + // to Wolfram Alpha in tests. for i := 0; i < maxLog2Iterations; i++ { xCopy = xCopy.Mul(xCopy) if xCopy.GTE(twoBigDec) {