Skip to content

Commit 8cd9e1f

Browse files
author
Tuhin-SnapD
committed
BUG: Preserve full float precision when double_precision=15 in to_json; avoid truncation before serialization (#62072)
1 parent 7863029 commit 8cd9e1f

File tree

1 file changed

+45
-1
lines changed

1 file changed

+45
-1
lines changed

pandas/io/json/_json.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,44 @@
9292
FrameSeriesStrT = TypeVar("FrameSeriesStrT", bound=Literal["frame", "series"])
9393

9494

95+
# Helpers to stringify floats at full precision when requested
96+
def _format_float_full_precision(value: Any) -> Any:
97+
"""Return a string with full precision for finite floats; otherwise original.
98+
99+
Uses 17 significant digits to preserve IEEE-754 double precision.
100+
"""
101+
try:
102+
# Fast path for Python float and NumPy floating scalars
103+
if isinstance(value, (float, np.floating)):
104+
x = float(value)
105+
if np.isfinite(x):
106+
return format(x, ".17g")
107+
return value
108+
except Exception:
109+
# On any unexpected error, fall back to original value
110+
return value
111+
return value
112+
113+
114+
def _stringify_floats_full_precision(obj: Any) -> Any:
115+
"""Recursively convert finite floats to full-precision strings.
116+
117+
Leaves non-finite floats (NaN, +/-Inf) and non-float types unchanged.
118+
Works for pandas Series/DataFrame, Python dict/list, and NumPy arrays.
119+
"""
120+
if isinstance(obj, Series):
121+
return obj.map(_format_float_full_precision)
122+
if isinstance(obj, DataFrame):
123+
return obj.applymap(_format_float_full_precision)
124+
if isinstance(obj, dict):
125+
return {k: _stringify_floats_full_precision(v) for k, v in obj.items()}
126+
if isinstance(obj, list):
127+
return [_stringify_floats_full_precision(v) for v in obj]
128+
if isinstance(obj, np.ndarray):
129+
return [_stringify_floats_full_precision(v) for v in obj.tolist()]
130+
# Scalars
131+
return _format_float_full_precision(obj)
132+
95133
# interface to/from
96134
@overload
97135
def to_json(
@@ -252,8 +290,14 @@ def _format_axes(self) -> None:
252290

253291
def write(self) -> str:
254292
iso_dates = self.date_format == "iso"
293+
payload = self.obj_to_write
294+
# When maximum precision is requested, avoid truncation by stringifying
295+
# finite float values at full IEEE-754 precision. Skip for orient='table'
296+
# to preserve Table Schema type conformance.
297+
if self.double_precision == 15 and not isinstance(self, JSONTableWriter):
298+
payload = _stringify_floats_full_precision(payload)
255299
return ujson_dumps(
256-
self.obj_to_write,
300+
payload,
257301
orient=self.orient,
258302
double_precision=self.double_precision,
259303
ensure_ascii=self.ensure_ascii,

0 commit comments

Comments
 (0)