9

Code is below

import json
from decimal import Decimal
from pprint import pprint
import boto3


def update_movie(title, year, rating=None, plot=None, actors=None, dynamodb=None):
    if not dynamodb:
        dynamodb = boto3.resource('dynamodb')

    table = dynamodb.Table('Movies')

    response = table.update_item(
        Key={
            'year': year,
            'title': title
        },
        UpdateExpression="set info.rating=:r, info.plot=:p, info.actors=:a",
        ExpressionAttributeValues={
            ':r': Decimal(rating),
            ':p': plot,
            ':a': actors
        },
        ReturnValues="UPDATED_NEW"
    )
    return response


def lambda_handler(event, context):
    update_response = update_movie(
        "Rush", 2013, 8.3, "Car show",
        ["Daniel", "Chris", "Olivia"])
    print("Update movie succeeded:")
    pprint(update_response, sort_dicts=False)

While updating a key in the dynamodb i got the error below

  "errorMessage": "[<class 'decimal.Inexact'>, <class 'decimal.Rounded'>]",
  "errorType": "Inexact",

If i am changing 8.3 to 8 My code is working fine

update_response = update_movie(
        "Rush", 2013, 8.3, "Car show",
        ["Daniel", "Chris", "Olivia"])
    print("Update movie succeeded:")``` 
aysh
  • 493
  • 1
  • 11
  • 23

3 Answers3

24

The problem is that DynamoDB's representation of floating-point numbers is different from Python's:

  1. DynamoDB represents floating point numbers with a decimal representation. So "8.3" can be represented exactly - with no rounding or inexactness.
  2. Python uses, as traditional, base-2 representation, so it can't represent 8.3 exactly. 8.3 is actually represented as 8.3000000000000007105 and is known to be inexact (python doesn't know which digits you intended at the very end).

The SDK knows the floating-point 8.3 is inexact, and refuses to use it.

The solution is to use the Decimal class as intended: It should be constructed with a string parameter, not a floating-point one. I.e., use Decimal("8.3") (note the quotes), not Decimal(8.3).

In your code above fixing this is as trivial as changing 8.3 to "8.3", with quotes.

That's the best approach. The other, not as good, approach is to do Decimal(str(8.3))), but be prepared for the potential of inexact representation of the numbers. Moreover, creating a Decimal with a string allows you to create numbers which are simply not supported in Python. For example, Decimal("3.1415926535897932384626433832795028841") will get you 38 decimal digits of precision (the maximum supported by DynamoDB) - something you cannot do in Python floating point.

Nadav Har'El
  • 11,785
  • 1
  • 24
  • 45
  • You say `Decimal(str(8.3)))` is not a good approach, but how else would I get the string representation of my number? I'm usually working with variables and can't just put quotes around the number, i.e. it looks like `Decimal(str(my_float)))`. Is there a way to avoid inexact representation? In my case, it wouldn't make a difference most of the time, but I'm still interested if there's a "proper" way to do this. – Christian Nov 26 '21 at 13:57
  • 1
    Christian, the original question was about an exact constant 8.3, where the right thing is indeed to add quotes and write "8.3". You're right that if this number wasn't a constant, but was calculated in Python using floating-point calculations, then it can already be inexact (in DynamoDB's standards of exactness) - and there is no way to make it more exact. In that case you can indeed use Decimal(str(my_float)) - but be aware that you lost precision in those calculations. – Nadav Har'El Jan 19 '22 at 09:10
7

I ran into this issue as well. The trouble I found with the Decimal(str(my_float)) approach was that str(my_float) will only preserve 16 significant digits, which wasn't enough for us.

Instead, you can construct the Decimal object under a new decimal.Context, which will not raise the [<class 'decimal.Inexact'>, <class 'decimal.Rounded'>] error, since it doesn't have those traps set:

from decimal import Decimal, Context

my_float = 8.3

# 1. Create the context. DynamoDB supports up to 38
# digits of precision.
ctx = Context(prec=38)

# 2. Create the decimal from within the new context, which
# will not raise the Inexact/Rounded error.
my_decimal = ctx.create_decimal_from_float(my_float)

# 3. Save `my_decimal` to DynamoDB without error, and with
# maximum precision preserved.
EPeterson
  • 81
  • 1
  • 4
3

once try this:

ExpressionAttributeValues={
            ':r': Decimal(str(rating)),
            ':p': plot,
            ':a': actors
        },
Sai Sreenivas
  • 1,690
  • 1
  • 7
  • 16
  • can you check the below https://stackoverflow.com/questions/63351210/how-to-extract-the-elements-from-csv-to-json-in-s3 – aysh Aug 11 '20 at 04:12