web-dev-qa-db-ja.com

float()のrange()

Pythonのfloatに相当するrange()はありますか?

>>> range(0.5,5,1.5)
[0, 1, 2, 3, 4]
>>> range(0.5,5,0.5)

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    range(0.5,5,0.5)
ValueError: range() step argument must not be zero
113
Jonathan

私は組み込み関数を知りませんが、 this のようなものを書くのはあまり複雑ではないはずです。

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

コメントが言及しているように、これは次のような予測不可能な結果を​​もたらす可能性があります。

>>> list(frange(0, 100, 0.1))[-1]
99.9999999999986

期待される結果を得るには、この質問の他の回答の1つを使用するか、@ Tadhgが述べたように、jump引数としてdecimal.Decimalを使用できます。必ずフロートではなく文字列で初期化してください。

>>> import decimal
>>> list(frange(0, 100, decimal.Decimal('0.1')))[-1]
Decimal('99.9')

あるいは:

import decimal

def drange(x, y, jump):
  while x < y:
    yield float(x)
    x += decimal.Decimal(jump)

その後:

>>> list(drange(0, 100, '0.1'))[-1]
99.9
89
kichik

次のいずれかを使用できます。

[x / 10.0 for x in range(5, 50, 15)]

またはラムダ/マップを使用します:

map(lambda x: x/10.0, range(5, 50, 15))
92
Xaerxess

以前はnumpy.arangeを使用していましたが、浮動小数点エラーのため、返される要素の数を制御するためにいくつかの問題がありました。だから今、私はlinspaceを使用します、例えば:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])
68
wim

Pylabにはfrange(実際にはmatplotlib.mlab.frangeのラッパー)があります:

>>> import pylab as pl
>>> pl.frange(0.5,5,0.5)
array([ 0.5,  1. ,  1.5,  2. ,  2.5,  3. ,  3.5,  4. ,  4.5,  5. ])
38
Pat

熱心に評価されました(2.x range):

[x * .5 for x in range(10)]

遅延評価(2.x xrange、3.x range):

itertools.imap(lambda x: x * .5, xrange(10)) # or range(10) as appropriate

代わりに:

itertools.islice(itertools.imap(lambda x: x * .5, itertools.count()), 10)
# without applying the `islice`, we get an infinite stream of half-integers.
10
Karl Knechtel

itertoolsを使用:遅延評価された浮動小数点範囲:

>>> from itertools import count, takewhile
>>> def frange(start, stop, step):
        return takewhile(lambda x: x< stop, count(start, step))

>>> list(frange(0.5, 5, 1.5))
# [0.5, 2.0, 3.5]
8
Ayush

関数 numeric_range をパッケージ more-itertools に追加するのを手伝いました。

more_itertools.numeric_range(start, stop, step)は組み込みの関数範囲のように機能しますが、float、Decimal、Fraction型を処理できます。

>>> from more_itertools import numeric_range
>>> Tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)
5
William Rusnack

このような組み込み関数はありませんが、次の(Python 3コード)を使用して、Pythonが許可する限り安全にジョブを実行できます。

from fractions import Fraction

def frange(start, stop, jump, end=False, via_str=False):
    """
    Equivalent of Python 3 range for decimal numbers.

    Notice that, because of arithmetic errors, it is safest to
    pass the arguments as strings, so they can be interpreted to exact fractions.

    >>> assert Fraction('1.1') - Fraction(11, 10) == 0.0
    >>> assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

    Parameter `via_str` can be set to True to transform inputs in strings and then to fractions.
    When inputs are all non-periodic (in base 10), even if decimal, this method is safe as long
    as approximation happens beyond the decimal digits that Python uses for printing.


    For example, in the case of 0.1, this is the case:

    >>> assert str(0.1) == '0.1'
    >>> assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'


    If you are not sure whether your decimal inputs all have this property, you are better off
    passing them as strings. String representations can be in integer, decimal, exponential or
    even fraction notation.

    >>> assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
    >>> assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
    >>> assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
    >>> assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
    >>> assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

    """
    if via_str:
        start = str(start)
        stop = str(stop)
        jump = str(jump)
    start = Fraction(start)
    stop = Fraction(stop)
    jump = Fraction(jump)
    while start < stop:
        yield float(start)
        start += jump
    if end and start == stop:
        yield(float(start))

いくつかのアサーションを実行して、すべてを検証できます。

assert Fraction('1.1') - Fraction(11, 10) == 0.0
assert Fraction( 0.1 ) - Fraction(1, 10) == Fraction(1, 180143985094819840)

assert str(0.1) == '0.1'
assert '%.50f' % 0.1 == '0.10000000000000000555111512312578270211815834045410'

assert list(frange(1, 100.0, '0.1', end=True))[-1] == 100.0
assert list(frange(1.0, '100', '1/10', end=True))[-1] == 100.0
assert list(frange('1', '100.0', '.1', end=True))[-1] == 100.0
assert list(frange('1.0', 100, '1e-1', end=True))[-1] == 100.0
assert list(frange(1, 100.0, 0.1, end=True))[-1] != 100.0
assert list(frange(1, 100.0, 0.1, end=True, via_str=True))[-1] == 100.0

assert list(frange(2, 3, '1/6', end=True))[-1] == 3.0
assert list(frange(0, 100, '1/3', end=True))[-1] == 100.0

GitHub で利用可能なコード

3
marcotama

numpyなどの依存関係のないソリューション はkichikによって提供されましたが、 浮動小数点演算 により、しばしば予期しない動作をします。 me および blubberdiblub で示されているように、追加の要素が簡単に結果に侵入します。たとえば、naive_frange(0.0, 1.0, 0.1)は、最後の値として0.999...を生成し、合計で11個の値を生成します。

堅牢なバージョンは次のとおりです。

def frange(x, y, jump=1.0):
    '''Range for floats.'''
    i = 0.0
    x = float(x)  # Prevent yielding integers.
    x0 = x
    epsilon = jump / 2.0
    yield x  # yield always first value
    while x + epsilon < y:
        i += 1.0
        x = x0 + i * jump
        yield x

乗算のため、丸め誤差は累積しません。もちろん、epsilonを使用すると、乗算の丸め誤差が発生する可能性がありますが、非常に小さな問題と非常に大きな問題が発生する可能性があります。さて、予想通り:

> a = list(frange(0.0, 1.0, 0.1))
> a[-1]
0.9
> len(a)
10

そして、やや大きい数字で:

> b = list(frange(0.0, 1000000.0, 0.1))
> b[-1]
999999.9
> len(b)
10000000

コードは GitHub Gist としても利用できます。

3
Akseli Palén

なぜ標準ライブラリに浮動小数点範囲の実装がないのですか?

ここのすべての投稿で明らかにしたように、range()の浮動小数点バージョンはありません。とは言っても、range()関数がインデックスとしてよく使用されることを考えると、省略は意味があります(もちろん、これはaccessor)ジェネレーターを意味します。したがって、range(0,40)を呼び出すと、0から40までの値が必要ですが、40自体は含まれません。

インデックスの生成は、インデックスの値と同じくらいの数であると考えると、標準ライブラリでrange()のfloat実装を使用しても意味がありません。たとえば、関数frange(0, 10, 0.25)を呼び出した場合、0と10の両方が含まれることが期待されますが、それにより値が41のベクトルが生成されます。

したがって、用途に応じたfrange()関数は、常に直感に反する動作を示します。インデックス作成の観点から認識される値が多すぎるか、数学的な観点から合理的に返されるべき数が含まれていません。

数学的ユースケース

前述のとおり、numpy.linspace()は数学的な観点でうまく生成を実行します:

numpy.linspace(0, 10, 41)
array([  0.  ,   0.25,   0.5 ,   0.75,   1.  ,   1.25,   1.5 ,   1.75,
         2.  ,   2.25,   2.5 ,   2.75,   3.  ,   3.25,   3.5 ,   3.75,
         4.  ,   4.25,   4.5 ,   4.75,   5.  ,   5.25,   5.5 ,   5.75,
         6.  ,   6.25,   6.5 ,   6.75,   7.  ,   7.25,   7.5 ,   7.75,
         8.  ,   8.25,   8.5 ,   8.75,   9.  ,   9.25,   9.5 ,   9.75,  10.
])

インデックス作成の使用例

インデックス作成の観点から、小数点以下の桁数を指定できるトリッキーな文字列マジックを使用して、少し異なるアプローチを作成しました。

# Float range function - string formatting method
def frange_S (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield float(("%0." + str(decimals) + "f") % (i * skip))

同様に、組み込みのround関数を使用して、小数点以下の桁数を指定することもできます。

# Float range function - rounding method
def frange_R (start, stop, skip = 1.0, decimals = 2):
    for i in range(int(start / skip), int(stop / skip)):
        yield round(i * skip, ndigits = decimals)

簡単な比較とパフォーマンス

もちろん、上記の議論を考えると、これらの関数の使用例はかなり限られています。それにもかかわらず、ここに簡単な比較があります:

def compare_methods (start, stop, skip):

    string_test  = frange_S(start, stop, skip)
    round_test   = frange_R(start, stop, skip)

    for s, r in Zip(string_test, round_test):
        print(s, r)

compare_methods(-2, 10, 1/3)

結果はそれぞれ同じです:

-2.0 -2.0
-1.67 -1.67
-1.33 -1.33
-1.0 -1.0
-0.67 -0.67
-0.33 -0.33
0.0 0.0
...
8.0 8.0
8.33 8.33
8.67 8.67
9.0 9.0
9.33 9.33
9.67 9.67

そして、いくつかのタイミング:

>>> import timeit

>>> setup = """
... def frange_s (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield float(("%0." + str(decimals) + "f") % (i * skip))
... def frange_r (start, stop, skip = 1.0, decimals = 2):
...     for i in range(int(start / skip), int(stop / skip)):
...         yield round(i * skip, ndigits = decimals)
... start, stop, skip = -1, 8, 1/3
... """

>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))
0.024284090992296115

>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))
0.025324633985292166

私のシステムでは、文字列のフォーマット方法が髪の毛で勝っているように見えます。

制限事項

そして最後に、上記の議論からのポイントのデモンストレーションと1つの最後の制限:

# "Missing" the last value (10.0)
for x in frange_R(0, 10, 0.25):
    print(x)

0.25
0.5
0.75
1.0
...
9.0
9.25
9.5
9.75

さらに、skipパラメーターがstop値で割り切れない場合、後者の問題を考えるとあくびをする可能性があります。

# Clearly we know that 10 - 9.43 is equal to 0.57
for x in frange_R(0, 10, 3/7):
    print(x)

0.0
0.43
0.86
1.29
...
8.14
8.57
9.0
9.43

この問題に対処する方法はありますが、結局のところ、最良のアプローチはおそらくNumpyを使用することでしょう。

2
Greenstick

よりシンプルなライブラリレスバージョン

ああ、ちょっと-私は単純なライブラリレス版を放り投げます。自由に改善してください[*]:

def frange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    dy = stop-start
    # f(i) goes from start to stop as i goes from 0 to nsteps
    return [start + float(i)*dy/nsteps for i in range(nsteps)]

基本的な考え方は、nstepsが開始から停止までのステップ数であり、range(nsteps)が常に整数を出力するため、精度が失われないことです。最後のステップは、[0..nsteps]を[start..stop]に線形にマッピングすることです。

編集する

alancalvitti のように、系列に正確な有理数表現を持たせたい場合は、常に Fractions を使用できます。

from fractions import Fraction

def rrange(start=0, stop=1, jump=0.1):
    nsteps = int((stop-start)/jump)
    return [Fraction(i, nsteps) for i in range(nsteps)]

[*]特に、frange()はジェネレーターではなくリストを返します。しかし、私のニーズには十分でした。

1
fearless_fool

私は、100分の1を超える小数点以下の桁数のない倍精度浮動小数点数の範囲のタプルを返す関数を作成しました。それは単に、文字列のような範囲値を解析し、超過分を分割するだけの問題でした。 UI内から選択する範囲を表示するために使用します。他の誰かがそれを役に立つと思うことを願っています。

def drange(start,stop,step):
    double_value_range = []
    while start<stop:
        a = str(start)
        a.split('.')[1].split('0')[0]
        start = float(str(a))
        double_value_range.append(start)
        start = start+step
    double_value_range_Tuple = Tuple(double_value_range)
   #print double_value_range_Tuple
    return double_value_range_Tuple
0
chris mcinnis

ここには、負のステップ、間違った開始、停止などの単純なEdgeのケースを処理しないいくつかの回答があります。これらのケースの多くをネイティブrange()と同じ動作を正しく処理するバージョンを次に示します。

def frange(start, stop=None, step=1):
  if stop is None:
    start, stop = 0, start
  steps = int((stop-start)/step)
  for i in range(steps):
    yield start
    start += step  

これにより、ネイティブrangeと同様にstep = 0がエラーになることに注意してください。違いの1つは、ネイティブ範囲はインデックス可能でリバーシブルなオブジェクトを返しますが、上記のオブジェクトはそうではないことです。

このコードで遊ぶ とテストケースをここでできます。

0
Shital Shah