2

クライアント側の小計計算を注文ページに追加して、ユーザーが選択するとボリューム ディスカウントが表示されるようにします。

計算の一部があちこちで 1 セントずれていることがわかりました。これは、合計が (PHP で) サーバー側で計算された最終的な合計と一致しないという事実を除けば、それほど大きな問題ではありません。

浮動小数点数を扱う場合、丸め誤差が予期される結果であることはわかっています。たとえば、149.95 * 0.15 = 22.492499999999996 および 149.95 * 0.30 = 44.98499999999999 です。前者は必要に応じてラウンドしますが、後者はそうではありません。

このトピックについて検索したところ、さまざまな議論が見つかりましたが、問題に十分に対処しているものはありません。

私の現在の計算は次のとおりです。

discount = Math.round(price * factor * 100) / 100;

一般的な提案は、ドル単位ではなくセント単位で作業することです。ただし、これには、開始数値を変換し、丸め、乗算し、結果を丸めてから元に戻す必要があります。

基本的に:

discount = Math.round(Math.round(price * 100) * Math.round(factor * 100) / 100) / 100;

四捨五入前に数値に 0.0001 を加算することを考えていました。例えば:

discount = Math.round(price * factor * 100 + 0.0001) / 100;

これは私が試したシナリオでは機能しますが、私の論理について疑問に思っています。0.0001 を追加するだけで、必要な丸め結果を強制するのに常に十分であり、多すぎることはありませんか?

注: ここでの目的のために、価格ごとに 1 回の計算のみに関心があり (したがって、エラーを複雑にすることはありません)、小数点以下 2 桁を超えて表示することは決してありません。

編集:たとえば、149.95 * 0.30 の結果を小数点以下 2 桁に丸め、44.99 を取得します。ただし、実際の結果は 44.985 ではなく 44.98499999999999 であるため、44.98 が得られます。によってエラーが発生していません/ 100。その前に起こっています。

テスト:

alert(149.95 * 0.30); // yields 44.98499999999999

したがって:

alert(Math.round(149.95 * 0.30 * 100) / 100); // yields 44.98

乗算の実際の結果を考慮すると 44.98 が期待されますが、ユーザーが期待するものではない (そして PHP の結果とは異なる) ため、望ましくありません。

解決策: 計算を行うために、すべてを整数に変換します。受け入れられた回答が指摘しているように、元の変換計算をいくらか単純化できます。0.0001 を追加するという私の考えは、単なる汚いハックです。仕事に適したツールを使用するのが最善です。

4

3 に答える 3

2

少量を入れてもいいとは思いませんが、多すぎる場合もあると思います。また、適切に文書化する必要があります。そうしないと、正しくないと見なされる可能性があります。

セントで作業するには […] 最初の数値を変換し、丸め、乗算し、結果を丸め、元に戻す必要があります。

discount = Math.round(Math.round(price * 100) * Math.round(factor * 100) / 100) / 100;

後でのみラウンドすることも同様に機能するはずだと思います。ただし、有効桁数が前の 2 つの sig 桁の合計になるように、最初に結果を乗算する必要があります。つまり、この例では 2+2=4 桁になります。

discount = Math.round(Math.round( (price * factor) * 10000) / 100) / 100;
于 2013-02-18T15:33:55.300 に答える
1

Bergi の答えは解決策を示しています。この答えは、それが正しいという数学的デモンストレーションを示しています。その過程で、許容できる入力エラーの量についても一定の境界が確立されます。

あなたの問題はこれです:

  • 既に丸め誤差を含む浮動小数点数 x があります。たとえば、149.95 を表すことを意図していますが、実際には 149.94999999999998863131622783839702606201171875 が含まれています。
  • この浮動小数点数 x に割引値 d を掛けます。
  • 理想的な数学がエラーなしで使用されたかのように実行された、最も近いペニーへの乗算の結果を知りたいとします。

さらに 2 つの仮定を追加するとします。

  • x は常に正確なセント数を表します。つまり、149.95 など、正確な 100 分の 1 の数値を表します。
  • x の誤差は小さく、たとえば .00004 未満です。
  • 割引値 d は、整数のパーセンテージ (つまり、25% を表す .25 などの正確な 100 分の 1 の数) を表し、区間 [0%, 100%] にあります。
  • エラーは d is tiny です。常に、小数点以下 2 桁の 10 進数を倍精度 (64 ビット) 2 進浮動小数点に正しく変換した結果です。

値を検討してくださいx*d*10000。x と d はそれぞれ理想的には .01 の倍数であるため、理想的にはこれは整数であり、x と d の理想的な積に 10,000 を掛けると整数になります。x と d の誤差は小さいため、整数への丸めx*d*10000により理想的な整数が生成されます。たとえば、理想的な x と d の代わりに、x と d と小さな誤差 x+e0 と d+e1 があり、(x+e0)•(d+e1)•10000 = (x•d+ x•e1+d•e0+e0•e1)•10000。e1 が小さいと仮定したので、支配的な誤差は d•e0•10000 です。x の誤差である e0 は .00004 未満であり、d は最大で 1 (100%) であると仮定したため、d•e0•10000 は .4 未満です。この誤差と e1 からの小さな誤差は、丸めを変更するのに十分ではありません。x*d*10000理想的な整数から他の整数へ。(これは、整数になるはずの結果を丸める方法を変更するには、誤差が少なくとも .5 でなければならないためです。たとえば、3 と 0.5 の誤差を足すと 4 に丸められますが、3 と .49999 を足すと丸められません。)

したがって、Math.round(x*d*10000)目的の整数が生成されます。次にMath.round(x*d*10000)/100、 の近似値はx*d*1001 セント未満の正確さであるため、それを四捨五入すると、Math.round(Math.round(x*d*10000)/100)目的のセント数が正確に生成されます。最後に、それを 100 で割ると (整数としてのセント数ではなく 100 分の 1 でドル数を生成するため)、新しい丸め誤差が生成されますが、誤差は非常に小さいため、結果の値が正しく変換されると、 decimal が 2 桁の場合、正しい値が表示されます。(この値でさらに演算を行うと、当てはまらない可能性があります。)

上記から、x の誤差が .00005 まで大きくなると、この計算が失敗する可能性があることがわかります。注文額が 100,000 ドルに達する可能性があるとします。100,000 付近の値を表す際の浮動小数点誤差は、最大でも 100,000•2 -53です。誰かがこのエラーで 10 万個のアイテムを注文した場合 (アイテムの個々の価格は $100,000 よりも小さいため、注文できませんでした。したがって、エラーは小さくなります)、価格が個別に合計され、10 万 (マイナス 1) が実行されます。 10 万個の新しいエラーを追加すると、最大で 100,000•2 -53のほぼ 20 万個のエラーが発生するため、合計エラーは最大で 2•10 5 •10 5 •2 -53になります。、これは約 .00000222 です。したがって、このソリューションは通常の注文で機能するはずです。

割引が整数のパーセンテージでない場合、解決策を再検討する必要があることに注意してください。たとえば、割引が 33% ではなく「3 分の 1」と記載されx*d*10000ている場合、 は整数であるとは想定されません。

于 2013-02-18T17:50:01.780 に答える
1

数値に少量を追加しても、あまり正確ではありません。ライブラリを使用してより良い結果を得ることができます: https://github.com/jtobey/javascript-bignum

于 2013-02-18T15:07:52.253 に答える