Skip to content

Commit

Permalink
fix: zscore return 0 when double value is too small (#344)
Browse files Browse the repository at this point in the history
* fix: zscore return 0 when double value is too small

* "good enough" float-to-string conversion

---------

Co-authored-by: Harmen <[email protected]>
  • Loading branch information
zsh1995 and alicebob authored Oct 4, 2023
1 parent 7b7a51c commit aa376e7
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 17 deletions.
6 changes: 6 additions & 0 deletions cmd_sorted_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1089,9 +1089,15 @@ func TestSortedSetScore(t *testing.T) {
{
s.ZAdd("z2", 1, "one")
s.ZAdd("z2", 2, "two")
s.ZAdd("z2", 0.000000000000000000000000000000000000000000000000000000000000000025339988685347402, "small")
score, err := s.ZScore("z2", "two")
ok(t, err)
equals(t, 2.0, score)

score, err = s.ZScore("z2", "small")
ok(t, err)
equals(t, 0.000000000000000000000000000000000000000000000000000000000000000025339988685347402, score)

}

t.Run("errors", func(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions integration/sorted_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ func TestSortedSet(t *testing.T) {
c.Do("ZRANGEBYLEX", "z", "[a", "[z")
c.Do("ZRANGE", "z", "0", "-1", "WITHSCORES")
})

testRaw(t, func(c *client) {
// very small values
c.Do("ZADD", "a_zset", "1.2", "one")
c.Do("ZADD", "a_zset", "incr", "1.2", "one")
c.DoRounded(1, "ZADD", "a_zset", "incr", "1.2", "one") // real: 3.5999999999999996, mini: 3.6
c.Do("ZADD", "a_zset", "incr", "1.2", "one")
})
}

func TestSortedSetAdd(t *testing.T) {
Expand Down
20 changes: 3 additions & 17 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"math"
"net"
"strconv"
"strings"
"sync"
"unicode"
Expand Down Expand Up @@ -478,28 +479,13 @@ func (w *Writer) Flush() {
}

// formatFloat formats a float the way redis does (sort-of)
// Redis uses a method called "grisu2", which gives slightly different output.
func formatFloat(v float64) string {
if math.IsInf(v, 1) {
return "inf"
}
if math.IsInf(v, -1) {
return "-inf"
}
return stripZeros(fmt.Sprintf("%.12f", v))
}

func stripZeros(sv string) string {
for strings.Contains(sv, ".") {
if sv[len(sv)-1] != '0' {
break
}
// Remove trailing 0s.
sv = sv[:len(sv)-1]
// Ends with a '.'.
if sv[len(sv)-1] == '.' {
sv = sv[:len(sv)-1]
break
}
}
return sv
return strconv.FormatFloat(v, 'g', 16, 64)
}
38 changes: 38 additions & 0 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,41 @@ func TestTLS(t *testing.T) {
t.Errorf("have: %s, want: %s", have, want)
}
}

func TestFormatFloat(t *testing.T) {
eq := func(t *testing.T, want string, n float64) {
t.Helper()
have := formatFloat(n)
if have != want {
t.Errorf("have: %s, want: %s", have, want)
}
}

eq(t, "1", 1.0)
eq(t, "0.0001", 0.0001) // checked
eq(t, "1.1", 1.1)
eq(t, "1.01", 1.01)
eq(t, "1.001", 1.001)
eq(t, "1.0001", 1.0001)
// real: "2.5339988685347402e-65"
eq(t, "2.53399886853474e-65", 0.000000000000000000000000000000000000000000000000000000000000000025339988685347402)
// real: "2.5339988685347402e-65"
eq(t, "2.53399886853474e-65", 2.5339988685347402e-65)
eq(t, "3479099956230698", 3479099956230698)
// real: "3.479099956230698e+7"
eq(t, "34790999.56230698", 34790999.56230698123123123)

eq(t, "1.2", 1.2)
eq(t, "2.4", 2*1.2)
eq(t, "3.6", 3*1.2)
eq(t, "4.8", 4*1.2)
a := 1.2
eq(t, "1.2", a)
a += 1.2
eq(t, "2.4", a)
a += 1.2
// real: "3.5999999999999996"
eq(t, "3.6", a)
a += 1.2
eq(t, "4.8", a)
}

0 comments on commit aa376e7

Please sign in to comment.