Range() для поплавков
Есть ли эквивалент range()
для float в 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
Ответы
Ответ 1
Я не знаю встроенной функции, но писать ее, например это не должно быть слишком сложно.
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
Ответ 2
Вы можете использовать:
[x / 10.0 for x in range(5, 50, 15)]
или используйте lambda/map:
map(lambda x: x/10.0, range(5, 50, 15))
Ответ 3
Я использовал numpy.arange
, но имел некоторые сложности, контролирующие количество возвращаемых элементов из-за ошибок с плавающей запятой. Поэтому теперь я использую linspace
, например:
>>> import numpy
>>> numpy.linspace(0, 10, num=4)
array([ 0. , 3.33333333, 6.66666667, 10. ])
Ответ 4
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. ])
Ответ 5
Ожидаемая оценка (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.
Ответ 6
с использованием 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]
Ответ 7
Я помог добавить функцию 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)
Ответ 8
A решение без зависимостей 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.
Ответ 9
Нет такой встроенной функции, но вы можете использовать следующий (код 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
Ответ 10
Почему в стандартной библиотеке нет реализации с плавающей запятой?
Как ясно из всех постов здесь, не существует версии range()
плавающей запятой. Тем не менее, упущение имеет смысл, если учесть, что функция range()
часто используется в качестве генератора индекса (и, конечно, это означает метод доступа). Итак, когда мы вызываем 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.
Ответ 11
Я написал функцию, которая возвращает кортеж из диапазона чисел с плавающей запятой двойной точности без каких-либо десятичных знаков за сотые. это просто вопрос разбора значений диапазона, таких как строки, и отрыва избытка. Я использую его для отображения диапазонов для выбора из пользовательского интерфейса. Я надеюсь, что кто-то найдет это полезным.
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
Ответ 12
def Range(*argSequence):
if len(argSequence) == 3:
imin = argSequence[0]; imax = argSequence[1]; di = argSequence[2]
i = imin; iList = []
while i <= imax:
iList.append(i)
i += di
return iList
if len(argSequence) == 2:
return Range(argSequence[0], argSequence[1], 1)
if len(argSequence) == 1:
return Range(1, argSequence[0], 1)
Обратите внимание, что первая буква диапазона - это капитал. Этот метод именования не рекомендуется для функций в Python. Вы можете изменить Range на что-то вроде drange или frange, если хотите. Функция "Диапазон" ведет себя так, как вы этого хотите. Здесь вы можете проверить его здесь [http://reference.wolfram.com/language/ref/Range.html].
Ответ 13
Я думаю, что есть очень простой ответ, который действительно эмулирует все функции диапазона, но как для float, так и для integer. В этом решении вы просто предполагаете, что ваше приближение по умолчанию - 1e-7 (или тот, который вы выберете), и вы можете изменить его при вызове функции.
def drange(start,stop=None,jump=1,approx=7): # Approx to 1e-7 by default
'''
This function is equivalent to range but for both float and integer
'''
if not stop: # If there is no y value: range(x)
stop= start
start= 0
valor= round(start,approx)
while valor < stop:
if valor==int(valor):
yield int(round(valor,approx))
else:
yield float(round(valor,approx))
valor += jump
for i in drange(12):
print(i)
Ответ 14
Более простая безбиблиотечная версия
О, черт, я добавлю простую версию без библиотек. Не стесняйтесь улучшать это [*]:
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
- это количество шагов, которые nsteps
вам пройти от начала до range(nsteps)
а range(nsteps)
всегда выдает целые числа, поэтому потери точности не происходит. Последний шаг - отобразить [0..nsteps] линейно на [start..stop].
редактировать
Если, как и alancalvitti, вы хотите, чтобы серия имела точное рациональное представление, вы всегда можете использовать дроби:
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()
возвращает список, а не генератор. Но этого хватило для моих нужд.
Ответ 15
Есть ли эквивалент range() для float в Python?
НЕТ
Используйте это:
def f_range(start, end, step):
a = range(int(start/0.01), int(end/0.01), int(step/0.01))
var = []
for item in a:
var.append(item*0.01)
return var