LoginSignup
0
0

JavaScriptで正確な消費税の計算

Last updated at Posted at 2023-07-24

前提

decimal.jsを使います。
npm, cdnお好きな方法でダウンロードしてください。

なお、私はChromeで動作確認しています。

結論

function strPriceInclTax(strPrice, strTaxRate = "1.08") {
    return (new Decimal(strPrice)).times(new Decimal(strTaxRate)).floor().toString();
}

strPriceInclTax("100"); // "108"
strPriceInclTax("123456789"); // "133333332"

切り捨てではなく四捨五入したい場合はfloorをroundに、切り上げたい場合はceilにしてください。

終わり。以下は読みたい人だけ。

数値を文字列で扱っている理由

数値を文字列で扱っているのは、目視している数値とプログラム内部の数値が異なるケースを懸念しているからです。

decimal.jsはコンストラクタの引数の数値は内部的にtoStringで文字列に変換しているため、このような数値はバグの原因になりえます。

要するにこういうバグが嫌なのです。

// 文字列(笑)という人用
function priceInclTax(price, taxRate = 1.08) {
    return (new Decimal(price)).times(new Decimal(taxRate)).floor().toNumber();
}

console.log(priceInclTax(10000000000000017));
// 出力:10800000000000016
// 正解:10800000000000018

console.log(priceInclTax(100000000000000000, 0.11111111111111111));
// 出力:11111111111111110
// 正解:11111111111111111

個人の感想

(Decimalコンストラクタの引数とDecimalから値を取り出すときの話です!!!!)

上記のpriceInclTaxの実装でも、
数値をtoStringに変換したときに目視したときの値と同じならバグらないでしょう。多分。
ただ、私にはどこからがその境界線なのか、はっきりと断言することができません。
0.11111111111111111が駄目で
0.1111111111111111がOKだということが私には分からないのです。
10000000000000017はNumber.MAX_SAFE_INTEGERを超える数値なのでまあ正確には扱えないだろうなーとは感覚的に分かりますが。
同等の理由で
new Decimal("1.08")をnew Decimal(1.08)と書くのは許されるのか?
仕様変更で1.08からXになった場合、
new Decimal("X")とnew Decimal(X)が同等なのか?
私には分からないのです。
もちろん、識者は分かるでしょう。
ただ、実際運用していくうえで、開発、保守に関わる人全員がそれを理解して扱えるのでしょうか?
そうでないなら確実性を重視して数値ではなく文字列を使うべきなのではないでしょうか?
もちろん途中で値が壊れる可能性があるので、入力から出力、サーバーへの連携、すべてをです。

ぶっちゃけ個人の日常生活の範囲の金額や小数点以下数桁程度なら普通に数値を使ってもバグらないと思うのでこれが問題化することはないと思います。ただ、潜在バグがあることは頭の片隅に入れておいてください。

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0