「アリスメティックエクセプション」とは：「アリスの森」と関係あるのか...? PythonのDecimal除算エラーで解説

# 概要
* PythonのDecimalエラーに遭遇した時、「アリスメティックエクセプション」という用語を知ったので、今回はその用語の意味を調べてみました。

# 「アリスメティックエクセプション」とは
* 「アリスメティックエクセプション」「アリスメティック例外 (arithmetic exception)」とは、プログラムが実行される過程で算術演算子 (+、-、*、/ など) を使用した際に、予期せぬエラーによりスローされる例外のことを指します。元々はJava例外が起源?なのかな。Pythonではこういった例外クラスのことを「組み込み例外」と呼びます。
* 一般的なアリスメティック例外には、以下が含まれるようです。
    * `ZeroDivisionError`: ゼロ除算が発生した場合に発生する例外
    * `OverflowError`: 計算結果が、表現可能な範囲を超えた場合に発生する例外
    * `FloatingPointError`: 浮動小数点数演算において、計算結果が不正確な値になる場合に発生する例外
    * `ValueError`: 数値型以外のデータ型による算術演算が発生した場合に発生する例外
    * `TypeError`: 異なるデータ型同士の算術演算が発生した場合に発生する例外


### 「アリスの森」と関係あるの？
* 「アリスメティック (arithmetic)」とは、数学的な四則演算を意味する言葉です。つまり、アリスメティック例外は、四則演算を行う際に発生する例外を指します。
* 一部では、「アリスの森」に迷い込んでしまったときと同じような状況と、計算が狂ってしまうことが似ている状況のためか、「アリスメティック例外」と「アリス」を掛けているとも聞いたことがあります。
    * 「アリスの森」：ルイス・キャロルの『不思議の国のアリス』という小説に登場する架空の場所
* 「アリスの森に迷い込む」というのが理解できない出来事に遭遇することを表現しているのに対し、「アリスメティック例外」は、予期しない入力によってプログラムが正常に処理できなくなることを表現しています。ある意味、どちらも迷い込んでしまうという意味では同じなのでその「アリス」と掛けて広まった？のかもしれません。（英語ではAliceなので、多分日本だけだと思いますが...）

# PythonのDecimalモジュールで試してみる
* Pythonでは、decimal モジュールを使用して高精度の数値演算を行う場合には、ゼロによる除算によって例外が発生することがあります。
    * 例えば、以下のようなコードです。

```py
from decimal import Decimal

a = Decimal('10')
b = Decimal('0')
c = a/b
```

* Decimal型の変数aに10を割り当て、Decimal型の変数bに0を割り当て、aをbで割った値をDecimal型の変数cに割り当てようとしています。しかし、ゼロによる除算が発生するため、このコードはアリスメティック例外を引き起こします。
* Python対話モードの場合でも、`decimal.DivisionByZero`と言うエラーが表示されわかりやすいと思います。

```
>>> from decimal import Decimal
>>> Decimal(40)/Decimal(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
decimal.DivisionByZero: [<class 'decimal.DivisionByZero'>]
```

* アリスメティック例外を回避するには、例外処理を実装する必要があります。以下のように `try-except`文を使用して例外をキャッチして処理することができます。Pythonではゼロ除算による例外は`ZeroDivisionError`という名前で定義されています。

```py
from decimal import Decimal

a = Decimal('10')
b = Decimal('0')
try:
    c = a/b
except ZeroDivisionError:
    print('ゼロによる除算が発生しました。')
```

* `except ZeroDivisionError:`を単に`except:`としても例外を捕捉することはできますが、何の例外を補足したのか不明確であるため一般的には推奨されていません。

### ArithmeticError
* 算術上の様々なエラーに対して送出される組み込み例外のことを`ArithmeticError`と言います。
* `OverflowError`, `ZeroDivisionError`, `FloatingPointError`の基底クラスです。先ほどの`ZeroDivisionError`の親例外が`ArithmeticError`ですね。
    * 階層構造の詳細はこちら：[組み込み例外 — Python 3.11.3 ドキュメント](https://docs.python.org/ja/3/library/exceptions.html)
    * 例外処理についてはこちらも：[例外処理](https://www.python.ambitious-engineer.com/archives/374)　
* 親例外を指定すれば、継承した例外（サブクラス）も捕捉できます。ただし、上述の通り、情報の欠落があったり、発生原因を個別に知ることができなくなったりするので、使用の是非についてはよくご検討ください。

## エラーと例外の違いは？
* エラーは予期しない問題全般を指し、例外はプログラムの実行中に発生する予期しない問題を指します。
    * 「エラー（Error）」は、プログラムが実行される過程で発生した、予期せぬ問題を指します。エラーが発生すると、プログラムは実行を中断し、エラーの内容を報告します。一般的に、エラーはプログラマが事前に予想して対処すべきものとされています。
    * 「例外（Exception）」は、プログラムの実行中に発生するエラーの一種です。例外が発生した場合、プログラムは例外をキャッチし、適切な処理を行います。例外は、プログラムの信頼性を高めるために使用されることが多く、例外処理により、プログラムが停止することなく続行することができます。
* エラーが予期せぬ実行時エラー全般であるのに対して、例外は特定の構文や関数によって投げられる実行時エラーのことを指します。Pythonでは、プログラム実行中に起こるエラーは全て例外として扱われ、`try-except`構文を用いることで、例外処理を実装することができるわけですね。

## Pythonで定義されている、他の代表的な「例外」
* `ValueError`：値が不正である場合に発生する例外
* `TypeError`：型が不正である場合に発生する例外
* `IndexError`：インデックスが範囲外である場合に発生する例外
* `KeyError`：辞書のキーが存在しない場合に発生する例外
* `FileNotFoundError`：ファイルが見つからない場合に発生する例外
* `ImportError`：モジュールのインポートに失敗した場合に発生する例外