【Python】小数点以下の値の丸め(切り捨て)

Pythonで小数点以下の数値を切り捨てたいときを考えます。例えば、10.987654321と入力した場合に、小数点2位まで取り出して、10.98と返してくれるような関数を考えます。

# 環境
+ Windows 10
+ python=3.7.3

# 方法① (精度に問題あり)
値を100倍して、Intにして、100で割る

```python:
def round_off_to_two_decimal_places_1(input_val: float) -> float:
    output = int(input_val * 100) / 100
    return output


def round_off_test(round_off_method):
    input_val = 10.987654321
    print(input_val, "->", round_off_method(input_val))
    input_val = 10.12
    print(input_val, "->", round_off_method(input_val))
    input_val = 10.99
    print(input_val, "->", round_off_method(input_val))
    input_val = 10.74297492
    print(input_val, "->", round_off_method(input_val))
    input_val = 10.155
    print(input_val, "->", round_off_method(input_val))
    input_val = 10.585018
    print(input_val, "->", round_off_method(input_val))
    input_val = 10.000000
    print(input_val, "->", round_off_method(input_val))
    input_val = 10.
    print(input_val, "->", round_off_method(input_val))
    print("")


round_off_test(round_off_to_two_decimal_places_1)

```

```python:実行結果
10.987654321 -> 10.98
10.12 -> 10.11
10.99 -> 10.99
10.74297492 -> 10.74
10.155 -> 10.15
10.585018 -> 10.58
10.0 -> 10.0
10.0 -> 10.0
```

良さそうに見えますが、10.12を入力した場合に10.11と返されてしまっています。

# 方法②
文字列として操作する。

```python:
def round_off_to_two_decimal_places_2(input_val: float) -> float:
    str_input_val = str(input_val)
    split_str = str_input_val.split('.')
    if len(split_str[1]) > 2:
        split_str[1] = split_str[1][:2]  # 小数点以下2桁取り出す
    str_output = '.'.join(split_str)
    return float(str_output)


round_off_test(round_off_to_two_decimal_places_2)

```

```python:実行結果
10.987654321 -> 10.98
10.12 -> 10.12
10.99 -> 10.99
10.74297492 -> 10.74
10.155 -> 10.15
10.585018 -> 10.58
10.0 -> 10.0
10.0 -> 10.0
```

力技ですが、一応意図した値は返してくれるようです。

# 方法③
Decimalのquantizeメソッドを使う。

```python:
from decimal import Decimal, ROUND_DOWN


def round_off_to_two_decimal_places_3(input_val: float) -> float:
    TWOPLACES = Decimal(10) ** -2
    output = Decimal(str(input_val)).quantize(TWOPLACES, rounding=ROUND_DOWN)
    return float(output)


round_off_test(round_off_to_two_decimal_places_3)
```

```python:実行結果
10.987654321 -> 10.98
10.12 -> 10.12
10.99 -> 10.99
10.74297492 -> 10.74
10.155 -> 10.15
10.585018 -> 10.58
10.0 -> 10.0
10.0 -> 10.0
```

意図した値が返ってきてます。

### 補足
ContextのtrapsにInexactを設定すると、丸めが発生したことを通知することもできます。

```python:
from decimal import Decimal, ROUND_DOWN, Context, Inexact


def round_off_to_two_decimal_places_4(input_val: float) -> float:
    TWOPLACES = Decimal(10) ** -2
    try:
        output = Decimal(str(input_val)).quantize(
            TWOPLACES, rounding=ROUND_DOWN, context=Context(traps=[Inexact]))
    except Inexact:
        print("値の丸めが発生")
        output = Decimal(str(input_val)).quantize(TWOPLACES,
                                                  rounding=ROUND_DOWN)
    return float(output)


round_off_test(round_off_to_two_decimal_places_4)

```

```python:実行結果
値の丸めが発生
10.987654321 -> 10.98
10.12 -> 10.12
10.99 -> 10.99
値の丸めが発生
10.74297492 -> 10.74
値の丸めが発生
10.155 -> 10.15
値の丸めが発生
10.585018 -> 10.58
10.0 -> 10.0
10.0 -> 10.0
```

# 結果
Pythonで浮動小数点を扱う場合は、Decimalを使うのがよさそうです。

# 参考URL
+ https://docs.python.org/ja/3/tutorial/floatingpoint.html#tut-fp-issues
+ https://docs.python.org/ja/3/library/decimal.html#decimal.Inexact
+ https://www.sejuku.net/blog/73255