С эволюцией моделей потребления онлайн-покупки стали неотъемлемым аспектом общественной жизни. Обеспечение безопасности потребителей, конфиденциальности пользователей и точности имеют первостепенное значение. Представьте себе ситуацию: пользователь совершает покупку, но сумма оплаты неверна. Как отреагирует пользователь? (WTF?) Отрицательный отзыв неизбежен. Это не только снижает лояльность пользователей, но и затрудняет удержание клиентов. Когда руководство компании узнает о таком пользовательском опыте, программист, ответственный за код, может оказаться в центре внимания, столкнувшись с негативными последствиями. Как опытный разработчик, я давно столкнулся с проблемой точности вычислений, но откладывал ее решение. Однако, когда кто-то недавно обратился ко мне с соответствующими вопросами, я понял, как важно углубиться в эту тему и поделиться своими мыслями о достижении точных вычислений в JavaScript.
Вычисление ролловеров с помощью JavaScript
Теперь давайте углубимся в более конкретный пример, чтобы проиллюстрировать повседневную работу вычислений JavaScript.
Хотя это может показаться тривиальным примером, он выявляет распространенную проблему в вычислениях JavaScript. Наша цель состоит не только в том, чтобы сосредоточиться на этом конкретном примере, но и в том, чтобы понять, почему возникают такие результаты. Какой шаг был неправильным? Существуют ли другие расчеты, которые могут столкнуться с подобными проблемами? И самое главное, как мы можем эффективно с ними справляться?
Как JavaScript представляет числа?
В JavaScript числа (как целые числа, так и числа с плавающей запятой) представляются с использованием типа Number. JavaScript следует стандарту IEEE 754, который использует 64 бита для представления числа.
Вот разбивка того, как используются эти 64 бита:
- Бит 0: бит знака, где 0 представляет положительное число, а 1 представляет отрицательное число.
- Биты с 1 по 11: часть экспоненты, хранящая значение экспоненты.
- Биты с 12 по 63: дробная часть (также известная как действующее число), в которой хранятся значащие цифры. (мантисса)
Теперь углубимся в интересный факт: максимально безопасное число в JavaScript представлено числом.MAX_SAFE_INTEGER, что равно Math.pow(2, 53) — 1, а не Math.pow(2, 52) — 1. Но почему что по делу? Ведь мантисса состоит всего из 52 бит, верно?
Причина в том, что двоичное представление всегда принимает вид 1.xx…xx, где первая цифра мантиссы в общепринятой записи считается равной 1 (таким образом, она опущена и явно не записывается). Xx…xx представляет мантиссу f и может состоять из 52 цифр. Таким образом, JavaScript обеспечивает допустимое числовое представление с точностью до 53 двоичных разрядов (последние 52 бита 64-битного числа с плавающей запятой плюс пропущенный 1 бит).
Просто проверьте -
Что происходит в процессе расчета?
Во-первых, компьютеры не могут напрямую оперировать десятичными числами, что определяется физическими характеристиками оборудования. Таким образом, операция делится на две части:
сначала преобразовать его в соответствующий двоичный файл согласно IEEE 754, а затем выполнить операцию по порядку
По этой идее анализируем процесс работы 0.1+0.2
1. Шестнадцатеричное преобразование
0.1 и 0.2 будут бесконечным циклом после преобразования в двоичный
0.1 -> 0.0001100110011001...(infinite loop) 0.2 -> 0.0011001100110011...(infinite loop)
Однако из-за ограничения количества цифр в мантиссе стандарта IEEE 754 необходимо обрезать лишние биты сзади (данная статья использует этот веб-сайт, чтобы наглядно показать двоичное представление чисел с плавающей запятой в памяти)
0.1
0.2
Таким образом, точность была потеряна при преобразовании между основаниями.
Вот еще один небольшой пункт знаний
Тогда почему x=0,1 может получить 0,1?
Это потому, что это 0,1 на самом деле не 0,1. Разве это не бессмысленно? успокойся, позволь мне объяснить
Стандарт предусматривает, что фиксированная длина мантиссы f составляет 52 бита плюс один пропущенный бит, эти 53 бита являются диапазоном точности JS. Он может представлять до 2⁵³ (9007199254740992), а длина равна 16, поэтому вы можете использовать toPrecision(16) для выполнения точных вычислений, а избыточная точность будет автоматически округлена в большую сторону.
0.10000000000000000555.toPrecision(16) // returns 0.1000000000000000, which is exactly 0.1 after removing the trailing zero 0.1.toPrecision(21) = 0.100000000000000005551
Вот почему 0,1 может быть равно 0,1. хорошо давай
2. Параллельная работа
Из-за разницы в количестве показателей операцию необходимо выполнять со стороны операции порядка, что также может привести к потере точности.
В соответствии с приведенными выше двумя этапами работы (включая два этапа потери точности) окончательный результат равен
0.0100110011001100110011001100110011001100110011001100
После преобразования результата в десятичный вид он равен 0,300000000000000004, поэтому выполняется предыдущая операция: 0,1 + 0,2 != 0,3
so:
В процессе конвертации базы и работы с заказом может произойти потеря точности
Как решить проблему точности?
1. Преобразование чисел в целые числа
Это самый простой способ думать об этом, и это относительно просто
function add(num1, num2) { const num1Digits = (num1.toString().split('.')[1] || '').length; const num2Digits = (num2.toString().split('.')[1] || '').length; const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits)); return (num1 * baseNum + num2 * baseNum) / baseNum; }
Но этот метод по-прежнему не годится для больших чисел.
2. Трехсторонняя библиотека
Это более комплексный подход. Я рекомендую 2 библиотеки, с которыми я обычно соприкасаюсь.
1).Math.js
Обширная математическая библиотека, посвященная JavaScript и Node.js. Поддерживает числа, большие числа (числа за безопасными числами), комплексные числа, дроби, единицы измерения и матрицы. Мощный и простой в использовании.
Официальный сайт: mathjs.org/
2).big.js
Официальный сайт: mikemcl.github.io/big.js
Эти библиотеки классов очень мощны и могут удовлетворить различные потребности, но во многих случаях проблемы, которые можно решить с помощью функции, не нужно решать путем ссылки на библиотеку классов.
Вышеизложенное является моим пониманием точного расчета js, и я надеюсь, что оно может вам помочь.