Python: добавление элемента с плавающей точкой к массиву изменяет его точность, например, с 1.2 до 1.200000002

Я столкнулся с очень странной проблемой. Я пытаюсь создать функцию, которая возвращает массив значений, которые заключают диапазон с определенным размером шага (например, вы можете найти на оси графика). Вместо того, чтобы просто использовать np.arange(min,max,step)Я хочу что-то, что лучше округляет размер шага. Вот что я попробовал:

def get_decade(value):
    return pow(10,math.floor(math.log10(value)))

def get_steparray(min,max,delta):
    delta_step = float(get_decade(delta))
    next = math.floor(min/delta_step)*delta_step
    print next
    array = [next]
    while next < max:
        next = int((next+delta_step)/delta_step)*delta_step
        print next
        array.append(next)
        print array
    print array
    return array

Там есть печатные заявления, которые помогут мне понять, что происходит. Вот что я попытался запустить с:

print get_steparray(1.032,1.431,0.1)

Исходя из этого, я ожидал, что массив в конечном итоге [1.0,1.1,1.2,1.3,1.4,1.5]

Вот что я получаю от функции:

1.0
1.1
[1.0, 1.1]
1.2
[1.0, 1.1, 1.2000000000000002]
1.3
[1.0, 1.1, 1.2000000000000002, 1.3]
1.4
[1.0, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001]
1.5
[1.0, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5]
[1.0, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5]

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

У кого-нибудь есть идеи, что может быть причиной этого? Спасибо за любую информацию, которую вы можете предоставить. С другой стороны, я был бы так же рад, если бы кто-то знал лучший, более функциональный способ создания такого массива. Может быть, я должен просто придерживаться np.arange и корректировать значения max/min/step?

(Да, я знаю, что мой код не самый чистый. Приведенная выше функция начиналась намного чище, но я добавил некоторые ненужные функции, чтобы попытаться заставить его работать.)

Изменить: Хотя я ценю все проницательные комментарии, я все еще не уверен, что они решают мою проблему полностью. Как видно на распечатке, каждое значение хранится с достаточной точностью, как мои потребности, в качестве изолированного типа с плавающей запятой. Но когда они добавляются в массив, только тогда они меняют точность. Я в целом хорошо осведомлен о проблемах с плавающей запятой, но мне было любопытно узнать о конкретных различиях между плавающей точкой и массивом. Интересно, может быть, массив хранит значение в меньшем количестве бит, чем отдельное значение.

При этом, я думаю, что в конечном итоге я сделаю предложение сосредоточиться на форматировании в момент использования.

Спасибо!

1 ответ

Зачем?

Это типичная арифметическая проблема с плавающей точкой.

Я опубликую здесь выдержку из документации Python для арифметических проблем и ограничений с плавающей точкой, но это верно для большинства языков:

Числа с плавающей точкой представлены в компьютерном оборудовании в виде двоичных (двоичных) дробей. Например, десятичная дробь

0.125

имеет значение 1/10 + 2/100 + 5/1000 и таким же образом двоичная дробь

0.001

имеет значение 0/2 + 0/4 + 1/8, Эти две дроби имеют одинаковые значения, единственное реальное отличие состоит в том, что первая записана в дробной нотации 10, а вторая - в 2.

К сожалению, большинство десятичных дробей не могут быть представлены точно как двоичные дроби. Следствием этого является то, что, как правило, вводимые десятичные числа с плавающей запятой аппроксимируются только двоичными числами с плавающей запятой, фактически сохраненными в машине.

Проблема легче понять сначала в базе 10. Рассмотрим дробь 1/3, Вы можете приблизить это как основную 10 фракцию:

0.3

или лучше,

0.33

или лучше,

0.333

и так далее. Независимо от того, сколько цифр вы хотите записать, результат никогда не будет точно 1/3, но будет все более лучшим приближением 1/3,


Как?

Итак, теперь, когда вы знаете, почему, вот как вы можете обойти это.

Если вам не нужна вся эта точность (или в этом вопросе... вся эта неточность), вы всегда можете:

  • использовать round встроенная
  • оставьте номер как есть, но отформатируйте его только тогда, когда вам нужно, чтобы он был напечатан для графического интерфейса или консоли
Другие вопросы по тегам