【お金の処理】業務上の計算で精度を失わないようにするためにはどうすればよいでしょうか?

プロジェクト主導の学習と実践でテストされた知識

序文

電子商取引システム、金融システム、レジ システムなど、多くのシステムには「お金を処理する」という要件があります。お金に関係するものである限り、120,000ポイントのエネルギーで処理する必要があり、一歩も間違えることはできません。そうでないと、システムとユーザーにとって大惨事になります。

保証金額の精度には、オーバーフロー精度という 2 つの主な側面があります。オーバーフローとは、データを保存するのに十分なスペースがあることを意味しますが、量が多すぎるとデータを保存できません。正確とは、金額を計算する際にズレがないことを意味し、それ以上でも以下でも機能しません。

オーバーフローの問題を解決する方法は誰もが知っていますが、桁数の長い数値型を選択する、つまり float 使用し ないでくださいdouble 。double 浮動小数点数は精度の低下を引き起こすため、精度の問題は解決できません。

精度の低下を直観的に感じてみましょう。

double money = 1.0 - 0.9;

この計算結果は誰もが知っています 0.1が、実際の結果は次のとおりです 0.09999999999999998この現象は、コンピューターの最下層が 2 進演算であり、2 進数では 10 進数を正確に表現できないために発生します。したがって、商用計算などの精密な計算では、精度が失われないように他のデータ型を使用する必要があり、浮動小数点数は使用しないでください。

次に、実際の開発で商業的な計算を行う方法をこのカニが詳しく説明し、コードと SQL ステートメントをすべて Github に置き (アドレスは記事の最後にあります)、クローンして実行することができます。

解決

ビジネス コンピューティングのニーズを満たすデータ型は 2 つあり、1 つはビジネス コンピューティング専用に設計された Decimal型 、もう 1 つは固定長の整数です。

10進数

データ型の選択に関しては、一方はデータベースを考慮する必要があり、もう一方はプログラミング言語を考慮する必要があります。つまり、データベースにデータを保存するためにどのタイプが使用され、コード内のデータを処理するためにどのタイプが使用されるかということです。

decimal この型では精度が失われることがなく、商業的な計算に最適であるため、データベース レベルではこの型が自然に使用されます 。

フィールドをそのまま定義するための decimal 構文は decimal(M,N)M 格納する桁数とN 小数点以下の桁数を表します。と仮定すると decimal(20,2)、合計 20 桁が保存され、そのうち 2 桁が小数点を占めることを意味します。

主キーと残高という 2 つの単純なフィールドを持つ新しいユーザー テーブルを作成します。

ここでは、小数点として 2 ポイントが予約されています。つまり、金額はセント 単位でのみ保存され、実際のプロジェクトに保存される単位はビジネス ニーズに応じて異なりますが、これはすべて可能です。

データベース レベルは完了です。コード レベルを見てみましょう。Java では、型はデータベースに対応しており decimal 、 java.math.BigDecimal当然のことながら、精度が完全に正確であることを保証できます。

を作成するにはBigDecimal主に 3 つの方法があります。

BigDecimal d1 = new BigDecimal(0.1); // BigDecimal(double val)
BigDecimal d2 = new BigDecimal("0.1"); // BigDecimal(String val)
BigDecimal d3 = BigDecimal.valueOf(0.1); // static BigDecimal valueOf(double val)

最初の 2 つはコンストラクターで、後者は静的メソッドです。3 つの方法はどれも非常に便利ですが、最初の方法は禁止されています。これら 3 つのオブジェクトのそれぞれの印刷結果を見て、その理由を確認してください。

d1: 0.1000000000000000055511151231257827021181583404541015625
d2: 0.1
d3: 0.1

最初のメソッドでは、 double コンストラクターを介して渡される型のパラメーターは値を正確に取得できません。値を正しく作成するには、文字列に BigDecimal変換 double してからコンストラクターを呼び出すか、静的メソッドを直接呼び出す必要があります。実際、静的メソッド内には、 double 文字列に変換されて呼び出されるコンストラクターもあります。

BigDecimal 10 進値がデータベースからクエリされる場合、または 10 進値がフロントエンドから渡される場合、データはオブジェクトに正確にマッピングされるため 、これについて心配する必要はありません。

作成について話した後、最も重要な数値演算について話しましょう。演算は加算、減算、乗算、除算にほかならず、 BigDecimal 対応するメソッドを提供します。

BigDecimal add(BigDecimal); // 加
BigDecimal subtract(BigDecimal); // 减
BigDecimal multiply(BigDecimal); // 乘
BigDecimal divide(BigDecimal); // 除

BigDecimal これは不変オブジェクトです。つまり、これらの操作では元のオブジェクトの値は変更されず、メソッドの実行後には新しいオブジェクトのみが返されます。操作後に元の値を更新したい場合は、再割り当てすることしかできません。

d1 = d1.subtract(d2);

証拠はありません。精度が失われるかどうかを確認してみましょう。

BigDecimal d1 = new BigDecimal("1.0");
BigDecimal d2 = new BigDecimal("0.9");
System.out.println(d1.subtract(d2));

出力は間違いなく です  0.1

コードは精度が失われないことを保証できていますが、数学における割り算は無尽蔵である可能性があります。たとえば、 10 で 除算すると3、次の例外がスローされます。


割り切れないことによる無限小数の問題を解決するには、小数の精度を人為的に制御する必要があります。精度を制御するために使用される除算演算の別の方法があります。

BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

scale このパラメータは、操作後に保持される小数点以下の桁数を示し、roundingMode パラメータは小数点以下の計算方法を示します。

BigDecimal d1 = new BigDecimal("1.0");
BigDecimal d2 = new BigDecimal("3");
System.out.println(d1.divide(d2, 2, RoundingMode.DOWN)); // 小数精度为2,多余小数直接舍去。输出结果为0.33

列挙を使用する RoundingMode と、小数点以下の計算方法を簡単に指定でき、直接四捨五入のほか、四捨五入や四捨五入など複数の方法があり、ビジネスニーズに応じて指定できます。

小数精度はデータベースではなく、可能な限りコード内で制御する必要があることに注意してください。デフォルトでは、データベースは小数精度を維持するために丸めを使用します。

たとえば、データベースに設定されている小数精度は 2 で、 0.335これを保存すると、最終的に保存される値は になります 0.34

オブジェクトを作成して操作する方法がわかったので BigDecimal 、残っている操作は最後の操作、比較だけです。これは基本的なデータ型ではないため、 == 二重等号を使用することはできません。そのため、 equals比較を使用してみます。

BigDecimal d1 = new BigDecimal("0.33");
BigDecimal d2 = new BigDecimal("0.3300");
System.out.println(d1.equals(d2)); // false

この  メソッド  は値を比較するだけでなく、値が同じでも精度が異なる場合でも精度も比較するため、出力結果は になり falseます 値が同じかどうかを確認したい場合は、次のメソッドを使用する必要があります。BigDecimalequalsfalseint compareTo(BigDecimal val)

BigDecimal d1 = new BigDecimal("0.33");
BigDecimal d2 = new BigDecimal("0.3300");
System.out.println(d1.compareTo(d2) == 0); // true

d1 より大きい場合は d2、戻り値を返します 1

d1 未満 d2、返却 -1;

2 つの値が等しい場合は、 を返します 0

BigDecimal ここでは の使い方を紹介しましたが、次に 2 番目の解決策を見てみましょう。

固定長整数

固定長整数は、その名前が示すように、固定 (10 進数) 長の整数です。これは単なる概念であり、新しいデータ型ではありません。引き続き通常の整数を使用します。

金額には当然のように小数点がありますが、少し考えてみると小数点は必要ないことが分かります。前に示した金額の単位は(1 元 5 セント)です1.55 。次に、単位が角度である場合、1 元、5 セント、5 セントの価値は になります 15.5次に単位をに絞り込むと、値は になります 155そう、最小単位までなら小数点以下は省略できるんです!最小単位はビジネス要件に応じて決定されます。たとえば、システムがセンチメートルまでの精度を必要とする場合、値は です1550もちろん、これは一般に要点まで正確であり、次に説明する単位はすべてポイントです。

新しいフィールドを作成しましょう。タイプはポイント bigint、単位はポイントです。

コード内の対応するデータ型は当然のことながら です Long私たちは基本的な型の数値演算に精通しており、演算演算子を直接使用できます。

long d1 = 10000L; // 100元
d1 += 500L; // 加五元
d1 -= 500L; // 减五元

足し算と引き算については何も言うことはありません。掛け算と割り算には小数が含まれる場合があります。たとえば、商品が 20% 割引される場合、演算は次のように乗算します 0.8

long d1 = 2366L; // 23.66元
double result = d1 * 0.8; // 打八折,运算后结果为1892.8
d1 = (long)result; // 转换为整数,舍去所有小数,值为1892。即18.92元

10 進演算の場合、型は当然浮動小数点数になるため、浮動小数点数を整数に変換する必要もあります。

強制転送ではすべての小数点以下が四捨五入されますが、この四捨五入は精度の低下を意味するものではありませんビジネス要件の最小単位は何か、あるものだけを保持し、スコアより低い単位を保持する必要はありません。この点 BigDecimal はそれと一致しており、システムでポイントのみが必要な場合、小数点以下の精度は であり 2、残りの小数点は破棄されます。

ただし、ビジネス計算によっては、四捨五入などの他の操作が必要になる場合があります。これは Mathクラスを通じて実行できます。

long d1 = 2366L; // 23.66元
double result = d1 * 0.8; // 运算后结果为1892.8
d1 = (long)result; // 强转舍去所有小数,值为1892
d1 = (long)Math.ceil(result); // 向上取整,值为1893
d1 = (long)Math.round(result); // 四舍五入,值为1893
...

除算演算を見てみましょう。整数を整数で割る場合、小数点以下はすべて自動的に四捨五入されます。

long d1 = 2366L;
long result = d1 / 3; // 正确的值本应该为788.6666666666666,舍去所有小数,最终值为788

丸めなどの他の 10 進演算を実行する場合は、最初に浮動小数点演算を実行してから、整数に変換します。

long d1 = 2366L;
double result = d1 / 3.0; // 注意,这里除以不是 3,而是 3.0 浮点数
d1 = (long)Math.round(result); // 四射勿入,最终值为789,即7.89元

データベース ストレージとコード操作はどちらも整数ですが、フロントエンドの表示がセント単位のままでは、あまり使いやすくありません。そのため、バックエンドがフロントエンドに値を渡した後、フロントエンドはその値を自分で除算して 100ユーザーにで表示する必要があります。フロントエンドが値をバックエンドに渡すときも、その値は合意された整数として渡されます。

エンディング

金額の処理について説明します。私たちは 2 つのビジネス コンピューティング シナリオを学びました。

  • 10進数タイプ

  • 固定長整数

実はビジネスコンピューティングは技術的には難しいものではありませんが、扱いを誤ると計り知れない損失が発生するため、やはりお金の問題は簡単ではありません。

おすすめ

転載: blog.csdn.net/wufaqidong1/article/details/131558942
おすすめ