1. การแนะนำ

ไลบรารี Go Decimal เป็นเครื่องมือที่มีประสิทธิภาพในการจัดการทศนิยมที่มีความแม่นยำแบบคงที่ในภาษา Go โดยมันช่วยในการบวก ลบ คูณ และหารโดยไม่สูญเสียความแม่นยำ นอกจากนี้ยังมีความสามารถในการจัดการการแปลงข้อมูลเช่น ซีเรียลไลซ์ / ดีซีเรียลไซเรียลเลชัน, รวมทั้ง ซีเรียลสลับ / ดีซียร์แอ็กซ์เอลไชเรียลเลชัน JSON/XML

2. การติดตั้ง

เพื่อที่จะติดตั้งไลบรารี Go Decimal คุณสามารถใช้คำสั่งต่อไปนี้:

go get github.com/shopspring/decimal

โปรดทราบว่าไลบรารี Decimal ต้องการ Go version >=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 มีเมทอดต่าง ๆ เพื่อสร้างตัวแปรทศนิยม ต่อไปนี้คือ 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 มีการดำเนินการทางคณิตศาสตร์หลายอย่างที่สามารถกระทำกับตัวแปรทศนิยม ต่อไปนี้คือการดำเนินการที่รองรับ:

  • Add(d2 Decimal) Decimal: บวกค่าทศนิยมสองตัวและคืนผลลัพธ์
  • Sub(d2 Decimal) Decimal: ลบค่าทศนิยมหนึ่งจากอีกตัวหนึ่งและคืนค่า
  • Div(d2 Decimal) Decimal: หารค่าทศนิยมหนึ่งด้วยอีกตัวและคืนค่า
  • DivRound(d2 Decimal, precision int32) Decimal: หารค่าทศนิยมหนึ่งด้วยอีกตัวและคืนค่าด้วยความแม่นยำที่ระบุ
  • Mod(d2 Decimal) Decimal: คำนวณเศษ (เศษ) ของค่าทศนิยมหนึ่งที่ถูกหารด้วยอีกตัวและคืนค่า
  • Mul(d2 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

ณตัวอย่างข้างต้น เราใช้เมทอด Mul() เพื่อคำนวณ subtotal โดยการคูณ price และ quantity จากนั้นเราคำนวณ tax โดยการคูณ subtotal ด้วยอัตราภาษี สุดท้ายเราคำนวณ total โดยการบวก subtotal และ tax โดยใช้เมทอด Add()

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: ปัดเศษทศนิยมไปทางศูนย์
  • RoundFloor(places int32) Decimal: ปัดเศษทศนิยมไปทางลบไปยังจำนวนทศนิยมที่กำหนด
  • RoundUp(places int32) Decimal: ปัดเศษทศนิยมไปห่างจากศูนย์

6.1. การปัดเศษ (Round)

Round จะปัดเศษทศนิยมไปยังจำนวนทศนิยมที่กำหนด หาก places < 0 จะปัดส่วนจำนวนเต็มไปยัง 10 ที่ใกล้ที่สุดของ 10^(-places)

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

6.2. การปัดตามหลักของธนาคาร (RoundBank)

RoundBank จะปัดเศษทศนิยมไปยังจำนวนทศนิยมที่กำหนด หากระยะห่างระหว่างหลักสุดท้ายที่จะปัดและจำนวนเต็มที่ใกล้ที่สุดเท่ากัน ค่าที่ปัดจะถือเป็นคู่

หาก places < 0 หน่วยจำนวนเต็มจะถูกปัดไปยัง 10^(-places) ที่ใกล้ที่สุด

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

6.3. การปัดตามการทำธุรกรรมเงินสด (RoundCash)

RoundCash (หรือรูปแบบการปัดเงิน/เศษเงิน/การปัดแบบไอริช) จะปัดเศษทศนิยมไปยังระยะเวลาที่ระบุ จำนวนเงินที่ต้องจ่ายในธุรกรรมเงินสดจะถูกปัดไปยังตัวคูณที่ใกล้ที่สุดของหน่วยเงินที่ขนานน้อยที่สุด ระยะเวลาที่ใช้งานได้ คือ: 5, 10, 25, 50 และ 100 สตางค์ หมายเลขอื่น ๆ จะทำให้เกิดข้อยกเว้น

  5:   5 cents rounding 3.43 => 3.45
 10:  10 cents rounding 3.45 => 3.50 (5 is rounded up)
 25:  25 cents rounding 3.41 => 3.50
 50:  50 cents rounding 3.75 => 4.00
100: 100 cents rounding 3.50 => 4.00

6.4. การปัดทางบวก (RoundCeil)

RoundCeil จะปัดเศษทศนิยมไปทางบวก

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

6.5. การปัดทางศูนย์ (RoundDown)

RoundDown จะปัดเศษทศนิยมไปทางศูนย์

NewFromFloat(545).RoundDown(-2).String()   // Output: "500"
NewFromFloat(-500).RoundDown(-2).String()   // Output: "-500"
NewFromFloat(1.1001).RoundDown(2).String() // Output: "1.1"
NewFromFloat(-1.454).RoundDown(1).String() // Output: "-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. แปลงประเภททศนิยมเป็นสตริง

ไลบรารี่ก็จะทำการแปลงค่าทศนิยมเป็นรูปแบบสตริงได้ ซึ่งมีเมธอดต่าง ๆ ให้เลือกใช้ดังนี้:

  • 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 ถูกออกแบบให้เป็นไปอย่างง่ายและเข้าใจได้ง่าย