Fix Redis 'ERR value is not a valid float' When Using INCRBYFLOAT

beginner๐Ÿ”ด Redis2026-05-15| Redis 4.x, 5.x, 6.x, 7.x โ€” any OS (Linux, macOS, Windows WSL)

Error Message

ERR value is not a valid float
#redis#incrbyfloat#float#data-type#number

TL;DR

Redis throws ERR value is not a valid float when INCRBYFLOAT hits a key that can't be parsed as a number โ€” think empty strings, "1,5" with a comma decimal, or "NaN". The fix: make sure the key holds a clean numeric string before calling INCRBYFLOAT.

What triggers this error

Try this and you'll see it instantly:

127.0.0.1:6379> SET price "not-a-number"
OK
127.0.0.1:6379> INCRBYFLOAT price 1.5
(error) ERR value is not a valid float

Empty strings and locale-formatted numbers hit the same wall:

127.0.0.1:6379> SET counter ""
OK
127.0.0.1:6379> INCRBYFLOAT counter 0.1
(error) ERR value is not a valid float

127.0.0.1:6379> SET ratio "1,75"   # comma decimal โ€” invalid in Redis
OK
127.0.0.1:6379> INCRBYFLOAT ratio 0.25
(error) ERR value is not a valid float

Under the hood, Redis calls strtod() to parse the stored value. That function accepts the same format as a C double literal: optional sign, digits, dot, exponent โ€” so 1.5, -3e2, and .75 all pass. Everything else gets rejected.

Four ways this sneaks into production

Most teams hit this error through one of these paths:

  • Formatted strings from the app layer. A Python or PHP app called SET price "1,500.00" with thousand-separator commas, or stored a currency string like "$9.99".
  • Empty-string placeholder. Someone wrote SET counter "" to "initialize" the key instead of SET counter 0.
  • Wrong data type leaked in. A boolean ("true"), a JSON blob ("{\"v\":1}"), or a shell-echoed value with a trailing newline.
  • Serialized collection stored as a string. A value like "[1,2,3]" is a valid string type (won't give you WRONGTYPE), but it's still not a parseable float.

Fix 1 โ€” Check what's actually stored

Don't guess. Look first:

127.0.0.1:6379> TYPE mykey
string
127.0.0.1:6379> GET mykey
"not-a-number"

If TYPE returns anything other than string, you've got a wrong-type problem โ€” not a float-format one. The rest of the fixes below apply only to string keys.

Fix 2 โ€” Overwrite the bad value

Simplest fix: just replace it with a real number.

# Reset to zero
127.0.0.1:6379> SET price 0
OK
127.0.0.1:6379> INCRBYFLOAT price 1.5
"1.5"

# Or start from a specific value
127.0.0.1:6379> SET price 9.99
OK
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"

Fix 3 โ€” Initialize safely with SETNX

Multiple workers racing to create the same key? Use SET โ€ฆ NX so you don't clobber a valid value that's already there:

127.0.0.1:6379> SET price 0 NX
OK   # only sets if key does not exist
127.0.0.1:6379> INCRBYFLOAT price 2.50
"2.5"

It's a no-op if the key already holds a good value. This is the pattern most Redis clients use in high-concurrency counters.

Fix 4 โ€” Validate before writing from application code

The real fix is upstream: catch bad values before they ever reach Redis.

Python (redis-py)

import redis

r = redis.Redis()

def safe_incrbyfloat(key: str, increment: float) -> float:
    current = r.get(key)
    if current is not None:
        try:
            float(current)  # raises ValueError if stored value is junk
        except ValueError:
            r.set(key, 0)  # reset before incrementing
    return r.incrbyfloat(key, increment)

result = safe_incrbyfloat("price", 1.5)
print(result)  # 1.5

Node.js (ioredis)

const Redis = require('ioredis');
const client = new Redis();

async function safeIncrByFloat(key, increment) {
  const current = await client.get(key);
  if (current !== null && isNaN(parseFloat(current))) {
    await client.set(key, 0);
  }
  return client.incrbyfloat(key, increment);
}

const result = await safeIncrByFloat('price', 1.5);
console.log(result); // '1.5'

Fix 5 โ€” Atomic check-and-increment with Lua

The Python and Node.js examples above have a race window between GET and INCRBYFLOAT. If that matters, move the logic into a Lua script โ€” Redis executes it atomically:

local val = redis.call('GET', KEYS[1])
if val == false or tonumber(val) == nil then
  redis.call('SET', KEYS[1], 0)
end
return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])

Run it directly:

redis-cli EVAL "
  local val = redis.call('GET', KEYS[1])
  if val == false or tonumber(val) == nil then
    redis.call('SET', KEYS[1], 0)
  end
  return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])
" 1 price 1.5

Edge cases worth knowing

  • Trailing newline from shell echo. redis-cli SET key $(echo "1.5") stores "1.5\n" โ€” Redis rejects it. Use printf or quote the value explicitly.
  • Locale-formatted numbers. Neither "1.234,56" (European) nor "1,234.56" (US with thousands separator) will parse. Strip formatting before storing.
  • NaN and Infinity. Both are valid IEEE 754 floats but Redis rejects them explicitly. Only finite numeric strings pass strtod().
  • The increment itself. The second argument to INCRBYFLOAT must also be a valid float โ€” though most client libraries catch this before the command hits Redis.

Verification steps

After applying a fix, run this sequence to confirm everything works:

# 1. Confirm the stored value is numeric
127.0.0.1:6379> GET price
"0"

# 2. INCRBYFLOAT should return a number, not an error
127.0.0.1:6379> INCRBYFLOAT price 9.99
"9.99"

# 3. Increment again to verify accumulation
127.0.0.1:6379> INCRBYFLOAT price 0.01
"10"

# 4. GET the final value to be sure
127.0.0.1:6379> GET price
"10"

Debugging in production and can't touch the value? Use this non-destructive probe:

# INCRBYFLOAT with 0 still validates the stored value without changing it
127.0.0.1:6379> INCRBYFLOAT suspicious_key 0
# error โ†’ stored value is not a valid float
# no error โ†’ stored value is fine

Quick reference

  • Valid values: "0", "1.5", "-3.14", "2e3", ".5"
  • Invalid values: "", "abc", "1,5", "$9.99", "NaN", "Infinity", "true"
  • Safe init pattern: SET key 0 NX before the first INCRBYFLOAT
  • Need atomicity: wrap init + increment in a Lua script

Related Error Notes