Skip to content

Commit 7059d89

Browse files
funnycrabjreback
authored andcommitted
BUG: Fix rollover handling in json encoding
closes #15716 closes #15864 whenever the frac is incremented, there is a chance that its value may hit the value of pow10. Author: funnycrab <[email protected]> Author: Funnycrab <Funnycrab> Closes #15865 from funnycrab/fix_rollover_handling_in_json_enc and squashes the following commits: c9710ee [funnycrab] add more tests for examples listed in issue #15716 and #15864 3cee6b3 [funnycrab] add whatsnew entry 9b0dff0 [funnycrab] remove additional blank line 75effb4 [funnycrab] add tests 6acb969 [funnycrab] fix for cpplint aec58e6 [Funnycrab] BUG: Fix rollover handling in json encoding
1 parent f49f905 commit 7059d89

File tree

4 files changed

+75
-5
lines changed

4 files changed

+75
-5
lines changed

doc/source/whatsnew/v0.20.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,7 @@ I/O
10101010
- Bug in ``pd.read_hdf()`` passing a ``Timestamp`` to the ``where`` parameter with a non date column (:issue:`15492`)
10111011
- Bug in ``DataFrame.to_stata()`` and ``StataWriter`` which produces incorrectly formatted files to be produced for some locales (:issue:`13856`)
10121012
- Bug in ``StataReader`` and ``StataWriter`` which allows invalid encodings (:issue:`15723`)
1013+
- Bug in ``pd.to_json()`` for the C engine where rollover was not correctly handled for case where frac is odd and diff is exactly 0.5 (:issue:`15716`, :issue:`15864`)
10131014

10141015
Plotting
10151016
^^^^^^^^

pandas/_libs/src/ujson/lib/ultrajsonenc.c

+7-5
Original file line numberDiff line numberDiff line change
@@ -823,17 +823,19 @@ int Buffer_AppendDoubleUnchecked(JSOBJ obj, JSONObjectEncoder *enc,
823823

824824
if (diff > 0.5) {
825825
++frac;
826-
/* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */
827-
if (frac >= pow10) {
828-
frac = 0;
829-
++whole;
830-
}
831826
} else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {
832827
/* if halfway, round up if odd, OR
833828
if last digit is 0. That last part is strange */
834829
++frac;
835830
}
836831

832+
// handle rollover, e.g.
833+
// case 0.99 with prec 1 is 1.0 and case 0.95 with prec is 1.0 as well
834+
if (frac >= pow10) {
835+
frac = 0;
836+
++whole;
837+
}
838+
837839
if (enc->doublePrecision == 0) {
838840
diff = value - whole;
839841

pandas/tests/io/json/test_pandas.py

+25
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,31 @@ def test_frame_from_json_nones(self):
380380
unser = read_json(df.to_json(), dtype=False)
381381
self.assertTrue(np.isnan(unser[2][0]))
382382

383+
def test_frame_to_json_float_precision(self):
384+
df = pd.DataFrame([dict(a_float=0.95)])
385+
encoded = df.to_json(double_precision=1)
386+
self.assertEqual(encoded, '{"a_float":{"0":1.0}}')
387+
388+
df = pd.DataFrame([dict(a_float=1.95)])
389+
encoded = df.to_json(double_precision=1)
390+
self.assertEqual(encoded, '{"a_float":{"0":2.0}}')
391+
392+
df = pd.DataFrame([dict(a_float=-1.95)])
393+
encoded = df.to_json(double_precision=1)
394+
self.assertEqual(encoded, '{"a_float":{"0":-2.0}}')
395+
396+
df = pd.DataFrame([dict(a_float=0.995)])
397+
encoded = df.to_json(double_precision=2)
398+
self.assertEqual(encoded, '{"a_float":{"0":1.0}}')
399+
400+
df = pd.DataFrame([dict(a_float=0.9995)])
401+
encoded = df.to_json(double_precision=3)
402+
self.assertEqual(encoded, '{"a_float":{"0":1.0}}')
403+
404+
df = pd.DataFrame([dict(a_float=0.99999999999999944)])
405+
encoded = df.to_json(double_precision=15)
406+
self.assertEqual(encoded, '{"a_float":{"0":1.0}}')
407+
383408
def test_frame_to_json_except(self):
384409
df = DataFrame([1, 2, 3])
385410
self.assertRaises(ValueError, df.to_json, orient="garbage")

pandas/tests/io/json/test_ujson.py

+42
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,48 @@ def test_encodeDecimal(self):
4343
decoded = ujson.decode(encoded)
4444
self.assertEqual(decoded, 1337.1337)
4545

46+
sut = decimal.Decimal("0.95")
47+
encoded = ujson.encode(sut, double_precision=1)
48+
self.assertEqual(encoded, "1.0")
49+
decoded = ujson.decode(encoded)
50+
self.assertEqual(decoded, 1.0)
51+
52+
sut = decimal.Decimal("0.94")
53+
encoded = ujson.encode(sut, double_precision=1)
54+
self.assertEqual(encoded, "0.9")
55+
decoded = ujson.decode(encoded)
56+
self.assertEqual(decoded, 0.9)
57+
58+
sut = decimal.Decimal("1.95")
59+
encoded = ujson.encode(sut, double_precision=1)
60+
self.assertEqual(encoded, "2.0")
61+
decoded = ujson.decode(encoded)
62+
self.assertEqual(decoded, 2.0)
63+
64+
sut = decimal.Decimal("-1.95")
65+
encoded = ujson.encode(sut, double_precision=1)
66+
self.assertEqual(encoded, "-2.0")
67+
decoded = ujson.decode(encoded)
68+
self.assertEqual(decoded, -2.0)
69+
70+
sut = decimal.Decimal("0.995")
71+
encoded = ujson.encode(sut, double_precision=2)
72+
self.assertEqual(encoded, "1.0")
73+
decoded = ujson.decode(encoded)
74+
self.assertEqual(decoded, 1.0)
75+
76+
sut = decimal.Decimal("0.9995")
77+
encoded = ujson.encode(sut, double_precision=3)
78+
self.assertEqual(encoded, "1.0")
79+
decoded = ujson.decode(encoded)
80+
self.assertEqual(decoded, 1.0)
81+
82+
sut = decimal.Decimal("0.99999999999999944")
83+
encoded = ujson.encode(sut, double_precision=15)
84+
self.assertEqual(encoded, "1.0")
85+
decoded = ujson.decode(encoded)
86+
self.assertEqual(decoded, 1.0)
87+
4688
def test_encodeStringConversion(self):
4789
input = "A string \\ / \b \f \n \r \t </script> &"
4890
not_html_encoded = ('"A string \\\\ \\/ \\b \\f \\n '

0 commit comments

Comments
 (0)