プロジェクト主導の学習と実践でテストされた知識
序文
電子商取引システム、金融システム、レジ システムなど、多くのシステムには「お金を処理する」という要件があります。お金に関係するものである限り、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
ます 。値が同じかどうかを確認したい場合は、次のメソッドを使用する必要があります。BigDecimal
equals
false
int 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進数タイプ
-
固定長整数
実はビジネスコンピューティングは技術的には難しいものではありませんが、扱いを誤ると計り知れない損失が発生するため、やはりお金の問題は簡単ではありません。