Skip to content

Commit

Permalink
Merge PR cosmos#5360: Sortable Decimal Bytes
Browse files Browse the repository at this point in the history
  • Loading branch information
sunnya97 authored and alexanderbez committed Dec 5, 2019
1 parent 0300a6f commit 3c2ace9
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 0 deletions.
36 changes: 36 additions & 0 deletions types/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,42 @@ 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.Abs().LTE(MaxSortableDec)
}

// 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")
}
// 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()))
}

//___________________________________________________________________________________

// reuse nil values
var (
nilAmino string
Expand Down
25 changes: 25 additions & 0 deletions types/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,28 @@ 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")},
{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)) })
}

0 comments on commit 3c2ace9

Please sign in to comment.