[解決!Python]range関数と同様に、浮動小数点数値の等差数列を作成するには解決!Python

range関数は整数値を要素とする等差数列を作成できるが、浮動小数点数値を要素として同様な数列を得る方法を紹介する。

» 2022年05月31日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「解決!Python」のインデックス

連載目次

# range関数で得た値に係数をかける
for num in [num * 0.1 for num in range(0, 5)]:
    print(num)
# 出力結果:
#0.0
#0.1
#0.2
#0.30000000000000004
#0.4

# 浮動小数点の誤差をround関数で丸める
for num in [round(num * 0.1, 1) for num in range(0, 5)]:
    print(num)
# 出力結果:見た目は丸められているが、浮動小数点数値なので誤差は含まれている
#0.0
#0.1
#0.2
#0.3
#0.4

# 開始値、終了値、ステップ値に浮動小数点数値を受け取る関数の定義
def frange(start, end , step):
    if step == 0:
        raise ValueError('step must not be zero')

    start = float(start)
    end = float(end)
    step = float(step)

    # range関数と同様な振る舞いにする
    if abs(step) > abs(start - end):
        return [start]
    if step > 0 and end - start < 0:
        return []
    elif step < 0 and end - start > 0:
        return []

    exp = len(str(step).split('.')[1])  # 丸める際に使用する桁数
    result = [start]
    val = start
    if step > 0:
        while (val := round(val + step, exp)) < end:
            result.append(val)
    else:
        while (val := round(val + step, exp)) > end:
            result.append(val)
    return result

for num in frange(0.0, 0.5, 0.1):
    print(num)
# 出力結果:
#0.0
#0.1
#0.2
#0.3
#0.4

# range関数を使うバージョン
def frange(start, end, step):
    if step == 0:
        raise ValueError('step must not be zero')

    start = float(start)
    end = float(end)
    step = float(step)
    if abs(step) >= abs(start - end):
        return [start]

    exp = len(str(step).split('.')[1])  # ステップ値から整数化に使用する値を得る
    start = int(start * 10 ** exp)
    end = int(end * 10 ** exp)
    step = int(step * 10 ** exp)

    result = [round(val * 10 ** -exp, exp) for val in range(start, end, step)]
    return result

for num in frange(0.0, 0.5, 0.1):
    print(num)
# 出力結果:
#0.0
#0.1
#0.2
#0.3
#0.4

import numpy as np

for num in np.arange(0.0, 0.5, 0.1):
    print(num)
# 出力結果
#0.0
#0.1
#0.2
#0.30000000000000004
#0.4

for num in np.linspace(0.0, 0.5, 5):  # 0.0〜1.0の範囲を5要素で等分割する
    print(num)
# 出力結果:
#0.0
#0.125
#0.25
#0.375
#0.5

for num in np.linspace(0.0, 0.5, 6):  # 0.0〜0.5の範囲を6要素で等分割する
    print(num)
# 出力結果:
#0.0
#0.1
#0.2
#0.30000000000000004
#0.4
#0.5

for num in np.linspace(0.0, 0.5, 5, endpoint=False):  # 終了値を含まない
    print(num)
# 出力結果:
#0.0
#0.1
#0.2
#0.30000000000000004
#0.4


range関数と同様な処理を浮動小数点数値でも

 Pythonのrange関数は、指定した値を基に等差数列を表すrangeオブジェクトを生成するものだ。

 例えば、これは初項0、等差2、最大値10(を含まない範囲)の等差数列を表すrangeオブジェクトを作成して、その要素を表示するコードだ。

for num in range(0, 10, 2):
    print(num)


 range関数は便利だが、浮動小数点数値を要素とする等差数列が作成できないのが残念なところともいえる。とはいえ、forとrange関数を組み合わせ、得られた値に10-xをかけることで(xは正数)、求める処理はだいたい行える。

 例えば、以下は初項0.0、公差0.1、終了値0.5(を含まない)範囲の等差数列を表示するコードである。

for num in range(0, 5):
    num *= 0.1
    print(num)
# 出力結果:
#0.0
#0.1
#0.2
#0.30000000000000004
#0.4


 等差数列を含むリストが必要であれば、リスト内包表記を使って次のように書いてもよいだろう(出力は省略)。

flist = [num * 0.1 for num in range(0, 5)]:
for num in flist:
    print(num)


 ただし、上の出力結果を見ても分かる通り、浮動小数点数の演算には多くの場合、誤差が含まれることには注意が必要だ。以下のように、round関数の第2引数に小数点以下の有効桁数を指定することで丸めることも可能だが、誤差が含まれることには変わりはない。

for num in [round(num * 0.1, 1) for num in range(0, 5)]:
    print(num)
# 出力結果:見た目は丸められているが、浮動小数点数値なので誤差は含まれている
#0.0
#0.1
#0.2
#0.3
#0.4


 これは上のコードのprint関数を修正してみると分かる。

for num in [round(num * 0.1, 1) for num in range(0, 5)]:
    print(f'{num:.20f}')
# 出力結果:
#0.00000000000000000000
#0.10000000000000000555
#0.20000000000000001110
#0.29999999999999998890
#0.40000000000000002220


 ここまでに見てきたように、自分で初項、公差、終了値がどうなるかを考えてfor文を書いたり、リスト内包表記を記述したりするのは意外に面倒くさい。そこで、range関数と同様なパラメーターを持つfrange関数を定義すると、次のようなコードが考えられる(frange関数の「f」は「float」を意味する)。

def frange(start, end , step):
    if step == 0:
        raise ValueError('step must not be zero')

    start = float(start)
    end = float(end)
    step = float(step)

    # range関数と同様な振る舞いにする
    if abs(step) > abs(start - end):
        return [start]
    if step > 0 and end - start < 0:
        return []
    elif step < 0 and end - start > 0:
        return []

    exp = len(str(step).split('.')[1])  # 丸める際に使用する桁数
    result = [start]
    val = start
    if step > 0:
        while (val := round(val + step, exp)) < end:
            result.append(val)
    else:
        while (val := round(val + step, exp)) > end:
            result.append(val)
    return result


Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。