Собственный BigInt и Intl.NumberFormat для точного расчета и отображения валюты?

Как вы, наверное, знаете, 0.1 + 0.2 == 0.30000000000000004 в JavaScript. Ограниченная точность при работе с деньгами может быть проблемой. Чтобы преодолеть это, можно использовать BigInt, для которого, очевидно, требуются целочисленные значения.

Вместо долларов в качестве базовой единицы и центов в качестве десятичной дроби (например, 0.10 для 10 центов) можно легко использовать центы в качестве базовой единицы (10 для 10 центов), чтобы сделать ее целочисленной.

Однако при представлении пользователю вы, вероятно, захотите показать значение в долларах, а не в центах. Но вы не можете разделить BigInt на Number:

> BigInt(10) / 100
Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

С другой стороны, Intl.NumberFormat похоже, тоже не дает способа сделать что-то подобное. Итак, что вы можете сделать вместо этого?

Простым способом благодаря основанию 10 было бы вставить точку в позиции -2 в числовую строку и передать ее форматировщику:

let formatter = Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
})
let s = (BigInt(10) + BigInt(20)).toString() // "30"
s = s.padStart(2, "0")                       // zero-pad single digit (if input 0..9)
s = s.slice(0, -2) + "." + s.slice(-2)       // ".30"
formatter.format(s)                          // "$0.30"

Это путь? Есть ли лучшие решения?

Я знаю, что format(0.30000000000000004) также приводит к $0.30 из-за округления, но это скорее общий вопрос о BigInt + UI.


person CodeManX    schedule 23.08.2018    source источник
comment
Не уверен, чего вы пытаетесь достичь с помощью BigInt. Используете ли вы числа с более чем 15 значащими цифрами?   -  person James    schedule 23.08.2018
comment
Мой вопрос носит теоретический характер, но, глядя на финансовые отчеты, такие как отчеты правительства США, цифры в триллионах, возможно, являются чем-то, с чем вы хотите посчитать и отформатировать их для отображения на веб-сайте.   -  person CodeManX    schedule 23.08.2018
comment
Проблема с вашим примером заключается в том, что когда вы передаете строковое значение в .format, оно создает из него число, поэтому вы теряете точность, которую имели в своих BigInts. Я не вижу аналогичной функции форматирования для BigInt, поэтому я полагаю, что вам нужно добавить $ самостоятельно, используя манипуляции со строками, аналогичные тому, как вы делаете десятичное число.   -  person James    schedule 23.08.2018


Ответы (1)


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

let toDisplayValue = n => Number(n) / 100;

console.log(toDisplayValue(BigInt(1234567)));
console.log(toDisplayValue(BigInt(45)));

person Cerbrus    schedule 23.08.2018
comment
Это предполагает, что n может быть представлено типом Number, что не обязательно так: Number(9007199254740993n)/100 // 90071992547409.92. Интересно, что Intl, по-видимому, использует Float64 для внутреннего форматирования, потому что даже с моим хаком, использующим строку в качестве входных данных, вы получаете "$90,071,992,547,409.94"... - person CodeManX; 23.08.2018
comment
Когда это не так, единственными реальными вариантами являются манипуляции со строками или какая-либо внешняя библиотека больших чисел. - person Cerbrus; 23.08.2018
comment
Я не вижу никаких проблем с реализацией BigInt. Это скорее отсутствие поддержки большого количества в другом месте. Честно говоря, я не ожидал, что Intl.NumberFormat будет основан на числах. Не уверен, по каким причинам это может быть, поскольку это кажется выполнимым только на основе строк - за исключением, может быть, неявного округления? - person CodeManX; 23.08.2018