lua-resty-redis icon indicating copy to clipboard operation
lua-resty-redis copied to clipboard

Lost number precision when saving to redis

Open KSDaemon opened this issue 8 years ago • 5 comments
trafficstars

Hi!

I've found a vary bad thing... as i have investigated, lua-resty-redis loose 64bit number precision, when saving numbers to redis.

See examples below. Lets assume, we have a number 1824192940134586

REDIS CLI CODE

127.0.0.1:6379[5]> set BIG_NUM_AS_NUM_REDIS 1824192940134586
OK
127.0.0.1:6379[5]> get BIG_NUM_AS_NUM_REDIS
"1824192940134586"

Everything works as expected

Now lets work with number through lua-resty-redis

NGINX LUA CODE

-- first, lets save a number in redis
redis:set("BIG_NUM_AS_NUM", 1824192940134586)
redis:set("BIG_NUM_AS_STR", string.format("%16.0f",1824192940134586))

-- now lets get keys
ngx.log(ngx.ERR, "BIG_NUM_AS_NUM: ", redis:get("BIG_NUM_AS_NUM"))
-- prints: 1.8241929401346e+15
ngx.log(ngx.ERR, "BIG_NUM_AS_NUM(tostr): ", string.format("%16.0f",redis:get("BIG_NUM_AS_NUM")))
-- prints: 1824192940134600 - FAIL
ngx.log(ngx.ERR, "BIG_NUM_AS_STR: ", redis:get("BIG_NUM_AS_STR"))
-- prints: 1824192940134586 - OK

ngx.log(ngx.ERR, "BIG_NUM_AS_NUM_REDIS: ", redis:get("BIG_NUM_AS_NUM_REDIS"))
-- prints: 1824192940134586 - OK
ngx.log(ngx.ERR, "BIG_NUM_AS_NUM_REDIS(tostr): ", string.format("%16.0f",redis:get("BIG_NUM_AS_NUM_REDIS")))
-- prints: 1824192940134586 - OK

KSDaemon avatar Oct 31 '17 09:10 KSDaemon

What do you think about this solution: in function _gen_req(args) process numbers with string.format instead of tostring?

        if type(arg) == "number" then
            arg = string.format("%18.0f",arg)
        elseif type(arg) ~= "string" then
            arg = tostring(arg)
        end

Of course it not a full solution. just an idea, because there are may different type of numbers

KSDaemon avatar Oct 31 '17 09:10 KSDaemon

Interesting, it seems that the format result of tostring is awful sometimes:

[3] lua(main)> tonumber(tostring(1824192940134586)) == 1824192940134586
=> false
[4] lua(main)> tonumber(tostring(1824192940134586)) == 1824192940134600
=> true

spacewander avatar Oct 31 '17 09:10 spacewander

I guess the tostring(num) in x64 is equal to string.format("%.14g", num). See https://github.com/openresty/luajit2/blob/v2.1-agentzh/src/lj_strfmt_num.c#L589

spacewander avatar Oct 31 '17 10:10 spacewander

My updated proposal for a check:

        local arg = args[i]
        if type(arg) ~= "string" then
            if type(arg) == "number" and arg == math.floor(arg) then
                arg = string.format("%.0f",arg)
            else
                arg = tostring(arg)
            end
        end

KSDaemon avatar Oct 31 '17 10:10 KSDaemon

@KSDaemon In your redis-cli example, you are using a string literal value instead of a number value. So why don't you just use a string value in Lua too? Lua's native numbers do not support 64-bit precision anyway.

agentzh avatar Oct 31 '17 17:10 agentzh