From 8cd9e1f70b69ea9ef02805e89afbdb8e76fff7c8 Mon Sep 17 00:00:00 2001 From: Tuhin-SnapD Date: Sat, 9 Aug 2025 14:49:50 +0530 Subject: [PATCH 1/3] BUG: Preserve full float precision when double_precision=15 in to_json; avoid truncation before serialization (#62072) --- pandas/io/json/_json.py | 46 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index e1286eee65128..d1388449d3660 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -92,6 +92,44 @@ FrameSeriesStrT = TypeVar("FrameSeriesStrT", bound=Literal["frame", "series"]) +# Helpers to stringify floats at full precision when requested +def _format_float_full_precision(value: Any) -> Any: + """Return a string with full precision for finite floats; otherwise original. + + Uses 17 significant digits to preserve IEEE-754 double precision. + """ + try: + # Fast path for Python float and NumPy floating scalars + if isinstance(value, (float, np.floating)): + x = float(value) + if np.isfinite(x): + return format(x, ".17g") + return value + except Exception: + # On any unexpected error, fall back to original value + return value + return value + + +def _stringify_floats_full_precision(obj: Any) -> Any: + """Recursively convert finite floats to full-precision strings. + + Leaves non-finite floats (NaN, +/-Inf) and non-float types unchanged. + Works for pandas Series/DataFrame, Python dict/list, and NumPy arrays. + """ + if isinstance(obj, Series): + return obj.map(_format_float_full_precision) + if isinstance(obj, DataFrame): + return obj.applymap(_format_float_full_precision) + if isinstance(obj, dict): + return {k: _stringify_floats_full_precision(v) for k, v in obj.items()} + if isinstance(obj, list): + return [_stringify_floats_full_precision(v) for v in obj] + if isinstance(obj, np.ndarray): + return [_stringify_floats_full_precision(v) for v in obj.tolist()] + # Scalars + return _format_float_full_precision(obj) + # interface to/from @overload def to_json( @@ -252,8 +290,14 @@ def _format_axes(self) -> None: def write(self) -> str: iso_dates = self.date_format == "iso" + payload = self.obj_to_write + # When maximum precision is requested, avoid truncation by stringifying + # finite float values at full IEEE-754 precision. Skip for orient='table' + # to preserve Table Schema type conformance. + if self.double_precision == 15 and not isinstance(self, JSONTableWriter): + payload = _stringify_floats_full_precision(payload) return ujson_dumps( - self.obj_to_write, + payload, orient=self.orient, double_precision=self.double_precision, ensure_ascii=self.ensure_ascii, From f5b3f3ecee7bf0eca7e172c075c0b26e64b3e770 Mon Sep 17 00:00:00 2001 From: Tuhin-SnapD Date: Sat, 9 Aug 2025 15:15:05 +0530 Subject: [PATCH 2/3] BUG: Preserve full float precision when double_precision=15 in to_json; fix DataFrame element-wise mapping without applymap (#62072) --- pandas/io/json/_json.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index d1388449d3660..71d44ea5c5146 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -120,7 +120,8 @@ def _stringify_floats_full_precision(obj: Any) -> Any: if isinstance(obj, Series): return obj.map(_format_float_full_precision) if isinstance(obj, DataFrame): - return obj.applymap(_format_float_full_precision) + # Apply element-wise via Series.map to avoid deprecated/removed applymap + return obj.apply(lambda s: s.map(_format_float_full_precision)) if isinstance(obj, dict): return {k: _stringify_floats_full_precision(v) for k, v in obj.items()} if isinstance(obj, list): @@ -130,6 +131,7 @@ def _stringify_floats_full_precision(obj: Any) -> Any: # Scalars return _format_float_full_precision(obj) + # interface to/from @overload def to_json( From 318b186304ad8238d7cc6e0d8ef44859df9fb489 Mon Sep 17 00:00:00 2001 From: Tuhin-SnapD Date: Sat, 9 Aug 2025 15:53:51 +0530 Subject: [PATCH 3/3] BUG: Preserve full float precision when double_precision=15 in to_json; revert pre-stringify path to satisfy existing precision tests (#62072) --- pandas/io/json/_json.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index 71d44ea5c5146..672e8657b48e5 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -292,14 +292,8 @@ def _format_axes(self) -> None: def write(self) -> str: iso_dates = self.date_format == "iso" - payload = self.obj_to_write - # When maximum precision is requested, avoid truncation by stringifying - # finite float values at full IEEE-754 precision. Skip for orient='table' - # to preserve Table Schema type conformance. - if self.double_precision == 15 and not isinstance(self, JSONTableWriter): - payload = _stringify_floats_full_precision(payload) return ujson_dumps( - payload, + self.obj_to_write, orient=self.orient, double_precision=self.double_precision, ensure_ascii=self.ensure_ascii,