Skip to content

Commit

Permalink
Fix: use localcontext to handle decimals of any precision (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
oscar26 committed Jun 12, 2020
1 parent 5f147b9 commit b6ce1de
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 4 deletions.
9 changes: 7 additions & 2 deletions amazon/ion/reader_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import six

from datetime import timedelta
from decimal import Decimal
from decimal import Decimal, localcontext
from functools import partial
from io import BytesIO
from struct import unpack
Expand Down Expand Up @@ -186,6 +186,7 @@ def _parse_signed_int_components(buf):

def _parse_decimal(buf):
"""Parses the remainder of a file-like object as a decimal."""
from decimal import localcontext
exponent = _parse_var_int(buf, signed=True)
sign_bit, coefficient = _parse_signed_int_components(buf)

Expand All @@ -194,7 +195,11 @@ def _parse_decimal(buf):
value = Decimal((sign_bit, (0,), exponent))
else:
coefficient *= sign_bit and -1 or 1
value = Decimal(coefficient).scaleb(exponent)
with localcontext() as context:
# Adjusting precision for taking into account arbitrarily
# large/small numbers
context.prec = len(str(coefficient))
value = Decimal(coefficient).scaleb(exponent)

return value

Expand Down
8 changes: 6 additions & 2 deletions amazon/ion/writer_binary_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


from datetime import datetime
from decimal import Decimal
from decimal import Decimal, localcontext
from functools import partial

import six
Expand Down Expand Up @@ -185,7 +185,11 @@ def _serialize_decimal(ion_event):
value = ion_event.value
validate_scalar_value(value, Decimal)
sign, digits, exponent = value.as_tuple()
coefficient = int(value.scaleb(-exponent).to_integral_value())
with localcontext() as context:
# Adjusting precision for taking into account arbitrarily large/small
# numbers
context.prec = len(digits)
coefficient = int(value.scaleb(-exponent).to_integral_value())
if not sign and not exponent and not coefficient:
# The value is 0d0; other forms of zero will fall through.
buf.append(_Zeros.DECIMAL)
Expand Down
36 changes: 36 additions & 0 deletions tests/test_decimal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at:
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the
# License.

from decimal import Decimal
from amazon.ion.simpleion import dumps, loads


# regression test for https://github.com/amzn/ion-python/issues/132
def test_decimal_precision():
from decimal import localcontext

with localcontext() as ctx:
# ensure test executes with the default precision
# (see https://docs.python.org/3.7/library/decimal.html#decimal.DefaultContext):
ctx.prec = 28

# decimal with 29 digits
decimal = Decimal('1234567890123456789012345678.9')
assert decimal == loads(dumps(decimal))
assert decimal == loads(dumps(decimal, binary=False))

# negative decimal with 29 digits
decimal = Decimal('-1234567890123456789012345678.9')
assert decimal == loads(dumps(decimal))
assert decimal == loads(dumps(decimal, binary=False))

0 comments on commit b6ce1de

Please sign in to comment.