Skip to content

Commit f48d782

Browse files
fix(data-format): preserve values when expanding scientific notation
1 parent 2ce3fc7 commit f48d782

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

backend/common/utils/data_format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def format_float_without_scientific(value):
5555
"""格式化浮点数,避免科学记数法"""
5656
if value == 0:
5757
return "0"
58-
formatted = str(Decimal(str(value)))
58+
formatted = format(Decimal(str(value)), 'f')
5959
if '.' in formatted:
6060
formatted = formatted.rstrip('0').rstrip('.')
6161
return formatted

backend/tests/test_data_format.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Regression tests for numeric values shared by query results and Excel exports."""
2+
3+
import ast
4+
from decimal import Decimal
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
10+
@pytest.fixture(scope="module")
11+
def convert_numbers():
12+
# Follow the existing isolated tests without importing the chat/LLM stack.
13+
source = Path(__file__).resolve().parents[1] / "common/utils/data_format.py"
14+
tree = ast.parse(source.read_text(encoding="utf-8"))
15+
node = next(
16+
node for node in tree.body if getattr(node, "name", None) == "DataFormat"
17+
)
18+
namespace = {"Decimal": Decimal}
19+
exec(
20+
compile(ast.Module(body=[node], type_ignores=[]), str(source), "exec"),
21+
namespace,
22+
)
23+
return namespace["DataFormat"].convert_large_numbers_in_object_array
24+
25+
26+
@pytest.mark.parametrize(
27+
("value", "expected"),
28+
[
29+
(1.23e20, "123000000000000000000"),
30+
(-1.23e20, "-123000000000000000000"),
31+
(1.23e-10, "0.000000000123"),
32+
(-1.23e-10, "-0.000000000123"),
33+
(1e20, "100000000000000000000"),
34+
(1e-7, "0.0000001"),
35+
(1e10, "10000000000"),
36+
(12345678901.25, "12345678901.25"),
37+
(0.0, "0"),
38+
(-0.0, "0"),
39+
],
40+
)
41+
def test_float_conversion_preserves_value_without_scientific_notation(
42+
convert_numbers, value, expected
43+
):
44+
assert convert_numbers([{"value": value}]) == [{"value": expected}]
45+
46+
47+
def test_values_outside_conversion_thresholds_keep_their_types(convert_numbers):
48+
row = {
49+
"regular_float": 123.4,
50+
"small_float_boundary": 1e-6,
51+
"below_float_threshold": 9999999999.5,
52+
"below_int_threshold": 999999999999999,
53+
"large_integer": 1000000000000000,
54+
"negative_integer": -1000000000000000,
55+
"zero": 0,
56+
"flag": True,
57+
"text": "1.23e20",
58+
"missing": None,
59+
}
60+
61+
converted = convert_numbers([row])[0]
62+
63+
assert converted == {
64+
**row,
65+
"large_integer": "1000000000000000",
66+
"negative_integer": "-1000000000000000",
67+
}
68+
for key in ("regular_float", "small_float_boundary", "below_float_threshold"):
69+
assert isinstance(converted[key], float)
70+
71+
72+
def test_scientific_notation_is_converted_in_nested_rows(convert_numbers):
73+
rows = [{"metrics": {"amount": 1.23e20}, "series": [{"value": 1.23e-10}]}]
74+
75+
assert convert_numbers(rows) == [
76+
{
77+
"metrics": {"amount": "123000000000000000000"},
78+
"series": [{"value": "0.000000000123"}],
79+
}
80+
]
81+
assert rows[0]["metrics"]["amount"] == 1.23e20
82+
83+
84+
def test_excel_integer_threshold_remains_supported(convert_numbers):
85+
assert convert_numbers(
86+
[{"below": 99999999999, "boundary": 100000000000, "amount": 1.23e20}],
87+
int_threshold=1e11,
88+
) == [
89+
{
90+
"below": 99999999999,
91+
"boundary": "100000000000",
92+
"amount": "123000000000000000000",
93+
}
94+
]

0 commit comments

Comments
 (0)