1. はじめに

Go Decimalライブラリは、Go言語で任意精度の固定小数点の操作に便利なツールです。これにより、精度を失うことなく加算、減算、乗算、および除算が可能です。さらに、データベース/SQLのシリアル化/デシリアル化や、JSON/XMLのシリアル化/デシリアル化などの機能を提供します。

2. インストール

Go Decimalライブラリをインストールするには、以下のコマンドを使用します。

go get github.com/shopspring/decimal

なお、Decimalライブラリを使用するには、Goバージョンが1.7以上である必要があります。

3. 基本的な使用法

GoプログラムでDecimalライブラリを使用するには、「github.com/shopspring/decimal」パッケージをインポートします。以下は基本的な使用法を示す簡単なコード例です。

package main

import (
	"fmt"
	"github.com/shopspring/decimal"
)

func main() {
	price, err := decimal.NewFromString("136.02")
	if err != nil {
		panic(err)
	}

	quantity := decimal.NewFromInt(3)

	fee, _ := decimal.NewFromString(".035")
	taxRate, _ := decimal.NewFromString(".08875")

	subtotal := price.Mul(quantity)
	preTax := subtotal.Mul(fee).Add(decimal.NewFromFloat(1))
	total := preTax.Mul(taxRate).Add(decimal.NewFromFloat(1))

	fmt.Println("小計:", subtotal)                  // Subtotal: 408.06
	fmt.Println("税を含まない金額:", preTax)                     // Pre-tax: 422.3421
	fmt.Println("税額:", total.Sub(preTax))            // Taxes: 37.482861375
	fmt.Println("合計:", total)                         // Total: 459.824961375
	fmt.Println("税率:", total.Sub(preTax).Div(preTax)) // Tax rate: 0.08875
}

4. Decimal変数の作成

Decimalライブラリは、Decimal変数を作成するためのさまざまなメソッドを提供します。以下がサポートされているAPIです。

  • decimal.NewFromBigInt(value *big.Int, exp int32) Decimal
  • decimal.NewFromFloat(value float64) Decimal
  • decimal.NewFromFloat32(value float32) Decimal
  • decimal.NewFromFloatWithExponent(value float64, exp int32) Decimal
  • decimal.NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error)
  • decimal.NewFromInt(value int64) Decimal
  • decimal.NewFromInt32(value int32) Decimal
  • decimal.NewFromString(value string) (Decimal, error)
  • decimal.RequireFromString(value string) Decimal

5. 算術演算

Go Decimalライブラリでは、Decimal変数に対して実行できるいくつかの算術演算が提供されています。以下はサポートされている演算の一部です。

  • Add(d2 Decimal) Decimal: 2つのDecimal値を加算し、結果を返します。
  • Sub(d2 Decimal) Decimal: 1つのDecimal値から別のDecimal値を減算し、結果を返します。
  • Div(d2 Decimal) Decimal: 1つのDecimal値を別のDecimal値で割り、結果を返します。
  • DivRound(d2 Decimal, precision int32) Decimal: 1つのDecimal値を別のDecimal値で割り、指定された精度で結果を返します。
  • Mod(d2 Decimal) Decimal: 1つのDecimal値を別のDecimal値で割った余りを計算し、結果を返します。
  • Mul(d2 Decimal) Decimal: 2つのDecimal値を乗算し、結果を返します。

これらの演算を使用してDecimal値に対する一般的な算術計算を実行できます。以下はこれらの演算の使用を示す例です。

price, _ := decimal.NewFromString("136.02")
quantity := decimal.NewFromInt(3)

subtotal := price.Mul(quantity)
tax := subtotal.Mul(decimal.NewFromFloat(0.08875))

total := subtotal.Add(tax)

fmt.Println("小計:", subtotal) // Subtotal: 408.06
fmt.Println("税:", tax)           // Tax: 36.244985
fmt.Println("合計:", total)       // Total: 444.304985

上記の例では、Mul()メソッドを使用してpricequantityを乗算して小計を計算しました。その後、税率を乗じてを計算し、最後にAdd()メソッドを使用して小計を加算して合計を計算しています。

6. 丸め演算

Go Decimalライブラリは、指定された精度にDecimal値を丸めるために使用できるいくつかの丸め演算を提供します。以下は利用可能な丸め演算のいくつかです。

  • Round(places int32) Decimal:Decimalを指定された小数点以下の桁数に丸めます。
  • RoundBank(places int32) Decimal:Decimalを銀行家の丸めを使用して、指定された小数点以下の桁数まで丸めます。
  • RoundCash(interval uint8) Decimal:5セント、10セント、25セント、50セント、または1ドルなどの特定の間隔でDecimalを丸めます。
  • RoundCeil(places int32) Decimal:Decimalを正の無限大に向かって丸めます。
  • RoundDown(places int32) Decimal:Decimalをゼロに向かって丸めます。
  • RoundFloor(places int32) Decimal:Decimalを負の無限大に向かって丸めます。
  • RoundUp(places int32) Decimal:Decimalをゼロから離れて丸めます。

6.1. Round

RoundはDecimalを指定された小数点以下の桁数に丸めます。もしplaces < 0なら、整数部を最も近い10^(-places)に丸めます。

NewFromFloat(5.45).Round(1).String() // 出力:"5.5"
NewFromFloat(545).Round(-1).String() // 出力:"550"

6.2. RoundBank

RoundBankはDecimalを指定された小数点以下の桁数に丸めます。もし、最後に丸める桁と最も近い2つの整数の距離が同じであれば、丸められた値は偶数になります。もしplaces < 0なら、整数部を最も近い10^(-places)に丸めます。

NewFromFloat(5.45).RoundBank(1).String() // 出力:"5.4"
NewFromFloat(545).RoundBank(-1).String() // 出力:"540"
NewFromFloat(5.46).RoundBank(1).String() // 出力:"5.5"
NewFromFloat(546).RoundBank(-1).String() // 出力:"550"
NewFromFloat(5.55).RoundBank(1).String() // 出力:"5.6"
NewFromFloat(555).RoundBank(-1).String() // 出力:"560"

6.3. RoundCash

RoundCash(キャッシュ/ペニー/アイリッシュ丸めとも呼ばれる)はDecimalを特定の間隔に丸めます。現金取引の支払可能な金額は最小通貨単位の最も近い倍数に丸められます。利用可能な間隔は、5、10、25、50、100です。それ以外の数値は例外を引き起こします。

  5:   5セントの丸め 3.43 => 3.45
 10:  10セントの丸め 3.45 => 3.50(5は切り上げ)
 25:  25セントの丸め 3.41 => 3.50
 50:  50セントの丸め 3.75 => 4.00
100: 100セントの丸め 3.50 => 4.00

6.4. RoundCeil

RoundCeilはDecimalを正の無限大に向かって丸めます。

NewFromFloat(545).RoundCeil(-2).String()   // 出力:"600"
NewFromFloat(500).RoundCeil(-2).String()   // 出力:"500"
NewFromFloat(1.1001).RoundCeil(2).String() // 出力:"1.11"
NewFromFloat(-1.454).RoundCeil(1).String() // 出力:"-1.5"

6.5. RoundDown

RoundDownはDecimalをゼロに向かって丸めます。

NewFromFloat(545).RoundDown(-2).String()   // 出力:"500"
NewFromFloat(-500).RoundDown(-2).String()   // 出力:"-500"
NewFromFloat(1.1001).RoundDown(2).String() // 出力:"1.1"
NewFromFloat(-1.454).RoundDown(1).String() // 出力:"-1.5"

6.6. RoundFloor

RoundFloorは小数を負の無限大に向かって丸めます。

NewFromFloat(545).RoundFloor(-2).String()   // 出力: "500"
NewFromFloat(-500).RoundFloor(-2).String()   // 出力: "-500"
NewFromFloat(1.1001).RoundFloor(2).String() // 出力: "1.1"
NewFromFloat(-1.454).RoundFloor(1).String() // 出力: "-1.4"

6.7. RoundUp

RoundUpは小数をゼロから遠ざけて丸めます。

NewFromFloat(545).RoundUp(-2).String()   // 出力: "600"
NewFromFloat(500).RoundUp(-2).String()   // 出力: "500"
NewFromFloat(1.1001).RoundUp(2).String() // 出力: "1.11"
NewFromFloat(-1.454).RoundUp(1).String() // 出力: "-1.4"

7. Decimal型を文字列に変換する

Go Decimalライブラリは、Decimalの値を文字列表現に変換するためのメソッドを提供しています。以下は使用可能なメソッドの一部です。

  • String(): string: 固定小数点での小数の文字列表現を返します。
  • StringFixed(places int32) string: 指定された小数点以下の桁数で四捨五入された文字列表現を返します。
  • StringFixedBank(places int32) string: 指定された小数点以下の桁数で(銀行家の丸め方式で)四捨五入された文字列表現を返します。

必要に応じて適切なメソッドを選択できます。以下は、Decimal数を文字列に変換する例です:

d := decimal.NewFromFloat(5.45)
str := d.String()

fmt.Println("Decimal数の文字列表現:", str) // Decimal数の文字列表現: 5.45

// StringFixedの例
NewFromFloat(0).StringFixed(2) // 出力: "0.00"
NewFromFloat(0).StringFixed(0) // 出力: "0"
NewFromFloat(5.45).StringFixed(0) // 出力: "5"
NewFromFloat(5.45).StringFixed(1) // 出力: "5.5"
NewFromFloat(5.45).StringFixed(2) // 出力: "5.45"
NewFromFloat(5.45).StringFixed(3) // 出力: "5.450"
NewFromFloat(545).StringFixed(-1) // 出力: "550"

// StringFixedBankの例
NewFromFloat(0).StringFixedBank(2) // 出力: "0.00"
NewFromFloat(0).StringFixedBank(0) // 出力: "0"
NewFromFloat(5.45).StringFixedBank(0) // 出力: "5"
NewFromFloat(5.45).StringFixedBank(1) // 出力: "5.4"
NewFromFloat(5.45).StringFixedBank(2) // 出力: "5.45"
NewFromFloat(5.45).StringFixedBank(3) // 出力: "5.450"
NewFromFloat(545).StringFixedBank(-1) // 出力: "540"

8. よくある質問

Q: 直接float64を使用しないのはなぜですか? A: float64は0.1のような数値を正確に表現することができないため、小さな誤差が発生する可能性があります。金融計算に関わる状況では、これらの誤差が時間の経過とともに蓄積され、重大な問題を引き起こす可能性があります。

Q: big.Ratを直接使用しないのはなぜですか? A: big.Ratは有理数を表現することができますが、通貨を表現するには適していません。小数は精度を失うことなく小数点以下を正確に表現できるため、金融計算に適しています。

Q: APIがbig.Intに似ていないのはなぜですか? A: DecimalライブラリのAPIは、パフォーマンスよりも使いやすさと正確さを優先しています。一方、big.IntのAPIはパフォーマンス上の理由からメモリ割り当てを削減しているため、複雑でエラーを起こしやすいコードにつながる可能性があります。DecimalライブラリのAPIは、シンプルで理解しやすい設計になっています。