DynamoDBで数値データを扱う際の処理と、decimal型とfloat型の実行時間の比較

DynamoDBに`Number`型で数値データを登録して、計算しようと思うと次のようなエラーが出た

```text:error
unsupported operand type(s) for *: 'decimal.Decimal' and 'float'
```

どうやら、DynamoDBの`Number`型は`Decimal`型らしい。

- [decimal --- 十進固定及び浮動小数点数の算術演算](https://docs.python.org/ja/3/library/decimal.html)

`int`や`float`を使うことで、それぞれの型に変換できる。


### 実験
ところで、この記事によれば、decimal型はfloat型に比べて処理が重いらしい。

- [【Python3】Decimalモジュールを使った、精度の高い小数点以下の数値計算](http://automaprog.hatenadiary.jp/entry/2017/04/09/180520)

実行時間はAWSの料金を決定する非常に重要なファクターだ。
そこで、以下のようなプログラムを作成して実験してみた。
実験の内容は以下の通り

- floatのみで計算
- decimalのみで計算
- decimalをfloatにして計算
- decimalをstrに変換し、それをfloatにして変換[^1]

```python:test-decimal.py
import time
from decimal import *
import numpy as np
import matplotlib.pyplot as plt

def getAverageTime(N:int):
    print("do "+str(N)+" times")
    ary1=np.array([])
    #froatのみ
    for i in range(N):
        start=time.time()
        result=float(1)/float(3)*float(10)/float(3)
        elapsed_time = time.time() - start
        ary1=np.append(ary1,float(elapsed_time))

    #decimalのみ
    ary2=np.array([])
    for i in range(N):
        start=time.time()
        result=Decimal("1")/Decimal("3")*Decimal("10")/Decimal("3")
        elapsed_time = time.time() - start
        ary2=np.append(ary2,float(elapsed_time))

    #decimal→float
    ary3=np.array([])
    for i in range(N):
        start=time.time()
        result=float(Decimal("1"))/float(Decimal("3"))*float(Decimal("10"))/float(Decimal("3"))
        ary3=np.append(ary3,float(elapsed_time))
    
    #decimal→str→float
    ary4=np.array([])
    for i in range(N):
        start=time.time()
        result=float(str(Decimal("1")))/float(str(Decimal("3")))*float(str(Decimal("10")))/float(str(Decimal("3")))
        elapsed_time = time.time() - start
        ary4=np.append(ary4,float(elapsed_time))
    
    print ("average time of float:                     {0}".format(np.average(ary1)) + "[sec]")
    print ("average time of decimal:                   {0}".format(np.average(ary2)) + "[sec]")
    print ("average time of decimal to float:          {0}".format(np.average(ary3)) + "[sec]")
    print ("average time of decimal to float with str: {0}".format(np.average(ary4)) + "[sec]")
    label=["float","decimal","decimal to float", "decimal to float \nwith str"]
    left=np.array([1,2,3,4])
    height=np.array([np.average(ary1),np.average(ary2),np.average(ary3),np.average(ary4)])
    plt.bar(left,height,tick_label=label,align="center")
    plt.title("do "+str(N)+" times")
    print("\n")
    plt.savefig('figure' + str(N)+'.png')
    plt.clf()

if __name__=="__main__":
    getAverageTime(1)
    getAverageTime(3)
    getAverageTime(100)
    getAverageTime(1000)
```

### 結果
以下の結果が得られた

- １回実行時
![figure1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251426/89c2d38f-03fc-2a79-79fd-92e16285ff06.png)

- ３回実行時
![figure3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251426/d3229750-e839-b449-e48d-3a97ae8ccf91.png)



- 100回実行時
![figure100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251426/413a73d7-cffe-a9a7-dbe7-3768eda6959b.png)



- 10000回実行時
![figure1000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251426/bfad3cd1-d2a8-23b0-7f52-ce19dbae84c9.png)



### 考察
いずれの実行方法でも、floatは最も早く計算が終了している。
１回のみ実行した場合はdecimal型をfloat型に変換をしてもしなくても実行時間に変化はない。
一方、実行回数が多い場合はdecimal型からfloat型にすることで、計算時間を大幅に短縮することができた。
decimalからstrを介してfloatに変換することで、値の正確性は保てるものの計算時間はどの実行方法よりも多いことがわかった。


[^1]: [このサイト](http://automaprog.hatenadiary.jp/entry/2017/04/09/180520)で紹介されている手法で、decimalの持つ値の正確性を保ちつつ、floatに変換することができる手法