package moneybase import ( "math/big" "strings" decimalbase "git.sendico.io/tech/sxchnge/bff/pkg/decimal" "git.sendico.io/tech/sxchnge/bff/pkg/merrors" ) // SourceRoundedConversionMatches reports whether sourceAmount is the minimum // source-scale amount that covers targetAmount at rate. func SourceRoundedConversionMatches(sourceAmount, targetAmount, rate string, sourceScale uint32) (bool, error) { if err := ValidateScale("source scale", sourceScale); err != nil { return false, err } source, err := positiveRat(sourceAmount, "source amount") if err != nil { return false, err } target, err := positiveRat(targetAmount, "target amount") if err != nil { return false, err } rateRat, err := positiveRat(rate, "conversion rate") if err != nil { return false, err } exactSource := new(big.Rat).Quo(target, rateRat) roundedSource := ceilDecimalToScale(exactSource, sourceScale) return roundedSource.Cmp(source) == 0 && source.Cmp(exactSource) >= 0, nil } func positiveRat(value string, field string) (*big.Rat, error) { parsed, err := decimalbase.RatFromString(strings.TrimSpace(value)) if err != nil || parsed.Sign() <= 0 { return nil, merrors.InvalidArgument(field + " must be a positive decimal") } return parsed, nil } func ceilDecimalToScale(value *big.Rat, scale uint32) *big.Rat { factor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil) scaled := new(big.Rat).Mul(value, new(big.Rat).SetInt(factor)) q, r := new(big.Int).QuoRem(scaled.Num(), scaled.Denom(), new(big.Int)) if r.Sign() > 0 { q.Add(q, big.NewInt(1)) } return new(big.Rat).SetFrac(q, factor) }