1. مقدمه

کتابخانه Decimal Go ابزار قدرتمندی برای کنترل اعشارهای نقطه‌ای با دقت دلخواه در زبان برنامه نویسی Go است. این کتابخانه امکان انجام عملیات جمع، تفریق، ضرب و تقسیم را بدون از دست دادن دقت فراهم می‌کند. علاوه بر این، امکاناتی مانند سریالیزیشن/دیسریالیزیشن پایگاه داده/SQL و سریالیزیشن/دیسریالیزیشن JSON/XML را نیز فراهم می‌کند.

2. نصب

برای نصب کتابخانه Decimal Go می‌توانید از دستور زیر استفاده کنید:

go get github.com/shopspring/decimal

لطفاً توجه داشته باشید که کتابخانه Decimal نسخه Go >=1.7 را مورد نیاز دارد.

3. استفاده ابتدایی

برای استفاده از کتابخانه Decimal در یک برنامه Go، بسته "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)                  // 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. عملیات حسابی

کتابخانه Decimal Go چند عملیات حسابی را بر روی متغیرهای 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 چند عملیات گرد کردن فراهم می‌کند که می‌توان از آنها برای گرد کردن مقادیر دسیمال به دقت مشخص استفاده کرد. در ادامه چند عملیات گرد کردن موجود آمده است:

  • 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: عدد دسیمال را به سمت صفر گرد می‌کند.
  • RoundFloor(places int32) Decimal: عدد دسیمال را به سمت بی‌نهایت منفی گرد می‌کند.
  • RoundUp(places int32) Decimal: عدد دسیمال را از صفر دورتر می‌کند.

6.1. گرد کردن (Round)

عدد دسیمال را به تعداد مشخصی اعشار می‌گرداند. اگر places < 0 باشد، بخش صحیح عدد را به نزدیک‌ترین 10^(-places) گرد می‌کند.

NewFromFloat(5.45).Round(1).String() // خروجی: "5.5"
NewFromFloat(545).Round(-1).String() // خروجی: "550"

6.2. گرد کردن بانکی (RoundBank)

عدد دسیمال را به تعداد مشخصی اعشار می‌گرداند. اگر فاصله بین آخرین رقمی که باید گرد شود و دو عدد صحیح نزدیک‌تر برابر باشد، مقدار گرد شده عدد میانی را می‌گیرد.

اگر 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)

گرد کردن نقدی (همچنین به عنوان گرد کردن پول/پنی/ایریش شناخته می‌شود) عدد دسیمال را به بازه‌های خاص گرد می‌کند. مقدار قابل پرداخت در یک تراکنش نقدی به نزدیک‌ترین ضریبی از واحد کوچک پولی گرد می‌شود. بازه‌های موجود عبارتند از: 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)

عدد دسیمال را به سمت بی‌نهایت مثبت گرد می‌کند.

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)

عدد دسیمال را به سمت صفر گرد می‌کند.

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 به نمایش رشته‌ای فراهم می‌کند. در زیر چند متد موجود آمده است:

  • 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) // خروجی: "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. سوالات متداول

پ: چرا مستقیماً از float64 استفاده نمی‌کنیم؟ ج: float64 نمی‌تواند اعدادی مانند 0.1 را به دقت نمایان کند که می‌تواند منجر به خطاهای کوچک شود. در شرایط محاسبات مالی، این خطاها ممکن است به مرور زمان انباشته شده و مشکلات قابل توجهی ایجاد کنند.

پ: چرا مستقیماً از big.Rat استفاده نمی‌کنیم؟ ج: گرچه big.Rat می‌تواند اعداد راشناگاهی را نمایان کند، اما برای نمایان کردن واحد پولی مناسب نیست. اعداد اعشاری بهتر برای محاسبات مالی هستند زیرا می‌توانند بدون از بین رفتن دقت، اعشارهای دهی را به دقت نمایان کنند.

پ: چرا API ماشابه big.Int نیست؟ **ج: API کتابخانه Decimal اولویت می‌دهد به سادگی و صحت نسبت به کارایی. در حالی که API big.Int به دلایل کارایی از تخصیص حافظه‌ها برای کاهش کار، منجر به کد پیچیده و مستعد خطا می‌شود. API کتابخانه Decimal به طراحی ساده و آسان برای درک می‌پردازد.