Python浮動小数点数の誤差対応比較:math.isclose()の使い方を徹底解説

 

浮動小数点数比較の問題

浮動小数点数の計算では、内部表現の制約により微小な誤差が生じます。この誤差により、理論上同じ値になるはずの計算結果が等価判定で異なる値として扱われる問題があります。

# 基本的な誤差の例
result = 0.1 + 0.2
expected = 0.3

print(f"0.1 + 0.2 = {result}")
print(f"期待値: {expected}")
print(f"等価判定: {result == expected}")  # False
print(f"差: {result - expected}")

math.isclose()関数の基本使用法

Python 3.5で導入されたmath.isclose()は、浮動小数点数の誤差を考慮した比較を行う関数です。

import math

# 基本的な使用法
a = 0.1 + 0.2
b = 0.3

print(f"通常の比較: {a == b}")
print(f"isclose比較: {math.isclose(a, b)}")

# より複雑な例
result = (0.1 + 0.1 + 0.1) - 0.3
print(f"計算結果: {result}")
print(f"ゼロとの比較: {math.isclose(result, 0.0)}")

math.isclose()のパラメータ詳解

rel_tol(相対許容誤差)

import math

# 相対許容誤差の設定
a = 1000000.1
b = 1000000.2

# デフォルト(1e-09)
print(f"デフォルト: {math.isclose(a, b)}")

# 許容誤差を緩める
print(f"rel_tol=1e-6: {math.isclose(a, b, rel_tol=1e-6)}")
print(f"rel_tol=1e-7: {math.isclose(a, b, rel_tol=1e-7)}")

# 実際の相対誤差を計算
rel_error = abs(a - b) / max(abs(a), abs(b))
print(f"実際の相対誤差: {rel_error}")

abs_tol(絶対許容誤差)

import math

# 絶対許容誤差の設定
small_a = 1e-10
small_b = 2e-10

# 相対許容誤差のみ(デフォルト)
print(f"rel_tolのみ: {math.isclose(small_a, small_b)}")

# 絶対許容誤差を設定
print(f"abs_tol=1e-10: {math.isclose(small_a, small_b, abs_tol=1e-10)}")
print(f"abs_tol=1e-9: {math.isclose(small_a, small_b, abs_tol=1e-9)}")

# ゼロとの比較では絶対許容誤差が重要
zero_like = 1e-15
print(f"ゼロとの比較: {math.isclose(zero_like, 0.0, abs_tol=1e-14)}")

実用的な使用例

三角関数の計算

import math

# 三角関数の計算例
angle = math.pi / 6  # 30度
sin_result = math.sin(angle)
expected = 0.5

print(f"sin(π/6) = {sin_result}")
print(f"期待値: {expected}")
print(f"通常比較: {sin_result == expected}")
print(f"isclose比較: {math.isclose(sin_result, expected)}")

# より複雑な三角関数
complex_result = math.sin(math.pi)**2 + math.cos(math.pi)**2
print(f"sin²π + cos²π = {complex_result}")
print(f"1との比較: {math.isclose(complex_result, 1.0)}")

反復計算での誤差累積

import math

# 反復計算での誤差例
def calculate_series(n):
    result = 0.0
    for i in range(n):
        result += 0.1
    return result

# 異なる方法で同じ値を計算
method1 = calculate_series(10)
method2 = 1.0
method3 = 0.1 * 10

print(f"反復加算: {method1}")
print(f"直接代入: {method2}")
print(f"乗算: {method3}")

print(f"method1 == method2: {method1 == method2}")
print(f"isclose(method1, method2): {math.isclose(method1, method2)}")

カスタム比較関数の作成

用途別の比較関数

import math

def financial_close(a, b):
    """金融計算用(小数点以下2桁程度)"""
    return math.isclose(a, b, abs_tol=1e-2, rel_tol=1e-5)

def scientific_close(a, b):
    """科学計算用(高精度)"""
    return math.isclose(a, b, abs_tol=1e-15, rel_tol=1e-12)

def engineering_close(a, b):
    """工学計算用(中程度精度)"""
    return math.isclose(a, b, abs_tol=1e-6, rel_tol=1e-6)

# テスト
test_pairs = [
    (1.001, 1.002),
    (1e-10, 2e-10),
    (1000.0001, 1000.0002)
]

for a, b in test_pairs:
    print(f"値: {a}, {b}")
    print(f"  金融用: {financial_close(a, b)}")
    print(f"  科学用: {scientific_close(a, b)}")
    print(f"  工学用: {engineering_close(a, b)}")

リストや配列での一括比較

numpy配列との比較

import math

def compare_lists(list1, list2, **kwargs):
    """リストの要素を一括比較"""
    if len(list1) != len(list2):
        return False
    
    for a, b in zip(list1, list2):
        if not math.isclose(a, b, **kwargs):
            return False
    return True

# テスト用データ
calc_results = [0.1 + 0.2, 0.2 + 0.3, 0.3 + 0.4]
expected = [0.3, 0.5, 0.7]

print(f"通常比較: {calc_results == expected}")
print(f"isclose一括比較: {compare_lists(calc_results, expected)}")

# 個別確認
for i, (calc, exp) in enumerate(zip(calc_results, expected)):
    print(f"要素{i}: {math.isclose(calc, exp)}")

注意すべき特殊ケース

無限大とNaNの扱い

import math

# 無限大の比較
inf1 = float('inf')
inf2 = float('inf')
print(f"inf同士: {math.isclose(inf1, inf2)}")

# NaNの比較
nan1 = float('nan')
nan2 = float('nan')
print(f"nan同士: {math.isclose(nan1, nan2)}")  # False

# infとnanの判定
def safe_isclose(a, b, **kwargs):
    """NaNやinfを考慮した安全な比較"""
    if math.isnan(a) or math.isnan(b):
        return False
    if math.isinf(a) and math.isinf(b):
        return a == b  # 同じ符号のinfかチェック
    return math.isclose(a, b, **kwargs)

# テスト
test_cases = [
    (float('inf'), float('inf')),
    (float('-inf'), float('-inf')),
    (float('nan'), float('nan')),
    (1.0, 1.0000000001)
]

for a, b in test_cases:
    print(f"{a}, {b}: {safe_isclose(a, b)}")

パフォーマンスの比較

import math
import time

def benchmark_comparison():
    """比較方法のパフォーマンステスト"""
    a = 0.1 + 0.2
    b = 0.3
    iterations = 1000000
    
    # 通常の等価比較
    start = time.time()
    for _ in range(iterations):
        result = a == b
    normal_time = time.time() - start
    
    # isclose比較
    start = time.time()
    for _ in range(iterations):
        result = math.isclose(a, b)
    isclose_time = time.time() - start
    
    print(f"通常比較: {normal_time:.6f}秒")
    print(f"isclose比較: {isclose_time:.6f}秒")
    print(f"倍率: {isclose_time / normal_time:.2f}倍")

benchmark_comparison()

まとめ

math.isclose()は浮動小数点数の誤差を考慮した比較において非常に有用な関数です。rel_tolabs_tolパラメータを適切に設定することで、用途に応じた精度での比較が可能になります。特に科学計算、金融計算、工学計算などの分野では、通常の等価演算子(==)の代わりにmath.isclose()を使用することで、より堅牢なプログラムを作成できます。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

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