// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package money import ( "errors" pb "github.com/open-telemetry/opentelemetry-demo/src/checkoutservice/genproto/oteldemo" ) const ( nanosMin = -999999999 nanosMax = +999999999 nanosMod = 1000000000 ) var ( ErrInvalidValue = errors.New("one of the specified money values is invalid") ErrMismatchingCurrency = errors.New("mismatching currency codes") ) // IsValid checks if specified value has a valid units/nanos signs and ranges. func IsValid(m *pb.Money) bool { return signMatches(m) && validNanos(m.GetNanos()) } func signMatches(m *pb.Money) bool { return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) } func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } // IsZero returns true if the specified money value is equal to zero. func IsZero(m *pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } // IsPositive returns true if the specified money value is valid and is // positive. func IsPositive(m *pb.Money) bool { return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) } // IsNegative returns true if the specified money value is valid and is // negative. func IsNegative(m *pb.Money) bool { return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) } // AreSameCurrency returns true if values l and r have a currency code and // they are the same values. func AreSameCurrency(l, r *pb.Money) bool { return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" } // AreEquals returns true if values l and r are the equal, including the // currency. This does not check validity of the provided values. func AreEquals(l, r *pb.Money) bool { return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() } // Negate returns the same amount with the sign negated. func Negate(m *pb.Money) *pb.Money { return &pb.Money{ Units: -m.GetUnits(), Nanos: -m.GetNanos(), CurrencyCode: m.GetCurrencyCode()} } // Must panics if the given error is not nil. This can be used with other // functions like: "m := Must(Sum(a,b))". func Must(v *pb.Money, err error) *pb.Money { if err != nil { panic(err) } return v } // Sum adds two values. Returns an error if one of the values are invalid or // currency codes are not matching (unless currency code is unspecified for // both). func Sum(l, r *pb.Money) (*pb.Money, error) { if !IsValid(l) || !IsValid(r) { return &pb.Money{}, ErrInvalidValue } else if l.GetCurrencyCode() != r.GetCurrencyCode() { return &pb.Money{}, ErrMismatchingCurrency } units := l.GetUnits() + r.GetUnits() nanos := l.GetNanos() + r.GetNanos() if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { // same sign units += int64(nanos / nanosMod) nanos = nanos % nanosMod } else { // different sign. nanos guaranteed to not to go over the limit if units > 0 { units-- nanos += nanosMod } else { units++ nanos -= nanosMod } } return &pb.Money{ Units: units, Nanos: nanos, CurrencyCode: l.GetCurrencyCode()}, nil } // MultiplySlow is a slow multiplication operation done through adding the value // to itself n-1 times. func MultiplySlow(m *pb.Money, n uint32) *pb.Money { out := m for n > 1 { out = Must(Sum(out, m)) n-- } return out }