Обложка канала

Goal Gesture программирование, IT

Новости, статьи и полезные материалы из мира IT

Goal Gesture программирование, IT

4 года назад
Открыть в
​​Что будет, если переменную разделить и сразу умножить на одно и то же число. Будет ошибка, которую чертовски сложно отловить В одном из ИТ-пабликов мы увидели такой код на JavaScript: > 7110 / 100 100 === 7110 < false > 7120 / 100 100 === 7120 > true Читается это так: сначала число 7110 делится на 100 и умножается на сто. Результат деления сравнивается с числом 7110, и JavaScript говорит, что результат не равен. Как будто если разделить на 100 и тут же умножить на 100, ты получишь не то же самое число, с которого начинал. Во втором примере то же самое, но с числом 7120. Если его разделить на 100 и умножить на 100, получится ровно 7120. Получается, что одни и те же математические действия в двух случаях дали разные результаты. Как такое возможно? Если разобрать этот код, мы увидим, что ошибки нет, — но нужно понимать, как работает JavaScript. Деление и дробные числа. Когда мы делим одно число на другое и они не делятся нацело, то получаем дробное число — целую часть и то, что идёт после запятой. Но компьютер не использует стандартное школьное деление в столбик — вместо этого он представляет число в виде последовательности нулей и единиц и использует побитовые операции для деления. Это значит, что он не останавливается, например, после точного вычисления 7110 / 100 = 71,1, а работает со всеми битами сразу. После такого деления у компьютера получается последовательность, например 11101011011011101, где 11010 — это целая часть, а всё остальное — дробная. Если ему понадобится в целой части хранить число побольше, то он просто возьмёт дополнительное место за счёт дробной части. Получается, что запятая в такой переменной как бы плавает в зависимости от знаков до запятой, отсюда и название — «число с плавающей запятой» (floating point по-английски). Но когда компьютер забирает разряды у дробной части, он иногда может этим внести небольшую погрешность, например потерять последнюю цифру в дробной части (например, одну миллиардную). Как точность деления влияет на умножение. Когда мы после деления умножаем результат на 100, то с точки зрения компьютера это просто побитовый сдвиг точки вправо на несколько разрядов. Если у нас всё было посчитано точно, то результат будет таким же, что и до деления. Но всё дело в том, что иногда компьютер не может что-то поделить, хотя с точки зрения математики там всё просто. В этом случае он заполняет результатами вычисления все доступные нули и единицы в переменной, а остальное отбрасывает. Это значит, что результат уже получился неточный, а приблизительный, и дальше ошибка будет только расти. Давайте посмотрим, что получается в каждом случае после деления: >7110/100100 <7109.999999999999 = $3 >7120/100100 <7120 = $5 В первом случае компьютер не смог поделить 7110 на 100 без остатка, поэтому при умножении он потащил за собой девятки после запятой. Отсюда и неточность при сравнении. Как исправить? В JavaScript есть объект Math, который занимается всякой полезной математикой. И у этого объекта есть метод .round (), который может корректно округлить число до ближайшего целого. Зная о возможной ошибке в коде, нам стоит использовать это округление: >Math.round(7110/100100)===7110 <true >Math.round(7120/100100)===7120 <true Где это может пригодиться? Обратите внимание на этот эффект, если пишете программу, в которой используется деление непредсказуемых чисел — например, если пользователь вводит что-то с клавиатуры, а вы потом совершаете с этими числами свои операции. Например, вы получили рост человека, поделили его на какой-то внутренний коэффициент и сравниваете со своими референсными значениями. Сделайте поправку на то, что при делении могла сломаться точность, и либо округляйте число, либо предусматривайте запасы при сравнении.