Skip to content

Loss of precision in floating point values #167

Closed
@rpgreen

Description

@rpgreen

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions