gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]