Это может быть самый простой способ вычислений с десятичными и большими числами в JavaScript.

Как разработчик веб-интерфейса, вы, должно быть, столкнулись со следующей неожиданной проблемой:

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

BigNumber('0.1 + 0.2')                       // "0.3"
BigNumber('0.2 * (5 + 3 * 4 - 1) + 0.1')     // "3.3"

Так удивительно? Прежде чем объяснять, как это сделать, я хотел бы объяснить, почему мы сталкиваемся с вышеуказанной проблемой. Если вы знали это или вас не интересуют утомительные объяснения, вы можете сразу перейти к здесь.

TL;DR

Как и многие другие языки, JavaScript также использует IEEE 754 в качестве представления действительных чисел на компьютере. Поэтому количество представленных с помощью JavaScript было ограничено схемой хранения IEEE 754. Подробное объяснение вы можете найти здесь.

Пример 64-битной раскладки:

Десятичное число хранится как бесконечность

Люди используют десятичную систему для подсчета чисел (возможно, потому, что у нас десять пальцев). Однако машина использует двоичный код для представления чисел. Преобразование представленных различных систем счисления является основной причиной. Например, 1/3, представленная тройным числом, равна 0.1, а представленная десятичным числом — 0.3333333333…. Это может быть хорошим примером, объясняющим, почему десятичная дробь хранится как бесконечность. Преобразование между другими системами счисления может быть по аналогии с примером, включая десятичную систему счисления в двоичную.

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

Проработай это

bignumber.js — довольно неплохая библиотека для десятичной и недесятичной арифметики произвольной точности. Но его использование не является дружественным к людям, как это:

var BigNumber = require(“bignumber.js”)
// 0.2 * (5 + 3 * 4 - 1) + 0.1
new BigNumber(0.2).times(new BigNumber(5).plus(new BigNumber(3).times(4)).minus(1)).plus(0.1).toString()

Итак, нам нужен код, который поможет нам делать описанные выше ужасные вещи.

Преобразование инфикса в постфикс (TL;DR)

Почему существует эпизод преобразования инфикса в постфикс? Это может быть связано с разным подходом к мышлению между машинами и людьми. Мы человеческая модель мышления, как квант, вы никогда не знаете, что вы получите, но Бог знает, но также есть таинственная сила, чтобы связать эту точку со следующей точкой или эту точку с предыдущей точкой. Но для компьютера, как мы все знаем, есть только две модели мышления — массив и связь. Вот почему очевидно простую арифметическую операцию так сложно решить с помощью программирования.

A * (B + C * D - E) + F

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

Решение арифметической задачи может быть очевидным и естественным делом, но оно определенно не является априорным. Оно также требует использования опыта и практики снова и снова. Теперь давайте проанализируем наше мышление, чтобы выяснить, что произошло и как это произошло. Независимо от того, как выполняется расчет, всегда существует принцип, что время течет слева направо, что является основой того, что мы делаем все. Остальные символы, такие как +-×÷ ( ), чья миссия состоит в том, чтобы ограничить течение времени, а не изменить его. Давайте вдумаемся в эти символы.

Правило приоритета

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

1.( )
2. × ÷
3. + -

Вышеуказанные группы приоритетов символов уменьшаются сверху вниз. И сверстники в каждой группе также придерживаются течения времени. Таким образом, мы просто следим за течением времени слева направо и в соответствии с приоритетом для расчета. Мы также должны знать, что круглые скобки отличаются от других операторов. Скобки не влияют на операнды для формирования нового операнда, они только помечают рекурсивную группу, как и всю арифметическую операцию. Я подробно остановился на подходе к расчету слева направо:

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

Алгоритм преобразования инфикса в постфикс

До сих пор самоанализ был завершен. Мы построим мост между людьми и компьютерами. Приведенный выше вывод приемлем для людей и компьютеров. Однако это не строгое описание технических деталей. К счастью, для решения проблем с компьютерной арифметикой обычно используется обходной путь: инфикс-постфикс. Ниже более строго описывается арифметический алгоритм, который был взят из здесь.

Обзор правил:

1. Выводить операнды по мере их поступления.
2. Если стек пуст или содержит левую скобку сверху, поместите входящий оператор в стек.
3. Если входящий символ является левой скобкой, поместите его в стек. стек.
4. Если входящий символ является правой скобкой, извлекайте стек и печатайте операторы, пока не увидите левую скобку. Отбросить пару скобок.
5. Если входящий символ имеет более высокий приоритет, чем вершина стека, поместите его в стек.
6. strong> Если входящий символ имеет такой же приоритет, как и вершина стека, используйте ассоциацию. Если ассоциация слева направо, извлеките и напечатайте вершину стека, а затем нажмите входящий оператор. Если связь справа налево, нажмите входящий оператор.
7. Если входящий символ имеет более низкий приоритет, чем символ на вершине стека, извлеките стек и напечатайте верхнюю часть. оператор. Затем проверьте входящий оператор на новой вершине стека.
8. В конце выражения извлеките и напечатайте все операторы в стеке. (Не должно оставаться скобок.)

Почему СТЕК

Почему должен быть СТЕК? Почему НЕ массив или очередь? Обратите внимание на 3 выше, что означает, что пакетное извлечение будет осуществляться в соответствии с правилом приоритета сверху вниз. Если только приходят с монотонно возрастающими, мы их высовываем, то не будем путаться и строго придерживаться правила старшинства. Это может быть немного сложно, но правильно использует стек, а также следует правилу приоритета.

Ниже приведен типичный пример реализации описанного выше подхода. Выполню поэтапно:

A * (B + C * D - E) + F

Фрагмент кода

Следующий фрагмент кода является реализацией вышеуказанного алгоритма.

Реальные вещи

Объединив bignumber.js, о котором мы упоминали ранее, и алгоритм преобразования инфикса в постфикс, мы, наконец, можем это сделать:

BigNumber('0.2 * (5 + 3 * 4 - 1) + 0.1')     // "3.3"

Есть удобный пакет NPM, который я хотел бы вам порекомендовать. Вы можете попробовать сделать ту же удивительную вещь, что и в начале: https://github.com/licaomeng/bignumber-math-expression

использованная литература

[1] https://github.com/tc39/proposal-bigint

[2] http://csis.pace.edu/~wolf/CS122/infix-postfix.htm