Range () для поплавков - PullRequest
       47

Range () для поплавков

115 голосов
/ 01 сентября 2011

Есть ли range() эквивалент для чисел с плавающей точкой в ​​Python?

>>> 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

Ответы [ 16 ]

94 голосов
/ 01 сентября 2011

Вы можете использовать:

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

или используйте лямбду / карту:

map(lambda x: x/10.0, range(5, 50, 15))
91 голосов
/ 01 сентября 2011

Я не знаю встроенной функции, но написание такой как this не должно быть слишком сложным.

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

Как отмечается в комментариях, этоможет привести к непредсказуемым результатам, таким как:

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

Чтобы получить ожидаемый результат, вы можете использовать один из других ответов в этом вопросе или, как упоминалось @Tadhg, вы можете использовать decimal.Decimal в качестве jumpаргумент.Обязательно инициализируйте его строкой, а не плавающей точкой.

>>> 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
68 голосов
/ 01 сентября 2011

Раньше я использовал numpy.arange, но у меня были некоторые сложности с контролем количества возвращаемых элементов из-за ошибок с плавающей запятой.Так что теперь я использую linspace, например:

>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([  0.        ,   3.33333333,   6.66666667,  10.        ])
38 голосов
/ 01 февраля 2013

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. ])
10 голосов
/ 01 сентября 2011

С готовностью оценены (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.
8 голосов
/ 06 декабря 2015

с использованием 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]
5 голосов
/ 22 апреля 2017

Я помог добавить функцию numeric_range в пакет more-itertools .

more_itertools.numeric_range(start, stop, step) действует как диапазон встроенных функций, но может обрабатывать типы с плавающей запятой, десятичные дроби и дробные числа.

>>> from more_itertools import numeric_range
>>> tuple(numeric_range(.1, 5, 1))
(0.1, 1.1, 2.1, 3.1, 4.1)
3 голосов
/ 22 декабря 2016

Нет такой встроенной функции, но вы можете использовать следующее (код 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 голосов
/ 18 марта 2016

A решение без зависимостей и т.д. было предоставлено 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

Код также доступен как a GitHub Gist .

2 голосов
/ 19 декабря 2017

Почему в стандартной библиотеке нет реализации с плавающей запятой?

Как ясно из всех сообщений здесь, нет версии с плавающей запятой range(). Тем не менее, упущение имеет смысл, если учесть, что функция range() часто используется в качестве индекса (и, конечно, это означает генератор accessor ). Итак, когда мы вызываем range(0,40), мы фактически говорим, что хотим 40 значений, начиная с 0, до 40, но не включая сами 40.

Когда мы считаем, что генерация индексов зависит как от количества индексов, так и от их значений, использование реализации с плавающей запятой range() в стандартной библиотеке имеет меньше смысла. Например, если бы мы вызвали функцию 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

Похоже, что метод форматирования строк выигрывает в моей системе.

Ограничения

И, наконец, демонстрация сути из вышеприведенного обсуждения и одно последнее ограничение:

# "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.

...