diff --git a/cmd_hash.go b/cmd_hash.go index fb54be01..08583485 100644 --- a/cmd_hash.go +++ b/cmd_hash.go @@ -3,6 +3,7 @@ package miniredis import ( + "math/big" "strconv" "strings" @@ -498,7 +499,7 @@ func (m *Miniredis) cmdHincrbyfloat(c *server.Peer, cmd string, args []string) { key, field, deltas := args[0], args[1], args[2] - delta, err := strconv.ParseFloat(deltas, 64) + delta, _, err := big.ParseFloat(deltas, 10, 128, 0) if err != nil { setDirty(c) c.WriteError(msgInvalidFloat) @@ -518,7 +519,7 @@ func (m *Miniredis) cmdHincrbyfloat(c *server.Peer, cmd string, args []string) { c.WriteError(err.Error()) return } - c.WriteBulk(formatFloat(v)) + c.WriteBulk(formatBig(v)) }) } diff --git a/cmd_string.go b/cmd_string.go index 1dea14f7..d4551c3b 100644 --- a/cmd_string.go +++ b/cmd_string.go @@ -3,6 +3,7 @@ package miniredis import ( + "math/big" "strconv" "strings" "time" @@ -504,7 +505,7 @@ func (m *Miniredis) cmdIncrbyfloat(c *server.Peer, cmd string, args []string) { } key := args[0] - delta, err := strconv.ParseFloat(args[1], 64) + delta, _, err := big.ParseFloat(args[1], 10, 128, 0) if err != nil { setDirty(c) c.WriteError(msgInvalidFloat) @@ -525,7 +526,7 @@ func (m *Miniredis) cmdIncrbyfloat(c *server.Peer, cmd string, args []string) { return } // Don't touch TTL - c.WriteBulk(formatFloat(v)) + c.WriteBulk(formatBig(v)) }) } diff --git a/db.go b/db.go index 526d1da1..68f9338e 100644 --- a/db.go +++ b/db.go @@ -2,6 +2,7 @@ package miniredis import ( "errors" + "math/big" "sort" "strconv" "time" @@ -164,17 +165,18 @@ func (db *RedisDB) stringIncr(k string, delta int) (int, error) { } // change float key value -func (db *RedisDB) stringIncrfloat(k string, delta float64) (float64, error) { - v := 0.0 +func (db *RedisDB) stringIncrfloat(k string, delta *big.Float) (*big.Float, error) { + v := big.NewFloat(0.0) + v.SetPrec(128) if sv, ok := db.stringKeys[k]; ok { var err error - v, err = strconv.ParseFloat(sv, 64) + v, _, err = big.ParseFloat(sv, 10, 128, 0) if err != nil { - return 0, ErrFloatValueError + return nil, ErrFloatValueError } } - v += delta - db.stringSet(k, formatFloat(v)) + v.Add(v, delta) + db.stringSet(k, formatBig(v)) return v, nil } @@ -346,19 +348,20 @@ func (db *RedisDB) hashIncr(key, field string, delta int) (int, error) { } // hashIncrfloat changes float key value -func (db *RedisDB) hashIncrfloat(key, field string, delta float64) (float64, error) { - v := 0.0 +func (db *RedisDB) hashIncrfloat(key, field string, delta *big.Float) (*big.Float, error) { + v := big.NewFloat(0.0) + v.SetPrec(128) if h, ok := db.hashKeys[key]; ok { if f, ok := h[field]; ok { var err error - v, err = strconv.ParseFloat(f, 64) + v, _, err = big.ParseFloat(f, 10, 128, 0) if err != nil { - return 0, ErrFloatValueError + return nil, ErrFloatValueError } } } - v += delta - db.hashSet(key, field, formatFloat(v)) + v.Add(v, delta) + db.hashSet(key, field, formatBig(v)) return v, nil } diff --git a/direct.go b/direct.go index 47272bf1..51d6defe 100644 --- a/direct.go +++ b/direct.go @@ -4,6 +4,7 @@ package miniredis import ( "errors" + "math/big" "time" ) @@ -148,7 +149,12 @@ func (db *RedisDB) Incrfloat(k string, delta float64) (float64, error) { return 0, ErrWrongType } - return db.stringIncrfloat(k, delta) + v, err := db.stringIncrfloat(k, big.NewFloat(delta)) + if err != nil { + return 0, err + } + vf, _ := v.Float64() + return vf, nil } // List returns the list k, or an error if it's not there or something else. @@ -534,7 +540,12 @@ func (db *RedisDB) HIncrfloat(k, f string, delta float64) (float64, error) { defer db.master.Unlock() defer db.master.signal.Broadcast() - return db.hashIncrfloat(k, f, delta) + v, err := db.hashIncrfloat(k, f, big.NewFloat(delta)) + if err != nil { + return 0, err + } + vf, _ := v.Float64() + return vf, nil } // SRem removes fields from a set. Returns number of deleted fields. diff --git a/redis.go b/redis.go index a804e227..ef0cc841 100644 --- a/redis.go +++ b/redis.go @@ -3,6 +3,7 @@ package miniredis import ( "fmt" "math" + "math/big" "strings" "sync" "time" @@ -130,16 +131,28 @@ func blocking( // formatFloat formats a float the way redis does (sort-of) func formatFloat(v float64) string { - // Format with %f and strip trailing 0s. This is the most like Redis does - // it :( - // .12 is the magic number where most output is the same as Redis. - if math.IsInf(v, +1) { + if math.IsInf(v, 1) { return "inf" } if math.IsInf(v, -1) { return "-inf" } - sv := fmt.Sprintf("%.12f", v) + return stripZeros(fmt.Sprintf("%.12f", v)) +} + +// formatBig formats a float the way redis does +func formatBig(v *big.Float) string { + // Format with %f and strip trailing 0s. + if v.IsInf() { + return "inf" + } + // if math.IsInf(v, -1) { + // return "-inf" + // } + return stripZeros(fmt.Sprintf("%.17f", v)) +} + +func stripZeros(sv string) string { for strings.Contains(sv, ".") { if sv[len(sv)-1] != '0' { break