Description
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