1. 소개

Go Decimal 라이브러리는 Go 언어에서 임의 정밀도의 고정 소수점(decimal)을 다루는 강력한 도구입니다. 정확도를 잃지 않고 덧셈, 뺄셈, 곱셈 및 나눗셈 연산이 가능합니다. 또한 데이터베이스/SQL 직렬화/역직렬화뿐만 아니라 JSON/XML 직렬화/역직렬화와 같은 기능을 제공합니다.

2. 설치

Go Decimal 라이브러리를 설치하려면 다음 명령을 사용할 수 있습니다:

go get github.com/shopspring/decimal

Decimal 라이브러리는 Go 버전 >=1.7이 필요합니다.

3. 기본 사용

Go 프로그램에서 Decimal 라이브러리를 사용하려면 "github.com/shopspring/decimal" 패키지를 import하세요. 기본 사용법을 보여주는 간단한 예제 코드는 다음과 같습니다:

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)                  // Subtotal: 408.06
	fmt.Println("Pre-tax:", preTax)                     // Pre-tax: 422.3421
	fmt.Println("Taxes:", total.Sub(preTax))            // Taxes: 37.482861375
	fmt.Println("Total:", total)                         // Total: 459.824961375
	fmt.Println("Tax rate:", 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: 두 개의 Decimal 값을 더하고 결과를 반환합니다.
  • Sub(d2 Decimal) Decimal: 한 Decimal 값을 다른 값에서 빼고 결과를 반환합니다.
  • Div(d2 Decimal) Decimal: 한 Decimal 값을 다른 값으로 나누고 결과를 반환합니다.
  • DivRound(d2 Decimal, precision int32) Decimal: 한 Decimal 값을 다른 값으로 나누고 지정된 정밀도로 결과를 반환합니다.
  • Mod(d2 Decimal) Decimal: 한 Decimal 값을 다른 값으로 나눈 나머지를 계산하고 결과를 반환합니다.
  • Mul(d2 Decimal) Decimal: 두 개의 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) // Subtotal: 408.06
fmt.Println("Tax:", tax)           // Tax: 36.244985
fmt.Println("Total:", total)       // Total: 444.304985

6. 반올림 연산

Go Decimal 라이브러리는 소수점 값을 지정된 정밀도로 반올림하는 데에 사용할 수 있는 여러 가지 반올림 연산을 제공합니다. 다음은 사용 가능한 반올림 연산 목록입니다:

  • Round(places int32) Decimal: 소수점을 지정된 소수 자릿수로 반올림합니다.
  • RoundBank(places int32) Decimal: 은행 방식으로 소수점을 지정된 소수 자릿수로 반올림합니다.
  • RoundCash(interval uint8) Decimal: 5센트, 10센트, 25센트, 50센트 또는 1달러 등 특정 간격으로 소수를 반올림합니다.
  • RoundCeil(places int32) Decimal: 소수를 양의 무한대 방향으로 반올림합니다.
  • RoundDown(places int32) Decimal: 소수를 0 방향으로 반올림합니다.
  • RoundFloor(places int32) Decimal: 소수를 음의 무한대 방향으로 반올림합니다.
  • RoundUp(places int32) Decimal: 소수를 0에서 떨어진 방향으로 반올림합니다.

6.1. Round

Round는 소수를 지정된 소수 자릿수로 반올림합니다. 만약 places가 음수이면, 정수 부분을 가장 가까운 10^(-places)로 반올림합니다.

NewFromFloat(5.45).Round(1).String() // 결과: "5.5"
NewFromFloat(545).Round(-1).String() // 결과: "550"

6.2. RoundBank

RoundBank는 소수를 특정 소수 자릿수로 반올림합니다. 만약 가장 가까운 두 정수까지의 거리가 동일하면, 반올림된 값은 짝수가 됩니다.

만약 places가 음수이면, 정수 부분을 가장 가까운 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(또는 cash/penny/Irish rounding으로도 알려짐)는 특정 간격으로 소수를 반올림합니다. 현금 거래의 결제 금액은 가장 작은 통화 단위의 가장 가까운 배수로 반올림됩니다. 가능한 간격은: 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은 소수를 양의 무한대 방향으로 반올림합니다.

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은 소수를 0 방향으로 반올림합니다.

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()   // Output: "500"
NewFromFloat(-500).RoundFloor(-2).String()   // Output: "-500"
NewFromFloat(1.1001).RoundFloor(2).String() // Output: "1.1"
NewFromFloat(-1.454).RoundFloor(1).String() // Output: "-1.4"

6.7. RoundUp

RoundUp은 소수점을 0으로부터 멀어지는 방향으로 반올림합니다.

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

7. 문자열로 Decimal 타입 변환

Go Decimal 라이브러리는 Decimal 값을 문자열 표현으로 변환하는 메서드를 제공합니다. 여기에 사용 가능한 메서드 목록이 있습니다:

  • String(): string: 고정된 소수점을 가진 소수점 수를 문자열로 반환합니다.
  • StringFixed(places int32) string: 지정된 소수 자릿수로 반올림된 문자열 표현을 반환합니다.
  • StringFixedBank(places int32) string: 지정된 소수 자릿수로 반올림(은행 방식)된 문자열 표현을 반환합니다.

필요에 따라 적절한 메서드를 선택할 수 있습니다. 아래는 소수를 문자열로 변환하는 예시입니다:

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

fmt.Println("소수의 문자열 표현:", str) // 소수의 문자열 표현: 5.45

// StringFixed 예시
NewFromFloat(0).StringFixed(2) // Output: "0.00"
NewFromFloat(0).StringFixed(0) // Output: "0"
NewFromFloat(5.45).StringFixed(0) // Output: "5"
NewFromFloat(5.45).StringFixed(1) // Output: "5.5"
NewFromFloat(5.45).StringFixed(2) // Output: "5.45"
NewFromFloat(5.45).StringFixed(3) // Output: "5.450"
NewFromFloat(545).StringFixed(-1) // Output: "550"

// StringFixedBank 예시
NewFromFloat(0).StringFixedBank(2) // Output: "0.00"
NewFromFloat(0).StringFixedBank(0) // Output: "0"
NewFromFloat(5.45).StringFixedBank(0) // Output: "5"
NewFromFloat(5.45).StringFixedBank(1) // Output: "5.4"
NewFromFloat(5.45).StringFixedBank(2) // Output: "5.45"
NewFromFloat(5.45).StringFixedBank(3) // Output: "5.450"
NewFromFloat(545).StringFixedBank(-1) // Output: "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는 간단하고 이해하기 쉽도록 설계되었습니다.