28.ゼロからspringbootを学ぶ-金銭類BigDecimalを詳しく理解する

5319 ワード

前言
著者は最近プロジェクトを開発し、javaの金銭処理類BigDecimalを使用する必要がある.
なぜFloatやDoubleではなくBigDecimalが必要なのか
これに対して私と同じように、普通のお金はDoubleでいいのに、どうやってBigDecimal類を作ったのか疑問に思っているに違いない.why?
実は、これはコンピュータの設計と関係があります.なぜなら、私たちのコンピュータはバイナリです.浮動小数点数はバイナリで正確に表すことはできません.
コンピュータCPUは浮動小数点数を表す2つの部分からなる:指数と末尾数、このような表示方法は一般的に一定の精度を失い、浮動小数点数演算にも一定の誤差が発生する.
FloatとDouble型の主な設計目標は科学計算とエンジニアリング計算のためである.広域数値範囲でより正確な高速近似計算を提供するために丹念に設計されたバイナリ浮動小数点演算を実行した.
しかし、それらは完全に正確な結果を提供していないので、正確な結果を要求する場合に使用されるべきではない.
そのため、FloatとDoubleは科学計算やエンジニアリング計算にしか使えません.ビジネス計算ではjavaを使います.math.BigDecimal
BigDecimalの初期化
BigDecimalは、int、long、double、StringなどによってBigDecimalオブジェクトを構築できる豊富な構造関数を提供します.コンストラクタの説明
BigDecimal(int)                        。 
BigDecimal(double)                   。 
BigDecimal(long)                      。 
BigDecimal(String)                        

では、このいくつかの異なるタイプで構築されたBigDecimalオブジェクトには違いがありますか?もちろんあります.次のテストを行います.
BigDecimal a = new BigDecimal(1.1);
BigDecimal b = BigDecimal.valueOf(1.1);
BigDecimal c = new BigDecimal("1.1");
System.out.println("a="+a);
System.out.println("b="+b);
System.out.println("c="+c);

結果
a=1.100000000000000088817841970012523233890533447265625
b=1.1
c=1.1

Doubleタイプの初期化は明らかに問題があることがわかりますが、なぜですか?
コンピュータ01の方式は10進数の0.1では正確には表現できないため、Doubleの数字については近接表示しかできず、このDoubleでBigDecimalを初期化すると問題が発生する.
この現象の公式説明を見てみましょう.
  • パラメータタイプがDoubleの構成方法の結果には一定の予知不可能性がある.JavaにnewBigDecimal(0.1)を書き込んで作成されたBigDecimalは、ちょうど0.1(スケール値1ではなく、スケール値1)に等しいと考えられているかもしれませんが、実際には0.100000000000555123125782702181583404541015625に等しくなります.これは、0.1をDoubleと正確に表現することができない(あるいは、この場合、任意の有限長の2進小数として表すことができない)ためである.このように、構造方法に伝達する値が0.1(表面的にはその値に等しいが)に等しくなることはない.

  • 2 .一方、String構造方法は、newBigDecimal(「0.1」)に書き込むと、予想される0.1に等しいBigDecimalが作成されることが完全に予知されている.したがって、比較的には、通常、String構造方法を優先的に使用することが推奨される.
    ただし、BidDecimalをDoubleで初期化しなければならない場合があります.この場合、Doubleを使用してください.toString(double)をStringに変換する、String構造方法を使用するか、BigDecimalの静的方法valueOfを使用する.
    まとめると、stringタイプを使用してBigDecimalを初期化することを推奨するには、通常、次の3つの方法があります.
    //   
    BigDecimal bg1 = new BigDecimal("1.1");
    //   
    BigDecimal bg2 = new BigDecimal(Double.toString(1.1));
    //   
    BigDecimal bg3 = BigDecimal.valueOf(1.1);
    

    BigDecimal.value(double val)メソッドはなぜできますか?ソースコードの実装を見てみましょう.
    public static BigDecimal valueOf(double val) {
      return new BigDecimal(Double.toString(val));
     }
    

    BigDecimalの加減乗除
  • add(BigDecimal)加算
  • subtract(BigDecimal)減算
  • multiply(BigDecimal)乗算
  • divide(BigDecimal)除算
  • toString()BigDecimalオブジェクトの数値を文字列に変換します.
  • doubleValue()BigDecimalオブジェクトの値を二重精度で返します.
  • floatValue()BigDecimalオブジェクトの値を単一の精度で返します.
  • longValue()は、BigDecimalオブジェクトの値を長い整数で返します.
  • intValue()BigDecimalオブジェクトの値を整数で
  • に戻します.
  • abs()絶対値
  • //  
    BigDecimal result1 = num1.add(num2);
    BigDecimal result12 = num12.add(num22);
    //  
    BigDecimal result2 = num1.subtract(num2);
    BigDecimal result22 = num12.subtract(num22);
    //  
    BigDecimal result3 = num1.multiply(num2);
    BigDecimal result32 = num12.multiply(num22);
    //  
    BigDecimal result5 = num2.divide(num1,20,BigDecimal.ROUND_HALF_UP);
    BigDecimal result52 = num22.divide(num12,20,BigDecimal.ROUND_HALF_UP);
    //   
    BigDecimal result4 = num3.abs();
    BigDecimal result42 = num32.abs();
    

    ここで注意しなければならないのは除算演算divideです.BigDecimal除算では、4.5/1.3など、整除できない場合があります.この場合、エラーが発生します.
    java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
    

    divideメソッドには2つのリロードメソッドがあります.
  • の2つのパラメータの方法:divide(入力除数、入力roundのモード)
  • の3つのパラメータの方法:divide(入力除数、入力精度、入力roundのモード)
  • したがって,除去が不十分な場合を避けるためには,精度パラメータの伝達が必要である.
    num1.divide(num2, 2, BigDecimal.ROUND_HALF_UP);
    

    したがって、BigDecimalで除算する場合は、必ずdivideメソッドに2番目のパラメータを渡し、小数点以下数桁まで正確に定義しなければなりません.そうしないと、整除しない場合、結果が無限サイクル小数である場合、以上の異常が放出されます.
    ROUNDの8パターン
  • ROUND_UP丸めゼロから離れた丸めモード.ゼロ以外の部分を廃棄する前に、常に数値を増やします(常にゼロ以外の部分の前の数値に1を加えます).この丸めモードでは、計算値のサイズは常に減少しません.
  • ROUND_DOWNはゼロに近い丸めモードです.ある部分を捨てるまで数字を増やさない(捨てた部分の前の数字に1を加えない、つまり短くする).この丸めモードでは、計算値のサイズは常に増加しません.
  • ROUND_CEILINGは正無限大の丸めモードに近い.BigDecimalが正の場合、丸め動作とROUND_UP同じ;負の場合は、丸め動作とROUND_DOWNは同じです.この丸めモードでは、計算値は常に減少しません.
  • ROUND_FLOORは負の無限大の丸めモードに近い.BigDecimalが正の場合、丸め動作とROUND_DOWNは同じです.負の場合は、丸め動作とROUND_UPは同じです.この丸めモードでは、計算値は常に増加しません.
  • ROUND_HALF_UPは「最も近い」数字に丸められ、隣接する2つの数字との距離が等しい場合は、丸められた丸めモードになります.切り捨て部分>=0.5の場合、切り捨て動作はROUND_UP同じ;それ以外の場合は、丸め動作とROUND_DOWNは同じです.注意、これは私たちの多くの人が小学校で学んだ捨入モード(四捨五入)です.
  • ROUND_HALF_DOWNは「最も近い」数字に丸められ、2つの隣接する数字との距離が等しい場合は、丸められた丸めモードになります.切り捨て部分>0.5の場合は、切り捨て動作とROUND_UP同じ;それ以外の場合は、丸め動作とROUND_DOWNは同じ(五捨六入).
  • ROUND_HALF_EVENは「最も近い」数字に丸められ、2つの隣接する数字との距離が等しい場合、隣接する偶数に丸められる.切り捨て部分の左の数字が奇数の場合、切り捨て動作とROUND_HALF_UP同じ;偶数の場合は、丸め動作とROUND_HALF_DOWNは同じです.なお、一連の計算を繰り返すと、この丸めモードでは、累積エラーを最小限に抑えることができます.この丸めモードは「銀行家丸め法」とも呼ばれ、主に米国で使用されています.四捨六入、五分二の場合.前のビットが奇数の場合は、ビットが入り、そうでない場合は切り捨てられます.以下の例は、小数点1ビットを保持する場合、このような切り込み方式の結果である.1.15>1.2 1.25>1.2
  • ROUND_UNNECESSARYは,要求の操作が正確な結果をもたらすと断言するので,切り捨てる必要はない.正確な結果を得る操作に対してこの丸めモードを指定すると、ArithmeticExceptionが放出されます.

  • 私に注目してください