diff --git a/scw/custom_types.go b/scw/custom_types.go index 658e618ec..8840088b4 100644 --- a/scw/custom_types.go +++ b/scw/custom_types.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net" + "strconv" "strings" "time" @@ -76,13 +77,43 @@ type Money struct { Nanos int32 `json:"nanos,omitempty"` } -// NewMoneyFromFloat conerts a float with currency to a Money object. -func NewMoneyFromFloat(value float64, currency string) *Money { - return &Money{ - CurrencyCode: currency, +// NewMoneyFromFloat converts a float with currency to a Money. +// +// value: The float value. +// currencyCode: The 3-letter currency code defined in ISO 4217. +// precision: The number of digits after the decimal point used to parse the nanos part of the value. +// +// Examples: +// - (value = 1.3333, precision = 2) => Money{Units = 1, Nanos = 330000000} +// - (value = 1.123456789, precision = 9) => Money{Units = 1, Nanos = 123456789} +func NewMoneyFromFloat(value float64, currencyCode string, precision int) *Money { + if precision > 9 { + panic(fmt.Errorf("max precision is 9")) + } + + strValue := strconv.FormatFloat(value, 'f', precision, 64) + parts := strings.Split(strValue, ".") + + money := &Money{ + CurrencyCode: currencyCode, Units: int64(value), - Nanos: int32((value - float64(int64(value))) * 1000000000), + Nanos: 0, + } + + // Handle nanos. + if len(parts) == 2 { + // Add leading zeros. + strNanos := parts[1] + "000000000"[len(parts[1]):] + + n, err := strconv.ParseInt(strNanos, 10, 32) + if err != nil { + panic(fmt.Errorf("invalid nanos %s", strNanos)) + } + + money.Nanos = int32(n) } + + return money } // String returns the string representation of Money. diff --git a/scw/custom_types_test.go b/scw/custom_types_test.go index d55c173a9..d9b860964 100644 --- a/scw/custom_types_test.go +++ b/scw/custom_types_test.go @@ -11,20 +11,116 @@ import ( "github.com/scaleway/scaleway-sdk-go/internal/testhelpers" ) +func TestMoney_NewMoneyFromFloat(t *testing.T) { + cases := []struct { + value float64 + currency string + precision int + want *Money + }{ + { + value: 0.0, + currency: "EUR", + precision: 0, + want: &Money{ + CurrencyCode: "EUR", + Units: 0, + Nanos: 0, + }, + }, + { + value: 1.0, + currency: "EUR", + precision: 3, + want: &Money{ + CurrencyCode: "EUR", + Units: 1, + Nanos: 0, + }, + }, + { + value: 1.3, + currency: "EUR", + precision: 3, + want: &Money{ + CurrencyCode: "EUR", + Units: 1, + Nanos: 300000000, + }, + }, + { + value: 1.333, + currency: "EUR", + precision: 2, + want: &Money{ + CurrencyCode: "EUR", + Units: 1, + Nanos: 330000000, + }, + }, + { + value: 1.04, + currency: "EUR", + precision: 1, + want: &Money{ + CurrencyCode: "EUR", + Units: 1, + Nanos: 0, + }, + }, + { + value: 1.05, + currency: "EUR", + precision: 1, + want: &Money{ + CurrencyCode: "EUR", + Units: 1, + Nanos: 100000000, + }, + }, + { + value: 1.123456789, + currency: "EUR", + precision: 9, + want: &Money{ + CurrencyCode: "EUR", + Units: 1, + Nanos: 123456789, + }, + }, + { + value: 1.999999999, + currency: "EUR", + precision: 9, + want: &Money{ + CurrencyCode: "EUR", + Units: 1, + Nanos: 999999999, + }, + }, + } + + for _, c := range cases { + t.Run(c.want.String(), func(t *testing.T) { + testhelpers.Equals(t, c.want, NewMoneyFromFloat(c.value, c.currency, c.precision)) + }) + } +} + func TestMoney_String(t *testing.T) { cases := []struct { - money Money + money *Money want string }{ { - money: Money{ + money: &Money{ CurrencyCode: "EUR", Units: 10, }, want: "€ 10.00", }, { - money: Money{ + money: &Money{ CurrencyCode: "USD", Units: 10, Nanos: 1, @@ -32,28 +128,35 @@ func TestMoney_String(t *testing.T) { want: "$ 10.000000001", }, { - money: Money{ + money: &Money{ CurrencyCode: "EUR", Nanos: 100000000, }, want: "€ 0.10", }, { - money: Money{ + money: &Money{ CurrencyCode: "EUR", Nanos: 500000, }, want: "€ 0.0005", }, { - money: Money{ + money: &Money{ + CurrencyCode: "EUR", + Nanos: 333000000, + }, + want: "€ 0.333", + }, + { + money: &Money{ CurrencyCode: "EUR", Nanos: 123456789, }, want: "€ 0.123456789", }, { - money: Money{ + money: &Money{ CurrencyCode: "?", }, want: "? 0.00",