Files

53 lines
2.1 KiB
Swift

import Foundation
/// Exact decimal <-> drops conversion (1 XRP = 1_000_000 drops) without
/// floating point rounding errors.
enum XRPAmount {
static let dropsPerXRP: UInt64 = 1_000_000
static let maxDrops: UInt64 = 100_000_000_000 * dropsPerXRP // 1e11 XRP
static func drops(fromXRP value: String) throws -> UInt64 {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty, !text.hasPrefix("-"), !text.hasPrefix("+") else {
throw XRPError.rpc("Enter a valid amount")
}
let parts = text.split(separator: ".", omittingEmptySubsequences: false)
guard parts.count <= 2 else { throw XRPError.rpc("Enter a valid amount") }
let wholeText = parts[0].isEmpty ? "0" : String(parts[0])
guard let whole = UInt64(wholeText) else { throw XRPError.rpc("Enter a valid amount") }
var fraction: UInt64 = 0
if parts.count == 2 {
let fractionText = String(parts[1])
guard fractionText.count <= 6, fractionText.allSatisfy({ $0.isNumber }) else {
throw XRPError.rpc("XRP supports at most 6 decimal places")
}
let padded = fractionText.padding(toLength: 6, withPad: "0", startingAt: 0)
guard let parsed = UInt64(padded) else { throw XRPError.rpc("Enter a valid amount") }
fraction = parsed
}
let (wholeDrops, overflow) = whole.multipliedReportingOverflow(by: dropsPerXRP)
guard !overflow else { throw XRPError.rpc("Amount is too large") }
let (total, carry) = wholeDrops.addingReportingOverflow(fraction)
guard !carry, total <= maxDrops else { throw XRPError.rpc("Amount is too large") }
return total
}
static func xrp(fromDrops drops: String) -> String {
guard let value = UInt64(drops.trimmingCharacters(in: .whitespacesAndNewlines)) else {
return "0"
}
return xrp(fromDrops: value)
}
static func xrp(fromDrops value: UInt64) -> String {
let whole = value / dropsPerXRP
let fraction = value % dropsPerXRP
if fraction == 0 { return String(whole) }
var fractionText = String(format: "%06llu", fraction)
while fractionText.hasSuffix("0") { fractionText.removeLast() }
return "\(whole).\(fractionText)"
}
}