Почему в стандартной библиотеке нет реализации диапазона с плавающей запятой?
Как ясно из всех сообщений здесь, версии с плавающей запятой не существует range(). Тем не менее, упущение имеет смысл, если учесть, что range()функция часто используется как генератор индекса (и, конечно, это означает средство доступа ). Итак, когда мы вызываем range(0,40), мы фактически говорим, что хотим 40 значений, начиная с 0, до 40, но не включая 40 значений.
Если учесть, что создание индекса зависит от количества индексов и их значений, использование реализации типа float 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.
])
Пример использования индексирования
Что касается индексации, я написал немного другой подход с использованием хитрой строковой магии, которая позволяет нам указывать количество десятичных знаков.
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функцию и указать количество десятичных знаков:
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
Похоже, метод форматирования строк выигрывает в моей системе.
Ограничения
И, наконец, демонстрация сути вышеприведенного обсуждения и последнее ограничение:
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значение, может возникнуть зияющий разрыв из-за последней проблемы:
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.