Наиболее Часто Используемый Фрагмент Переполнения Стека Содержит Ошибку


На самом деле это не шутка, что программирование стало вопросом копирования и вставки из различных источников. Да, это может сэкономить время, но когда вы просто копируете и вставляете, откуда вы знаете, что код работает? Даже высоко оцененный ответ SO мог иметь ошибку и имел ее.

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

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

Все это выяснилось, когда в академической статье, посвященной повторному использованию кода, было объявлено, что фрагмент кода на Java, который печатал количество байтов в «разумных» единицах, был наиболее скопирован и был встроен в более чем 6000 повторов GitHub. Это заставило автора, Андреаса Лундблада, еще раз взглянуть на нее, почти через десять лет после того, как он ее написал, и он обнаружил, что она была глючной.

Проблема заключается в том, чтобы напечатать значение, используя подходящие единицы измерения в качестве значения от 1 до 999,9, за которым следует единица измерения. Например, 999,999 должно быть напечатано как 1,0 М. Обратите внимание, что в этой спецификации печать его как 1000K неприемлема, и это немного сложнее достичь.

В предлагаемом решении использовался довольно простой подход. Если у вас есть значение и вы хотите масштабировать его в единицах K, M, G, T и т. Д., То все, что вам нужно, это

log(значение)/log(1000)

или

exp=log(значение)/3

В конце концов, журнал-это значение, на которое вы должны поднять 10, чтобы получить значение. Так:

log(123456789)/3 = 2,697..

и взятие целого значения дает в результате 2.

Это означает, что единицы измерения должны быть одинаковыми, а значение, записанное с одним десятичным знаком, равно:

123,4 М

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

exp=int(log(value)/3)

в качестве индекса в строковый массив для множителя и значения в выбранных единицах измерения просто:

значение/(1000^exp)

где ^ — это повышение до оператора мощности на языке, который вы используете.

Пока все хорошо, и это похоже на ответ из учебника. Что может пойти не так?

Серьезно, многие программисты просто приняли бы истину математики. Математика верна, значит, программа верна. К сожалению, математика верна только в том случае, если арифметика выполняется с идеальной точностью, а плавающая точка далека от совершенства. Первая проблема заключается в том, что 999,999 отображается как 1000,0 КБ. Причина этого в том, что log(999999)/3 равен 1.9999.. который при усечении до int равен 1, поэтому единица измерения равна КБ, а 999999/1000-999,999, что округляется до 1000,0 КБ. Как уже говорилось, правильный результат-1,0 М.

Вы можете видеть, что проблема заключается во взаимодействии между вычислением показателя в виде int и округлением, применяемым к масштабируемому значению. Предлагаемое исправление состоит в том, чтобы округлить показатель степени вверх, когда значение будет округлено до 1000:

если(значение >= (1000^exp)*(1000-0.05))  exp++:

Другое возможное решение состоит в том, чтобы проверить, что лог предлагаемого округленного значения меньше 3, и увеличить exp, если это так.

Есть и другие проблемы с вычислением, но все они одного и того же тонкого рода. Проблемы сводятся к тому, что длинный int имеет больше цифр точности, чем двойной.  Вы можете прочитать сообщение в блоге автора, чтобы изучить их.

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

Является ли это серьезной ошибкой? Не совсем, учитывая, что человек не будет введен в заблуждение представленным результатом. Однако представьте, что данные передаются на какое — то промышленное устройство-оно вполне может выйти из строя. Было бы неплохо провести тест, чтобы убедиться, что данные находятся в правильном формате.

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


Добавить комментарий