[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-taler-ios] 01/09: enhanced Amount+Time
From: |
gnunet |
Subject: |
[taler-taler-ios] 01/09: enhanced Amount+Time |
Date: |
Wed, 01 Feb 2023 09:28:50 +0100 |
This is an automated email from the git hooks/post-receive script.
marc-stibane pushed a commit to branch master
in repository taler-ios.
commit b813f20db60daea2a1d526959f70614f64b1c468
Author: Marc Stibane <marc@taler.net>
AuthorDate: Wed Feb 1 00:10:44 2023 +0100
enhanced Amount+Time
---
taler-swift/Sources/taler-swift/Amount.swift | 161 +++++++++++++++++----------
taler-swift/Sources/taler-swift/Time.swift | 58 ++++++++--
2 files changed, 152 insertions(+), 67 deletions(-)
diff --git a/taler-swift/Sources/taler-swift/Amount.swift
b/taler-swift/Sources/taler-swift/Amount.swift
index 62e1d7b..d97612e 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -34,14 +34,14 @@ enum AmountError: Error {
}
/// A value of some currency.
-public class Amount: Codable, CustomStringConvertible {
+public class Amount: Codable, Hashable, CustomStringConvertible {
/// Format that a currency must match.
private static let currencyRegex = #"^[-_*A-Za-z0-9]{1,12}$"#
/// The largest possible value that can be represented.
private static let maxValue: UInt64 = 1 << 52
- /// The size of `value` in relation to `fraction`.
+ /// The size of `integer` in relation to `fraction`.
private static let fractionalBase: UInt32 = 100000000
/// The greatest number of decimal digits that can be represented.
@@ -50,31 +50,38 @@ public class Amount: Codable, CustomStringConvertible {
/// 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 integer value of the amount (number to the left of the decimal
point).
+ var integer: 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`".
- public var description: String {
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(currency)
+ if let normalized = try? normalizedCopy() {
+ hasher.combine(normalized.integer)
+ hasher.combine(normalized.fraction)
+ } else {
+ hasher.combine(integer)
+ hasher.combine(fraction)
+ }
+ }
+
+ /// The floating point representation of the value
+ public var value: Double {
+ let value = Double(integer)
if fraction == 0 {
- return "\(currency):\(value)"
+ return value
} else {
- var frac = fraction
- var fracStr = ""
- while (frac > 0) {
- fracStr += "\(frac / (Amount.fractionalBase / 10))"
- frac = (frac * 10) % Amount.fractionalBase
- }
- return "\(currency):\(value).\(fracStr)"
+ let thousandths = Double(fraction / (Amount.fractionalBase / 1000))
+ return value + (thousandths / 1000.0)
}
}
-
- /// The string representation of the amount, formatted as
"`value`.`fraction` `currency`".
- public var readableDescription: String {
+
+ /// The string representation of the value, formatted as
"`integer`.`fraction`".
+ public var valueStr: String {
if fraction == 0 {
- return "\(value) \(currency)"
+ return "\(integer)"
} else {
var frac = fraction
var fracStr = ""
@@ -82,24 +89,39 @@ public class Amount: Codable, CustomStringConvertible {
fracStr += "\(frac / (Amount.fractionalBase / 10))"
frac = (frac * 10) % Amount.fractionalBase
}
- return "\(value).\(fracStr) \(currency)"
+ return "\(integer).\(fracStr)"
}
}
+
+ /// read-only getter
+ public var currencyStr: String {
+ return currency
+ }
+
+ /// The string representation of the amount, formatted as
"`currency`:`integer`.`fraction`".
+ public var description: String {
+ return "\(currency):\(valueStr)"
+ }
+
+ /// The string representation of the amount, formatted as
"`integer`.`fraction` `currency`".
+ public var readableDescription: String {
+ return "\(valueStr) \(currency)"
+ }
/// 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 {
if currency.range(of: Amount.currencyRegex, options:
.regularExpression) == nil {
return false
}
- return (value <= Amount.maxValue && currency != "")
+ return (integer <= Amount.maxValue && currency != "")
}
/// Whether this amount is zero or not.
- var isZero: Bool {
- return value == 0 && fraction == 0
+ public var isZero: Bool {
+ return integer == 0 && fraction == 0
}
- /// Initializes an amount by parsing a string representing the amount. The
string should be formatted as "`currency`:`value`.`fraction`".
+ /// Initializes an amount by parsing a string representing the amount. The
string should be formatted as "`currency`:`integer`.`fraction`".
/// - Parameters:
/// - fromString: The string to parse.
/// - Throws:
@@ -110,13 +132,13 @@ public class Amount: Codable, CustomStringConvertible {
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 integerStr = String(amountStr[..<dotIndex])
let fractionStr = String(amountStr[string.index(dotIndex,
offsetBy: 1)...])
if (fractionStr.count > Amount.fractionalBaseDigits) {
throw AmountError.invalidStringRepresentation
}
- guard let _value = UInt64(valueStr) else { throw
AmountError.invalidStringRepresentation }
- self.value = _value
+ guard let intValue = UInt64(integerStr) else { throw
AmountError.invalidStringRepresentation }
+ self.integer = intValue
self.fraction = 0
var digitValue = Amount.fractionalBase / 10
for char in fractionStr {
@@ -125,26 +147,26 @@ public class Amount: Codable, CustomStringConvertible {
digitValue /= 10
}
} else {
- guard let _value = UInt64(amountStr) else { throw
AmountError.invalidStringRepresentation }
- self.value = _value
+ guard let intValue = UInt64(amountStr) else { throw
AmountError.invalidStringRepresentation }
+ self.integer = intValue
self.fraction = 0
}
} else {
self.currency = string
- self.value = 0
+ self.integer = 0
self.fraction = 0
}
guard self.valid else { throw AmountError.invalidAmount }
}
- /// Initializes an amount with the specified currency, value, and fraction.
+ /// Initializes an amount with the specified currency, integer, and
fraction.
/// - Parameters:
/// - currency: The currency of the amount.
- /// - value: The value of the amount (number to the left of the decimal
point).
+ /// - integer: The integer 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).
- public init(currency: String, value: UInt64, fraction: UInt32) {
+ public init(currency: String, integer: UInt64, fraction: UInt32) {
self.currency = currency
- self.value = value
+ self.integer = integer
self.fraction = fraction
}
@@ -163,7 +185,7 @@ public class Amount: Codable, CustomStringConvertible {
/// Copies an amount.
/// - Returns: A copy of the amount.
func copy() -> Amount {
- return Amount(currency: self.currency, value: self.value, fraction:
self.fraction)
+ return Amount(currency: currency, integer: integer, fraction: fraction)
}
/// Creates a normalized copy of an amount (the fractional part is
strictly less than one unit of currency).
@@ -179,18 +201,18 @@ public class Amount: Codable, CustomStringConvertible {
/// - to: The encoder to encode the amount with.
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
- try container.encode(self.description)
+ try container.encode(description)
}
- /// Normalizes an amount by reducing `fraction` until it is less than
`Amount.fractionalBase`, increasing `value` appropriately.
+ /// Normalizes an amount by reducing `fraction` until it is less than
`Amount.fractionalBase`, increasing `integer` 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
+ integer += UInt64(fraction / Amount.fractionalBase)
+ fraction = fraction % Amount.fractionalBase
if !valid {
throw AmountError.invalidAmount
}
@@ -210,7 +232,7 @@ public class Amount: Codable, CustomStringConvertible {
let leftNormalized = try left.normalizedCopy()
let rightNormalized = try right.normalizedCopy()
let result: Amount = leftNormalized
- result.value += rightNormalized.value
+ result.integer += rightNormalized.integer
result.fraction += rightNormalized.fraction
try result.normalize()
return result
@@ -230,18 +252,23 @@ public class Amount: Codable, CustomStringConvertible {
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
+ guard leftNormalized.integer != 0 else { throw
AmountError.negativeAmount }
+ leftNormalized.integer -= 1
leftNormalized.fraction += Amount.fractionalBase
}
- guard leftNormalized.value >= rightNormalized.value else { throw
AmountError.negativeAmount }
+ guard leftNormalized.integer >= rightNormalized.integer else { throw
AmountError.negativeAmount }
let diff = Amount.zero(currency: left.currency)
- diff.value = leftNormalized.value - rightNormalized.value
+ diff.integer = leftNormalized.integer - rightNormalized.integer
diff.fraction = leftNormalized.fraction - rightNormalized.fraction
try diff.normalize()
return diff
}
-
+
+ public static func diff (_ left: Amount, _ right: Amount) throws -> Amount
{
+ return try left > right ? left - right
+ : right - left
+ }
+
/// Divides an amount by a scalar, possibly introducing rounding error.
/// - Parameters:
/// - dividend: The amount to divide.
@@ -253,8 +280,8 @@ public class Amount: Codable, CustomStringConvertible {
if (divisor == 1) {
return result
}
- var remainder = result.value % UInt64(divisor)
- result.value = result.value / UInt64(divisor)
+ var remainder = result.integer % UInt64(divisor)
+ result.integer = result.integer / UInt64(divisor)
remainder = (remainder * UInt64(Amount.fractionalBase)) +
UInt64(result.fraction)
result.fraction = UInt32(remainder) / divisor
try result.normalize()
@@ -268,9 +295,9 @@ public class Amount: Codable, CustomStringConvertible {
/// - Returns: The product of `amount` and `factor`, normalized.
public static func * (amount: Amount, factor: UInt32) throws -> Amount {
let result = try amount.normalizedCopy()
- result.value = result.value * UInt64(factor)
+ result.integer = result.integer * UInt64(factor)
let fraction_tmp = UInt64(result.fraction) * UInt64(factor)
- result.value += fraction_tmp / UInt64(Amount.fractionalBase)
+ result.integer += fraction_tmp / UInt64(Amount.fractionalBase)
result.fraction = UInt32(fraction_tmp % UInt64(Amount.fractionalBase))
return result
}
@@ -281,16 +308,30 @@ public class Amount: Codable, CustomStringConvertible {
/// - 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.
- public static func == (left: Amount, right: Amount) throws -> Bool {
- if left.currency != right.currency {
- throw AmountError.incompatibleCurrency
+ /// - Returns: `true` if and only if the amounts have the same `integer`
and `fraction` after normalization, `false` otherwise.
+// public 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.integer == rightNormalized.integer &&
leftNormalized.fraction == rightNormalized.fraction)
+// }
+
+ public static func == (left: Amount, right: Amount) -> Bool {
+ do {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ return (leftNormalized.integer == rightNormalized.integer &&
leftNormalized.fraction == rightNormalized.fraction)
+ }
+ catch {
+ return false
}
- 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.
@@ -304,10 +345,10 @@ public class Amount: Codable, CustomStringConvertible {
}
let leftNormalized = try left.normalizedCopy()
let rightNormalized = try right.normalizedCopy()
- if (leftNormalized.value == rightNormalized.value) {
+ if (leftNormalized.integer == rightNormalized.integer) {
return (leftNormalized.fraction < rightNormalized.fraction)
} else {
- return (leftNormalized.value < rightNormalized.value)
+ return (leftNormalized.integer < rightNormalized.integer)
}
}
@@ -349,6 +390,6 @@ public class Amount: Codable, CustomStringConvertible {
/// - currency: The currency to use.
/// - Returns: The zero amount for `currency`.
public static func zero(currency: String) -> Amount {
- return Amount(currency: currency, value: 0, fraction: 0)
+ return Amount(currency: currency, integer: 0, fraction: 0)
}
}
diff --git a/taler-swift/Sources/taler-swift/Time.swift
b/taler-swift/Sources/taler-swift/Time.swift
index b1d712e..1d76053 100644
--- a/taler-swift/Sources/taler-swift/Time.swift
+++ b/taler-swift/Sources/taler-swift/Time.swift
@@ -22,21 +22,29 @@ enum TimestampError: Error {
/// Invalid arguments were supplied to an arithmetic operation.
case invalidArithmeticArguments
+
+ /// The value `never` cannot be returned as UInt64.
+ case invalidUInt64Value
}
-/// A point in time, represented by milliseconds from Jaunuary 1, 1970..
-public enum Timestamp: Codable, Equatable {
+/// A point in time, represented by milliseconds from January 1, 1970..
+public enum Timestamp: Codable, Hashable {
case milliseconds(UInt64)
case never
enum CodingKeys: String, CodingKey {
case t_s = "t_s"
+ case t_ms = "t_ms"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
- self = Timestamp.milliseconds(try container.decode(UInt64.self,
forKey: .t_s) * 1000)
+ if let seconds: UInt64 = try? container.decode(UInt64.self,
forKey: .t_s) {
+ self = Timestamp.milliseconds(seconds * 1000)
+ } else {
+ self = Timestamp.milliseconds(try
container.decode(UInt64.self, forKey: .t_ms))
+ }
} catch {
let stringValue = try container.decode(String.self, forKey: .t_s)
if stringValue == "never" {
@@ -46,11 +54,14 @@ public enum Timestamp: Codable, Equatable {
}
}
}
-
- static func now() -> Timestamp {
- return Timestamp.milliseconds(UInt64(Date().timeIntervalSince1970 *
1000.0))
+ public func hash(into hasher: inout Hasher) {
+ switch self {
+ case .milliseconds(let t_ms):
+ hasher.combine(t_ms)
+ case .never:
+ hasher.combine("never")
+ }
}
-
public func encode(to encoder: Encoder) throws {
var value = encoder.container(keyedBy: CodingKeys.self)
switch self {
@@ -62,6 +73,39 @@ public enum Timestamp: Codable, Equatable {
}
}
+extension Timestamp {
+ /// make a Timestamp for `now`
+ public static func now() -> Timestamp {
+ return Timestamp.milliseconds(Date().millisecondsSince1970)
+ }
+
+ /// convenience initializer from UInt64 (milliseconds from January 1, 1970)
+ public init(from: UInt64) {
+ self = Timestamp.milliseconds(from)
+ }
+
+ /// switch-case the enum here so the caller function doesn't have to
+ public func milliseconds() throws -> UInt64 {
+ switch self {
+ case .milliseconds(let t_ms):
+ return t_ms
+ case .never:
+ throw TimestampError.invalidUInt64Value
+ }
+ }
+}
+
+/// extend iOS Date to work with milliseconds since 1970
+extension Date {
+ public var millisecondsSince1970: UInt64 {
+ UInt64((timeIntervalSince1970 * 1000.0).rounded())
+ }
+
+ public init(milliseconds: UInt64) {
+ self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
+ }
+}
+
/// A duration of time, measured in milliseconds.
public enum Duration: Equatable {
case milliseconds(UInt64)
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-taler-ios] branch master updated (78ef82c -> df4fe35), gnunet, 2023/02/01
- [taler-taler-ios] 02/09: removed iono, gnunet, 2023/02/01
- [taler-taler-ios] 03/09: most of Info.plist moved into the project, gnunet, 2023/02/01
- [taler-taler-ios] 04/09: Assets, gnunet, 2023/02/01
- [taler-taler-ios] 01/09: enhanced Amount+Time,
gnunet <=
- [taler-taler-ios] 05/09: Testing, gnunet, 2023/02/01
- [taler-taler-ios] 07/09: Project uses QuickJS instead of iono, gnunet, 2023/02/01
- [taler-taler-ios] 06/09: App sources, gnunet, 2023/02/01
- [taler-taler-ios] 08/09: more to ignore..., gnunet, 2023/02/01
- [taler-taler-ios] 09/09: Build instructions, gnunet, 2023/02/01