amount.go (4076B)
1 // This file is part of taler-go, the Taler Go implementation. 2 // Copyright (C) 2022 Martin Schanzenbach 3 // 4 // Taler Go is free software: you can redistribute it and/or modify it 5 // under the terms of the GNU Affero General Public License as published 6 // by the Free Software Foundation, either version 3 of the License, 7 // or (at your option) any later version. 8 // 9 // Taler Go is distributed in the hope that it will be useful, but 10 // WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 // Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 // 17 // SPDX-License-Identifier: AGPL3.0-or-later 18 19 package util 20 21 import ( 22 "errors" 23 "fmt" 24 "math" 25 "regexp" 26 "strconv" 27 "strings" 28 ) 29 30 // The GNU Taler Amount object 31 type Amount struct { 32 33 // The type of currency, e.g. EUR 34 Currency string 35 36 // The value (before the ".") 37 Value uint64 38 39 // The fraction (after the ".", optional) 40 Fraction uint64 41 } 42 43 // The maximim length of a fraction (in digits) 44 const FractionalLength = 8 45 46 // The base of the fraction. 47 const FractionalBase = 1e8 48 49 // The maximum value 50 var MaxAmountValue = uint64(math.Pow(2, 52)) 51 52 // Create a new amount from value and fraction in a currency 53 func NewAmount(currency string, value uint64, fraction uint64) Amount { 54 return Amount{ 55 Currency: currency, 56 Value: value, 57 Fraction: fraction, 58 } 59 } 60 61 // Subtract the amount b from a and return the result. 62 // a and b must be of the same currency and a >= b 63 func (a *Amount) Sub(b Amount) (*Amount, error) { 64 if a.Currency != b.Currency { 65 return nil, errors.New("Currency mismatch!") 66 } 67 v := a.Value 68 f := a.Fraction 69 if a.Fraction < b.Fraction { 70 v -= 1 71 f += FractionalBase 72 } 73 f -= b.Fraction 74 if v < b.Value { 75 return nil, errors.New("Amount Overflow!") 76 } 77 v -= b.Value 78 r := Amount{ 79 Currency: a.Currency, 80 Value: v, 81 Fraction: f, 82 } 83 return &r, nil 84 } 85 86 // Add b to a and return the result. 87 // Returns an error if the currencies do not match or the addition would 88 // cause an overflow of the value 89 func (a *Amount) Add(b Amount) (*Amount, error) { 90 if a.Currency != b.Currency { 91 return nil, errors.New("Currency mismatch!") 92 } 93 v := a.Value + 94 b.Value + 95 uint64(math.Floor((float64(a.Fraction)+float64(b.Fraction))/FractionalBase)) 96 97 if v >= MaxAmountValue { 98 return nil, errors.New(fmt.Sprintf("Amount Overflow (%d > %d)!", v, MaxAmountValue)) 99 } 100 f := uint64((a.Fraction + b.Fraction) % FractionalBase) 101 r := Amount{ 102 Currency: a.Currency, 103 Value: v, 104 Fraction: f, 105 } 106 return &r, nil 107 } 108 109 // Parses an amount string in the format <currency>:<value>[.<fraction>] 110 func ParseAmount(s string) (*Amount, error) { 111 re, err := regexp.Compile(`^\s*([-_*A-Za-z0-9]+):([0-9]+)\.?([0-9]+)?\s*$`) 112 parsed := re.FindStringSubmatch(s) 113 114 if nil != err { 115 return nil, errors.New(fmt.Sprintf("invalid amount: %s", s)) 116 } 117 tail := "0.0" 118 if len(parsed) >= 4 { 119 tail = "0." + parsed[3] 120 } 121 if len(tail) > FractionalLength+1 { 122 return nil, errors.New("fraction too long") 123 } 124 value, err := strconv.ParseUint(parsed[2], 10, 64) 125 if nil != err { 126 return nil, errors.New(fmt.Sprintf("Unable to parse value %s", parsed[2])) 127 } 128 fractionF, err := strconv.ParseFloat(tail, 64) 129 if nil != err { 130 return nil, errors.New(fmt.Sprintf("Unable to parse fraction %s", tail)) 131 } 132 fraction := uint64(math.Round(fractionF * FractionalBase)) 133 currency := parsed[1] 134 a := NewAmount(currency, value, fraction) 135 return &a, nil 136 } 137 138 // Check if this amount is zero 139 func (a *Amount) IsZero() bool { 140 return (a.Value == 0) && (a.Fraction == 0) 141 } 142 143 // Returns the string representation of the amount: <currency>:<value>[.<fraction>] 144 // Omits trailing zeroes. 145 func (a *Amount) String() string { 146 v := strconv.FormatUint(a.Value, 10) 147 if a.Fraction != 0 { 148 f := strconv.FormatUint(a.Fraction, 10) 149 f = strings.TrimRight(f, "0") 150 v = fmt.Sprintf("%s.%s", v, f) 151 } 152 return fmt.Sprintf("%s:%s", a.Currency, v) 153 }