オーバーフローとアンダーフローとは?違いと具体例をPythonで確認(np.uint8(-1)のOverflowErrorも解説)

オーバーフローは計算結果が型で表せる最大値(絶対値)を超えること、アンダーフローは0に近すぎて表せず0などに丸められることです。8ビットの符号なし整数なら256通りしか表せないため、250+10は260ではなく4になり、浮動小数点数ではとても小さい値どうしの掛け算が0.0になります。

この記事では、その仕組みを図と表で整理したあと、Python 3.14.8とNumPy 2.5.4で実際に動かした結果を載せます。NumPy 2系では np.uint8(-1) がOverflowErrorになるなど、古い解説と挙動が違う部分があるので、そこも実行結果で確かめます。

オーバーフローとアンダーフローの違い

オーバーフローアンダーフロー
意味結果が表せる範囲の上限(絶対値の最大)を超える結果が0に近すぎて、表せる最小の大きさを下回る
主に起きる型整数型・浮動小数点数型の両方浮動小数点数型
起きたときの結果の例整数は折り返す(250+10→4)、浮動小数点数はinfになる0.0になる(1e-200×1e-200→0.0)
よくある場面カウンタ、累積和、指数関数、32bit整数の上限確率の掛け算、指数関数の負の大きな値

なお「アンダーフロー」という言葉は、整数で最小値を下回る(0から1を引くと255になる)ことに使われる場合もあります。厳密にはこれも表せる範囲を外れるので、整数の場合はまとめて「オーバーフロー」と呼ぶ説明も多く見られます。

整数のオーバーフロー:256で折り返す

ビット数と表せる範囲

nビットの整数は2のn乗通りの値しか表せません。8ビットなら256通りなので、「256 オーバーフロー」という言葉がよく使われます。

型符号なしの範囲符号付きの範囲
8ビット0〜255-128〜127
16ビット0〜65,535-32,768〜32,767
32ビット0〜4,294,967,295-2,147,483,648〜2,147,483,647
64ビット0〜18,446,744,073,709,551,615-9,223,372,036,854,775,808〜9,223,372,036,854,775,807

8ビットの符号なし整数で250+10を計算すると、2進数では9桁目にあふれた1が捨てられ、260を256で割った余りの4が残ります。時計の針が12を過ぎると1に戻るのと同じ考え方です。

NumPyで確かめる

Pythonの int は桁あふれしませんが、NumPyの整数型はビット数が決まっているので、オーバーフローを再現できます。

import numpy as np

x = np.uint8(250)
y = np.uint8(10)
print(x + y)          # 260 は 8ビットに入らない

arr = np.array([250, 251, 252], dtype=np.uint8)
print(arr + 10)       # 配列はそのまま折り返す

big = np.int32(2147483647)
print(big + np.int32(1))

実行結果(標準出力):

4
[4 5 6]
-2147483648

スカラー同士の計算では、標準エラーに RuntimeWarning: overflow encountered in scalar add という警告が出ました。一方、配列の計算(2行目)では警告が出ずに黙って折り返しています。32ビット符号付き整数の最大値2,147,483,647に1を足すと、最小値の-2,147,483,648に飛びます。NumPyの公式ドキュメントも、NumPyの整数型は固定サイズのためオーバーフローが起き、サイズが柔軟なPythonの int とは挙動が大きく違うと注意しています(NumPy: Data types)。

np.uint8(-1) はOverflowErrorになる

NumPy 1.26.4で試すと np.uint8(-1) は255を返し、「将来は失敗するようになる」というDeprecationWarningが出ました。NumPy 2系ではその予告どおり、範囲外のPythonの整数を渡すとエラーになります。

import numpy as np

try:
    np.uint8(-1)
except OverflowError as e:
    print("OverflowError:", e)

try:
    np.uint8(300)
except OverflowError as e:
    print("OverflowError:", e)

print(np.array(-1).astype(np.uint8))   # 明示的な型変換は折り返す

実行結果:

OverflowError: Python integer -1 out of bounds for uint8
OverflowError: Python integer 300 out of bounds for uint8
255

この変更は、Pythonのスカラーの扱いを定めたNEP 50に沿ったものです。NEP 50の表では、uint8(1) + 300 は例外になり、uint8(100) + 200 はuint8の44と警告になると整理されています(NEP 50)。古いコードで255になることを前提にしている場合は、astype で明示的に変換するよう書き直す必要があります。

C言語やJavaではどうなるか

Javaでは、int同士の足し算がオーバーフローすると、結果は数学的な和の下位ビットになり、符号が反転すると言語仕様に書かれています(Java言語仕様 15章)。つまり上の np.int32 と同じように-2,147,483,648へ折り返し、例外は出ません。C言語では、式の評価中に結果がその型で表せる範囲を外れた場合の動作は未定義とされています(C11規格の委員会草案N1570の6.5節、N1570)。符号付き整数のオーバーフローの結果に頼ってはいけない点がJavaと異なります。

浮動小数点数のオーバーフロー:infかエラーになる

Pythonの float は64ビットの倍精度浮動小数点数で、最大値は約1.8×10の308乗です。これを超えると、計算の種類によって結果が変わります。

import sys, math

print(pow(2, 100))              # Pythonのintは桁あふれしない
print(sys.float_info.max)
print(sys.float_info.max * 10)  # 掛け算は inf になる

try:
    math.exp(1000)
except OverflowError as e:
    print("math.exp:", e)

try:
    pow(10.0, 400)
except OverflowError as e:
    print("pow(10.0, 400):", e)

実行結果:

1267650600228229401496703205376
1.7976931348623157e+308
inf
math.exp: math range error
pow(10.0, 400): (34, 'Result too large')

掛け算は黙ってinf(無限大)になりますが、math.exp(1000) と pow(10.0, 400)(10.0の400乗)はOverflowErrorになります。Pythonの公式ドキュメントにも、mathモジュールは exp(1000.0) のようにオーバーフローする結果に対してOverflowErrorを送出すると書かれています(math — 数学関数)。一方、NumPyの np.exp(1000) は警告を出してinfを返します。

アンダーフロー:小さすぎて0.0になる

import sys
import numpy as np

print(sys.float_info.min)       # 正規化数の最小値
print(5e-324)                   # 非正規化数の最小値
print(5e-324 / 2)               # これ以上小さくすると 0.0
print(1e-200 * 1e-200)          # 1e-400 は表せない
print(np.exp(-1000))

実行結果:

2.2250738585072014e-308
5e-324
0.0
0.0
0.0

倍精度の正規化数で表せる最小の正の値は約2.2×10のマイナス308乗です。それより小さい値は、精度を落とした「非正規化数」として約5×10のマイナス324乗まで表せますが、それも下回ると0.0になります。1e-200 * 1e-200 は数学的には10のマイナス400乗ですが、結果は0.0です。整数の折り返しと違って警告も例外も出ないため、気づきにくいのがアンダーフローの怖いところです。

実践で困る例と対策

ソフトマックス関数は最大値を引いてから計算する

機械学習でよく使うソフトマックス関数は指数関数を使うため、オーバーフローとアンダーフローの両方が起きます。

import numpy as np

def softmax_naive(x):
    e = np.exp(x)
    return e / e.sum()

def softmax_stable(x):
    e = np.exp(x - x.max())     # 最大値を引いてから exp
    return e / e.sum()

x = np.array([1000.0, 1001.0, 1002.0])
print(softmax_naive(x))
print(softmax_stable(x))

y = np.array([-1000.0, -999.0, -998.0])
print(softmax_naive(y))
print(softmax_stable(y))

実行結果(標準出力):

[nan nan nan]
[0.09003057 0.24472847 0.66524096]
[nan nan nan]
[0.09003057 0.24472847 0.66524096]

大きな入力ではexpがinfになりinf÷infでnanに、負の大きな入力ではexpがすべて0.0になり0÷0でnanになります。最大値を引いておけば指数の中身が0以下になり、どちらの場合も正しい確率が出ます。ソフトマックスは全体に同じ数を足し引きしても値が変わらない性質があるので、この変形で答えは変わりません。

確率の掛け算は対数の足し算に置き換える

import math

probs = [1e-5] * 80              # 1e-5 を80回掛ける(真値は 1e-400)
p = 1.0
for q in probs:
    p *= q
print(p)

log_p = sum(math.log(q) for q in probs)
print(log_p)
print(log_p / math.log(10))      # 常用対数に直すと -400

実行結果:

0.0
-921.0340371976183
-400.0

そのまま掛けると0.0になりますが、対数の和なら-921.03…という普通の大きさの数で扱えます。確率を比べるだけなら、対数のまま大小を比べれば十分です。

オーバーフローを例外にして早めに気づく

import numpy as np

with np.errstate(over="raise"):
    try:
        np.uint8(250) + np.uint8(10)
    except FloatingPointError as e:
        print("FloatingPointError:", e)

a = np.array([200, 100], dtype=np.uint8)
print(a.sum(), a.sum(dtype=np.uint8))
print(np.iinfo(np.uint8).max, np.iinfo(np.int32).min)

実行結果:

FloatingPointError: overflow encountered in scalar add
300 44
255 -2147483648

np.errstate(over="raise") を使うと、警告で済んでいたオーバーフローを例外として止められます。また sum() は小さい整数型を自動で大きい型に広げて合計するので300になりますが、dtype=np.uint8 を指定すると44に折り返します。型の上限と下限は np.iinfo(浮動小数点数は np.finfo)で確認できます。

対策のまとめ

  • 扱う値の最大値を見積もり、足りる型を選ぶ(迷ったらint64やPythonの int)
  • 指数関数は最大値を引く、確率は対数で扱うなど、式を変形する
  • 開発中は np.errstate で例外にして、黙った折り返しを見逃さない
  • 型変換は astype で明示し、範囲外の値をどう扱うかを決めておく

よくある質問

Q. オーバーフローとアンダーフローの違いは何ですか?

オーバーフローは計算結果が型で表せる最大の大きさを超えること、アンダーフローは結果が0に近すぎて表せず、0などに丸められることです。オーバーフローは整数と浮動小数点数の両方で、アンダーフローは主に浮動小数点数で起きます。

Q. np.uint8(-1) がOverflowErrorになるのはなぜですか?

NumPy 2系では、型の範囲に入らないPythonの整数を渡すと例外を出す仕様になったためです。255が欲しい場合は np.array(-1).astype(np.uint8) のように明示的に変換します。

Q. 256でオーバーフローするのはなぜですか?

8ビットで表せるのは2の8乗=256通りだからです。符号なしなら0〜255までで、256以上になると上位のビットが捨てられ、256で割った余りが残ります。

Q. Pythonの整数はオーバーフローしないのですか?

Pythonのintは必要に応じて桁数を増やすので、整数の計算では桁あふれしません。ただしfloatやNumPyの固定サイズの型ではオーバーフローやアンダーフローが起きます。

【現役エンジニア歓迎】プログラミング学習お悩み相談会

【情報I】受験対策・お悩み相談会(オンライン・無料)

【オンライン無料】ゼロから始めるPython爆速講座

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら