diff options
author | Jonathan Buchanan <jonathan.russ.buchanan@gmail.com> | 2022-06-15 14:09:48 -0400 |
---|---|---|
committer | Jonathan Buchanan <jonathan.russ.buchanan@gmail.com> | 2022-06-15 14:09:48 -0400 |
commit | 00e03ddb0cc3ae1192f312c0f547097e9eeb5d17 (patch) | |
tree | 7d66bb2a786b79f915941c7173307a618c4694c7 /Taler/Amount.swift | |
parent | 416a1650f961fc496d825e547eefb499f8781fbd (diff) | |
download | taler-ios-00e03ddb0cc3ae1192f312c0f547097e9eeb5d17.tar.gz taler-ios-00e03ddb0cc3ae1192f312c0f547097e9eeb5d17.tar.bz2 taler-ios-00e03ddb0cc3ae1192f312c0f547097e9eeb5d17.zip |
separate common taler code into a local swift package
Diffstat (limited to 'Taler/Amount.swift')
-rw-r--r-- | Taler/Amount.swift | 410 |
1 files changed, 0 insertions, 410 deletions
diff --git a/Taler/Amount.swift b/Taler/Amount.swift deleted file mode 100644 index 98bb1b9..0000000 --- a/Taler/Amount.swift +++ /dev/null @@ -1,410 +0,0 @@ -/* - * This file is part of GNU Taler - * (C) 2021 Taler Systems S.A. - * - * GNU Taler is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3, or (at your option) any later version. - * - * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import Foundation - -/** - Errors for `Amount`. - */ -enum AmountError: Error { - /** - The string cannot be parsed to create an `Amount`. - */ - case invalidStringRepresentation - - /** - Could not compare or operate on two `Amount`s of different currencies. - */ - case incompatibleCurrency - - /** - The amount is invalid. The value is either greater than the maximum, or the currency is the empty string. - */ - case invalidAmount - - /** - The result of the operation would yield a negative amount. - */ - case negativeAmount - - /** - The operation was division by zero. - */ - case divideByZero -} - -/** - A value of a currency. - */ -class Amount: Codable, CustomStringConvertible { - /** - The largest possible value that can be represented. - */ - private static let maxValue: UInt64 = 1 << 52 - - /** - The size of `value` in relation to `fraction`. - */ - private static let fractionalBase: UInt32 = 100000000 - - /** - The greatest number of decimal digits that can be represented. - */ - private static let fractionalBaseDigits: UInt = 8 - - /** - The currency of the amount. - */ - var currency: String - - /** - The value of the amount (number to the left of the decimal point). - */ - var value: UInt64 - - /** - The fractional value of the amount (number to the right of the decimal point). - */ - var fraction: UInt32 - - /** - The string representation of the amount, formatted as "`currency`:`value`.`fraction`". - */ - var description: String { - if fraction == 0 { - return "\(currency):\(value)" - } else { - var frac = fraction - var fracStr = "" - while (frac > 0) { - fracStr += "\(frac / (Amount.fractionalBase / 10))" - frac = (frac * 10) % Amount.fractionalBase - } - return "\(currency):\(value).\(fracStr)" - } - } - - /** - Whether the value is valid. An amount is valid if and only if the currency is not empty and the value is less than the maximum allowed value. - */ - var valid: Bool { - return (value <= Amount.maxValue && currency != "") - } - - /** - Initializes an amount by parsing a string representing the amount. The string should be formatted as "`currency`:`value`.`fraction`". - - - Parameters: - - fromString: The string to parse. - - - Throws: - - `AmountError.invalidStringRepresentation` if the string cannot be parsed. - - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid. - */ - init(fromString string: String) throws { - guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation } - self.currency = String(string[..<separatorIndex]) - let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...]) - if let dotIndex = amountStr.firstIndex(of: ".") { - let valueStr = String(amountStr[..<dotIndex]) - let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...]) - guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation } - self.value = _value - self.fraction = 0 - var digitValue = Amount.fractionalBase / 10 - for char in fractionStr { - guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation } - self.fraction += digitValue * UInt32(digit) - digitValue /= 10 - } - } else { - guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation } - self.value = _value - self.fraction = 0 - } - guard self.valid else { throw AmountError.invalidAmount } - } - - /** - Initializes an amount with the specified currency, value, and fraction. - - - Parameters: - - currency: The currency of the amount. - - value: The value of the amount (number to the left of the decimal point). - - fraction: The fractional value of the amount (number to the right of the decimal point). - */ - init(currency: String, value: UInt64, fraction: UInt32) { - self.currency = currency - self.value = value - self.fraction = fraction - } - - /** - Initializes an amount from a decoder. - - - Parameters: - - fromDecoder: The decoder to extract the amount from. - - - Throws: - - `AmountError.invalidStringRepresentation` if the string cannot be parsed. - - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid. - */ - init(fromDecoder decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let string = try container.decode(String.self) - /* TODO: de-duplicate */ - guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation } - self.currency = String(string[..<separatorIndex]) - let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...]) - if let dotIndex = amountStr.firstIndex(of: ".") { - let valueStr = String(amountStr[..<dotIndex]) - let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...]) - guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation } - self.value = _value - self.fraction = 0 - var digitValue = Amount.fractionalBase / 10 - for char in fractionStr { - guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation } - self.fraction += digitValue * UInt32(digit) - digitValue /= 10 - } - } else { - guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation } - self.value = _value - self.fraction = 0 - } - guard self.valid else { throw AmountError.invalidAmount } - } - - /** - Copies an amount. - - - Returns: A copy of the amount. - */ - func copy() -> Amount { - return Amount(currency: self.currency, value: self.value, fraction: self.fraction) - } - - /** - Creates a normalized copy of an amount. - - - Returns: A copy of the amount that has been normalized - */ - func normalizedCopy() throws -> Amount { - let amount = self.copy() - try amount.normalize() - return amount - } - - /** - Encodes an amount. - - - Parameters: - - to: The encoder to encode the amount with. - */ - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self.description) - } - - /** - Normalizes an amount by reducing `fraction` until it is less than `Amount.fractionalBase`, increasing `value` appropriately. - - - Throws: - - `AmountError.invalidAmount` if the amount is invalid either before or after normalization. - */ - func normalize() throws { - if !valid { - throw AmountError.invalidAmount - } - self.value += UInt64(self.fraction / Amount.fractionalBase) - self.fraction = self.fraction % Amount.fractionalBase - if !valid { - throw AmountError.invalidAmount - } - } - - /** - Adds two amounts together. - - - Parameters: - - left: The amount on the left. - - right: The amount on the right. - - - Throws: - - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency. - - - Returns: The sum of `left` and `right`, normalized. - */ - static func + (left: Amount, right: Amount) throws -> Amount { - if left.currency != right.currency { - throw AmountError.incompatibleCurrency - } - let leftNormalized = try left.normalizedCopy() - let rightNormalized = try right.normalizedCopy() - let result: Amount = leftNormalized - result.value += rightNormalized.value - result.fraction += rightNormalized.fraction - try result.normalize() - return result - } - - /** - Subtracts one amount from another. - - - Parameters: - - left: The amount on the left. - - right: The amount on the right. - - - Throws: - - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency. - - - Returns: The difference of `left` and `right`, normalized. - */ - static func - (left: Amount, right: Amount) throws -> Amount { - if left.currency != right.currency { - throw AmountError.incompatibleCurrency - } - let leftNormalized = try left.normalizedCopy() - let rightNormalized = try right.normalizedCopy() - if (leftNormalized.fraction < rightNormalized.fraction) { - guard leftNormalized.value != 0 else { throw AmountError.negativeAmount } - leftNormalized.value -= 1 - leftNormalized.fraction += Amount.fractionalBase - } - guard leftNormalized.value >= rightNormalized.value else { throw AmountError.negativeAmount } - let diff = Amount.zero(currency: left.currency) - diff.value = leftNormalized.value - rightNormalized.value - diff.fraction = leftNormalized.fraction - rightNormalized.fraction - try diff.normalize() - return diff - } - - /** - Divides an amount by a scalar, possibly introducing rounding error. - - - Parameters: - - dividend: The amount to divide. - - divisor: The scalar dividing `dividend`. - - - Returns: The quotient of `dividend` and `divisor`, normalized. - */ - static func / (dividend: Amount, divisor: UInt32) throws -> Amount { - guard divisor != 0 else { throw AmountError.divideByZero } - let result = try dividend.normalizedCopy() - if (divisor == 1) { - return result - } - var remainder = result.value % UInt64(divisor) - result.value = result.value / UInt64(divisor) - remainder = (remainder * UInt64(Amount.fractionalBase)) + UInt64(result.fraction) - result.fraction = UInt32(remainder) / divisor - try result.normalize() - return result - } - - /** - Multiply an amount by a scalar. - - - Parameters: - - amount: The amount to multiply. - - factor: The scalar multiplying `amount`. - - - Returns: The product of `amount` and `factor`, normalized. - */ - static func * (amount: Amount, factor: UInt32) throws -> Amount { - let result = try amount.normalizedCopy() - result.value = result.value * UInt64(factor) - let fraction_tmp = UInt64(result.fraction) * UInt64(factor) - result.value += fraction_tmp / UInt64(Amount.fractionalBase) - result.fraction = UInt32(fraction_tmp % UInt64(Amount.fractionalBase)) - return result - } - - /** - Compares two amounts. - - - Parameters: - - left: The first amount. - - right: The second amount. - - - Throws: - - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency. - - - Returns: `true` if and only if the amounts have the same `value` and `fraction` after normalization, `false` otherwise. - */ - static func == (left: Amount, right: Amount) throws -> Bool { - if left.currency != right.currency { - throw AmountError.incompatibleCurrency - } - let leftNormalized = try left.normalizedCopy() - let rightNormalized = try right.normalizedCopy() - return (leftNormalized.value == rightNormalized.value && leftNormalized.fraction == rightNormalized.fraction) - } - - /** - Compares two amounts. - - - Parameters: - - left: The amount on the left. - - right: The amount on the right. - - - Throws: - - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency. - - - Returns: `true` if and only if `left` is smaller than `right` after normalization, `false` otherwise. - */ - static func < (left: Amount, right: Amount) throws -> Bool { - if left.currency != right.currency { - throw AmountError.incompatibleCurrency - } - let leftNormalized = try left.normalizedCopy() - let rightNormalized = try right.normalizedCopy() - if (leftNormalized.value == rightNormalized.value) { - return (leftNormalized.fraction < rightNormalized.fraction) - } else { - return (leftNormalized.value < rightNormalized.value) - } - } - - /** - Compares two amounts. - - - Parameters: - - left: The amount on the left. - - right: The amount on the right. - - - Throws: - - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency. - - - Returns: `true` if and only if `left` is bigger than `right` after normalization, `false` otherwise. - */ - static func > (left: Amount, right: Amount) throws -> Bool { - return try right < left - } - - /** - Creates the amount representing zero in a given currency. - - - Parameters: - - currency: The currency to use. - - - Returns: The zero amount for `currency`. - */ - static func zero(currency: String) -> Amount { - return Amount(currency: currency, value: 0, fraction: 0) - } -} |