graphql-python / graphql-core

A Python 3.6+ port of the GraphQL.js reference implementation of GraphQL.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Loss of precision in floating point values

rpgreen opened this issue · comments

Hello,
We are observing some surprising behavior with floating point numbers. Specifically, ast_from_value() appears to be converting python float values to strings in a lossy manner.

This appears to be happening in this line

Using :g appears to round numbers and/or convert them to scientific notation with 5 significant digits.

For example,

value_ast = ast_from_value(
    {'x': 12345678901234.0},
    type,
)

produces an AST with a FloatValueNode with string value of '1.23457e+13'

Printing back to a string:

printed_ast = printer.print_ast(value_ast)
print(printed_ast)

produces

{
x: 1.23457e+13
}

where we would expect it to be

{
x:  12345678901234.0
}

Similarly, a number like 1.1234567890123457 gets rounded to 1.12346.

In our experiments, changing the line references above to

return FloatValueNode(value=str(serialized))

produces better results but is still limited by the underlying limitations of Python floats (see test cases below).

We think the ultimate solution may require using Decimal types instead of floats throughout graphql-core.

Here is a simple test cases to reproduce:

@pytest.mark.cdk
@pytest.mark.parametrize(
    "name,input_num,expected_output_num",
    [
        pytest.param("large floating point", 12345678901234.123, "12345678901234.123"),
        pytest.param("floating point precision", 1234567.987654321, "1234567.987654321"),
        pytest.param("negative float", -12345678901234.123, "-12345678901234.123"),
        pytest.param("no decimal", 12345678901234, "12345678901234.0"),
        # these cases may require use of Decimal to avoid loss of precision:
        pytest.param("floating point precision large", 12345678901.987654321, "12345678901.987654"),
        pytest.param("floating point high precision", 1.1234567890123456789, "1.1234567890123457"),
        pytest.param("floating point precision 17 digits", 123456789012345678.123456, "1.2345678901234568e+17"),
    ],
)
def test_python_type_to_graphql_string_floating_point_numbers(
    name: str, input_num: float, expected_output_num: str, gql_schema_shapes
) -> None:
    schema = gql_schema_shapes.customer
    val = {"x": input_num}
    value_ast = ast_from_value(
        val,
        schema.get_type("MyType"),
    )
    res = printer.print_ast(value_ast)
    assert res == f'{{x: {expected_output_num}}}', f"{name} failed"

graphql-core version 3.2.0

This has been fixed in #164 in the main branch, will be part of the next patch release.

Regarding the use of Decimals, this should be implemented as a custom scalar type.

Thank you, I just noticed the commit for this. When can we expect the patch release?

This has been released now in v3.2.1.