From b13caced863da1b72ba14afb71c4313c8390af5d Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 3 Dec 2019 13:43:57 -0500 Subject: [PATCH 1/6] SortableDecBytes --- types/decimal.go | 22 ++++++++++++++++++++++ types/decimal_test.go | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/types/decimal.go b/types/decimal.go index 03934e2145ab..f226d0c25013 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -551,6 +551,28 @@ func (d Dec) Ceil() Dec { //___________________________________________________________________________________ +// Ensures that an sdk.Dec is within the sortable bounds +// Dec can't have precision of less that 10^-18 +func ValidSortableDec(dec Dec) bool { + return dec.LTE(OneDec().Quo(SmallestDec())) +} + +// FormattingStringSortableBytes is the string used in Sprintf to left and right pad the sdk.Dec +// It adjusts based on the Precision constant +var FormattingStringSortableBytes = fmt.Sprintf("%%0%ds", Precision*2+1) + +// Returns a byte slice representation of an sdk.Dec that can be sorted. +// Left and right pads with 0s so there are 18 digits to left and right of decimal point +// For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec +func SortableDecBytes(dec Dec) []byte { + if !ValidSortableDec(dec) { + panic("dec must be within bounds") + } + return []byte(fmt.Sprintf(FormattingStringSortableBytes, dec.String())) +} + +//___________________________________________________________________________________ + // reuse nil values var ( nilAmino string diff --git a/types/decimal_test.go b/types/decimal_test.go index e51c48fc7fee..67353870813c 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -443,3 +443,22 @@ func TestApproxSqrt(t *testing.T) { require.Equal(t, tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) } } + +func TestDecSortableBytes(t *testing.T) { + tests := []struct { + d Dec + want []byte + }{ + {NewDec(0), []byte("000000000000000000.000000000000000000")}, + {NewDec(1), []byte("000000000000000001.000000000000000000")}, + {NewDec(10), []byte("000000000000000010.000000000000000000")}, + {NewDec(12340), []byte("000000000000012340.000000000000000000")}, + {NewDecWithPrec(12340, 4), []byte("000000000000000001.234000000000000000")}, + {NewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")}, + {NewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")}, + {NewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")}, + } + for tcIndex, tc := range tests { + assert.Equal(t, tc.want, SortableDecBytes(tc.d), "bad String(), index: %v", tcIndex) + } +} From 7b3f4dc465421f9f978dbce6ea50c8b7edc331e7 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 3 Dec 2019 13:54:19 -0500 Subject: [PATCH 2/6] typos --- types/decimal.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/decimal.go b/types/decimal.go index f226d0c25013..c4496ea631d7 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -552,7 +552,8 @@ func (d Dec) Ceil() Dec { //___________________________________________________________________________________ // Ensures that an sdk.Dec is within the sortable bounds -// Dec can't have precision of less that 10^-18 +// Dec can't have precision of less than 10^-18 +// Max sortable decimal was set to the reciprocal of SmallestDec func ValidSortableDec(dec Dec) bool { return dec.LTE(OneDec().Quo(SmallestDec())) } From aa97189b661a47f6be9b7e499844519be7f8ccf9 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 3 Dec 2019 17:11:57 -0500 Subject: [PATCH 3/6] Apply suggestions from code review Co-Authored-By: Jack Zampolin --- types/decimal.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/types/decimal.go b/types/decimal.go index c4496ea631d7..aa07c334b706 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -560,7 +560,6 @@ func ValidSortableDec(dec Dec) bool { // FormattingStringSortableBytes is the string used in Sprintf to left and right pad the sdk.Dec // It adjusts based on the Precision constant -var FormattingStringSortableBytes = fmt.Sprintf("%%0%ds", Precision*2+1) // Returns a byte slice representation of an sdk.Dec that can be sorted. // Left and right pads with 0s so there are 18 digits to left and right of decimal point @@ -569,7 +568,7 @@ func SortableDecBytes(dec Dec) []byte { if !ValidSortableDec(dec) { panic("dec must be within bounds") } - return []byte(fmt.Sprintf(FormattingStringSortableBytes, dec.String())) + return []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", Precision*2+1), dec.String())) } //___________________________________________________________________________________ From 5cf673b97a527faea289f3ed39541db1b03d6b2b Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 3 Dec 2019 17:13:29 -0500 Subject: [PATCH 4/6] removed extra comments --- types/decimal.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/types/decimal.go b/types/decimal.go index aa07c334b706..4d736d9eae4c 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -558,9 +558,6 @@ func ValidSortableDec(dec Dec) bool { return dec.LTE(OneDec().Quo(SmallestDec())) } -// FormattingStringSortableBytes is the string used in Sprintf to left and right pad the sdk.Dec -// It adjusts based on the Precision constant - // Returns a byte slice representation of an sdk.Dec that can be sorted. // Left and right pads with 0s so there are 18 digits to left and right of decimal point // For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec From eed55108cfcd54341e56b45306c572c9bb508d33 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Wed, 4 Dec 2019 11:22:42 -0500 Subject: [PATCH 5/6] Apply suggestions from code review Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- types/decimal.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/types/decimal.go b/types/decimal.go index 4d736d9eae4c..b2a28f9aa48d 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -551,16 +551,16 @@ func (d Dec) Ceil() Dec { //___________________________________________________________________________________ -// Ensures that an sdk.Dec is within the sortable bounds -// Dec can't have precision of less than 10^-18 -// Max sortable decimal was set to the reciprocal of SmallestDec +// ValidSortableDec ensures that a Dec is within the sortable bounds, +// a Dec can't have a precision of less than 10^-18. +// Max sortable decimal was set to the reciprocal of SmallestDec. func ValidSortableDec(dec Dec) bool { return dec.LTE(OneDec().Quo(SmallestDec())) } -// Returns a byte slice representation of an sdk.Dec that can be sorted. -// Left and right pads with 0s so there are 18 digits to left and right of decimal point -// For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec +// SortableDecBytes returns a byte slice representation of a Dec that can be sorted. +// Left and right pads with 0s so there are 18 digits to left and right of the decimal point. +// For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec. func SortableDecBytes(dec Dec) []byte { if !ValidSortableDec(dec) { panic("dec must be within bounds") From 2c757d92a194aeb84a0ddd4a63c07dcb56584d36 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Wed, 4 Dec 2019 17:39:42 -0500 Subject: [PATCH 6/6] address @karzak review --- types/decimal.go | 19 ++++++++++++++++++- types/decimal_test.go | 6 ++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/types/decimal.go b/types/decimal.go index b2a28f9aa48d..b066c3e43374 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -551,11 +551,15 @@ func (d Dec) Ceil() Dec { //___________________________________________________________________________________ +// MaxSortableDec is the largest Dec that can be passed into SortableDecBytes() +// Its negative form is the least Dec that can be passed in. +var MaxSortableDec = OneDec().Quo(SmallestDec()) + // ValidSortableDec ensures that a Dec is within the sortable bounds, // a Dec can't have a precision of less than 10^-18. // Max sortable decimal was set to the reciprocal of SmallestDec. func ValidSortableDec(dec Dec) bool { - return dec.LTE(OneDec().Quo(SmallestDec())) + return dec.Abs().LTE(MaxSortableDec) } // SortableDecBytes returns a byte slice representation of a Dec that can be sorted. @@ -565,6 +569,19 @@ func SortableDecBytes(dec Dec) []byte { if !ValidSortableDec(dec) { panic("dec must be within bounds") } + // Instead of adding an extra byte to all sortable decs in order to handle max sortable, we just + // makes its bytes be "max" which comes after all numbers in ASCIIbetical order + if dec.Equal(MaxSortableDec) { + return []byte("max") + } + // For the same reason, we make the bytes of minimum sortable dec be --, which comes before all numbers. + if dec.Equal(MaxSortableDec.Neg()) { + return []byte("--") + } + // We move the negative sign to the front of all the left padded 0s, to make negative numbers come before positive numbers + if dec.IsNegative() { + return append([]byte("-"), []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", Precision*2+1), dec.Abs().String()))...) + } return []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", Precision*2+1), dec.String())) } diff --git a/types/decimal_test.go b/types/decimal_test.go index 67353870813c..ea97a1029a5a 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -457,8 +457,14 @@ func TestDecSortableBytes(t *testing.T) { {NewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")}, {NewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")}, {NewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")}, + {NewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000010.090090090090090090")}, + {NewDec(1000000000000000000), []byte("max")}, + {NewDec(-1000000000000000000), []byte("--")}, } for tcIndex, tc := range tests { assert.Equal(t, tc.want, SortableDecBytes(tc.d), "bad String(), index: %v", tcIndex) } + + assert.Panics(t, func() { SortableDecBytes(NewDec(1000000000000000001)) }) + assert.Panics(t, func() { SortableDecBytes(NewDec(-1000000000000000001)) }) }