Search code examples
lua

Use string.format to convert a string value to a long number and meet a strange issue


Lua version is 5.1.4, here is the details:

Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio

print(string.format("where id = %d", "1596856742980208710"))

output:

where id = 1596856742980208640

But when changing %d to %s:

print(string.format("where id = %s", "1596856742980208710"))

output:

where id = 1596856742980208710

What does this mean?


Solution

  • This is a Lua "feature" called coercion: Lua will coerce strings to numbers if a number is expected and vice versa. Lua 5.1 by default uses doubles as only number type, which may lead to a loss of precision when coercing large numbers.

    Consider your first example:

    print(string.format("where id = %d", "1596856742980208710"))
    

    %d obviously expects a number. The string "1596856742980208710" is thus coerced to a number; the code is equivalent to

    print(string.format("where id = %d", tonumber"1596856742980208710"))
    

    on newer Lua versions (5.3+), this would work just fine, since the number would be converted to a 64-bit signed integer:

    $ lua
    Lua 5.3.4  Copyright (C) 1994-2017 Lua.org, PUC-Rio
    > tonumber"1596856742980208710"
    1596856742980208710
    

    Lua 5.1 does however not have an integer type; it will convert the number to a 64-bit double:

    $ lua5.1
    Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
    > =("%d"):format(tonumber"1596856742980208710")
    1596856742980208640
    

    as you can see, the number has changed. This is because of double precision issues: The maximum safe integer that can be stored in a double is 2^53 - 1 (see e.g. JS's MAX_SAFE_INTEGER). Your number significantly exceeds that: Compare 9.007199254741e+15 against 1596856742980208710. This leads to a loss of precision in the less significant decimal places. When the number is then formatted using %d, you get the incorrect result.

    Your second example:

    print(string.format("where id = %s", "1596856742980208710"))
    

    obviously works, since it treats "1596856742980208710" as a string and doesn't coerce it to a number in the first place. It is thus equivalent to a string concatenation "where id = " .. "1596856742980208710" no matter the Lua version (as "1596856742980208710" doesn't contain NULL bytes).