|
92 | 92 | FrameSeriesStrT = TypeVar("FrameSeriesStrT", bound=Literal["frame", "series"])
|
93 | 93 |
|
94 | 94 |
|
| 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 | + |
95 | 133 | # interface to/from
|
96 | 134 | @overload
|
97 | 135 | def to_json(
|
@@ -252,8 +290,14 @@ def _format_axes(self) -> None:
|
252 | 290 |
|
253 | 291 | def write(self) -> str:
|
254 | 292 | 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) |
255 | 299 | return ujson_dumps(
|
256 |
| - self.obj_to_write, |
| 300 | + payload, |
257 | 301 | orient=self.orient,
|
258 | 302 | double_precision=self.double_precision,
|
259 | 303 | ensure_ascii=self.ensure_ascii,
|
|
0 commit comments