diff --git a/src/pantab/writer.cpp b/src/pantab/writer.cpp index 3c1004b3..baa65781 100644 --- a/src/pantab/writer.cpp +++ b/src/pantab/writer.cpp @@ -360,9 +360,21 @@ class DecimalInsertHelper : public InsertHelper { std::string str{reinterpret_cast(buffer.data), static_cast(buffer.size_bytes)}; // The Hyper API wants the string to include the decimal place, which - // nanoarrow does not provide - if (scale_ > 0) - str = str.insert(str.size() - scale_, 1, '.'); + // nanoarrow does not provide. + if (scale_ > 0) { + // nanoarrow strips leading zeros + const auto insert_pos = static_cast(str.size()) - scale_; + if (insert_pos < 0) { + std::string newstr{}; + newstr.reserve(str.size() - insert_pos + 1); + newstr.append(1, '.'); + newstr.append(-insert_pos, '0'); + newstr.append(str); + str = std::move(newstr); + } else { + str = str.insert(str.size() - scale_, 1, '.'); + } + } constexpr auto PrecisionLimit = 39; // of-by-one error in solution? if (precision_ >= PrecisionLimit) { diff --git a/tests/test_roundtrip.py b/tests/test_roundtrip.py index e797c83c..95710311 100644 --- a/tests/test_roundtrip.py +++ b/tests/test_roundtrip.py @@ -1,3 +1,4 @@ +import decimal import sys import pandas as pd @@ -154,3 +155,21 @@ def test_roundtrip_works_without_tableauhyperapi(frame, tmp_hyper, monkeypatch): pt.frame_to_hyper(frame, tmp_hyper, table="foo") pt.frames_from_hyper(tmp_hyper) + + +@pytest.mark.parametrize( + "value,precision,scale", + [ + ("0.00", 3, 2), + ("0E-10", 3, 2), + ("100", 3, 0), + ("1.00", 3, 2), + (".001", 3, 3), + ], +) +def test_decimal_roundtrip(tmp_hyper, value, precision, scale, compat): + arr = pa.array([decimal.Decimal(value)], type=pa.decimal128(precision, scale)) + tbl = pa.Table.from_arrays([arr], names=["col"]) + pt.frame_to_hyper(tbl, tmp_hyper, table="test") + result = pt.frame_from_hyper(tmp_hyper, table="test", return_type="pyarrow") + compat.assert_frame_equal(result, tbl)