mirror of
https://github.com/luxwallet/ios.git
synced 2026-10-30 20:33:27 +00:00
215 lines
6.6 KiB
Swift
215 lines
6.6 KiB
Swift
import Foundation
|
|
|
|
enum ValueFormatter {
|
|
private static let rawFormatter: NumberFormatter = {
|
|
let formatter = NumberFormatter()
|
|
formatter.numberStyle = .decimal
|
|
formatter.roundingMode = .halfUp
|
|
formatter.minimumFractionDigits = 0
|
|
return formatter
|
|
}()
|
|
|
|
private static let currencyFormatter: NumberFormatter = {
|
|
let formatter = NumberFormatter()
|
|
formatter.numberStyle = .currency
|
|
formatter.maximumFractionDigits = 0
|
|
formatter.minimumFractionDigits = 0
|
|
return formatter
|
|
}()
|
|
|
|
private static func fractionZeroCount(value: Decimal, maxCount: Int) -> Int {
|
|
guard value > 0, value < 1 else {
|
|
return 0
|
|
}
|
|
|
|
for count in 0 ..< maxCount {
|
|
if value * pow(10, count + 1) >= 1 {
|
|
return count
|
|
}
|
|
}
|
|
|
|
return maxCount
|
|
}
|
|
|
|
private static func digitsAndValue(value: Decimal, basePow: Int) -> (Int, Decimal) {
|
|
let digits: Int
|
|
|
|
switch value {
|
|
case pow(10, basePow) ..< (2 * pow(10, basePow + 1)): digits = 2
|
|
case (2 * pow(10, basePow + 1)) ..< (2 * pow(10, basePow + 2)): digits = 1
|
|
default: digits = 0
|
|
}
|
|
|
|
return (digits, value / pow(10, basePow))
|
|
}
|
|
|
|
private static func transformedFull(value: Decimal, maxDigits: Int, minDigits: Int = 0) -> (value: Decimal, digits: Int) {
|
|
let value = abs(value)
|
|
let digits: Int
|
|
|
|
switch value {
|
|
case 0:
|
|
digits = 0
|
|
|
|
case 0 ..< 1:
|
|
let zeroCount = fractionZeroCount(value: value, maxCount: maxDigits - 1)
|
|
digits = min(maxDigits, zeroCount + 4)
|
|
|
|
case 1 ..< 1.01:
|
|
digits = 4
|
|
|
|
case 1.01 ..< 1.1:
|
|
digits = 3
|
|
|
|
case 1.1 ..< 20:
|
|
digits = 2
|
|
|
|
case 20 ..< 200:
|
|
digits = 1
|
|
|
|
default:
|
|
digits = 0
|
|
}
|
|
|
|
return (value: value, digits: max(digits, minDigits))
|
|
}
|
|
|
|
private static func transformedShort(value: Decimal, maxDigits: Int = Int.max) -> (value: Decimal, digits: Int, suffix: ((String) -> String)?, tooSmall: Bool) {
|
|
var value = abs(value)
|
|
var suffix: ((String) -> String)?
|
|
let digits: Int
|
|
var tooSmall = false
|
|
|
|
switch value {
|
|
case 0:
|
|
digits = 0
|
|
|
|
case 0 ..< 0.0000_0001:
|
|
digits = 8
|
|
value = 0.0000_0001
|
|
tooSmall = true
|
|
|
|
case 0.0000_0001 ..< 1:
|
|
let zeroCount = fractionZeroCount(value: value, maxCount: 8)
|
|
digits = min(maxDigits, zeroCount + 4, 8)
|
|
|
|
case 1 ..< 1.01:
|
|
digits = 4
|
|
|
|
case 1.01 ..< 1.1:
|
|
digits = 3
|
|
|
|
case 1.1 ..< 20:
|
|
digits = 2
|
|
|
|
case 20 ..< 200:
|
|
digits = 1
|
|
|
|
case 200 ..< 19999.5:
|
|
digits = 0
|
|
|
|
case 19999.5 ..< edge(6):
|
|
(digits, value) = digitsAndValue(value: value, basePow: 3)
|
|
suffix = { String(localized: "\($0)number.thousand") }
|
|
|
|
case edge(6) ..< edge(9):
|
|
(digits, value) = digitsAndValue(value: value, basePow: 6)
|
|
suffix = { String(localized: "\($0)number.million") }
|
|
|
|
case edge(9) ..< edge(12):
|
|
(digits, value) = digitsAndValue(value: value, basePow: 9)
|
|
suffix = { String(localized: "\($0)number.billion") }
|
|
|
|
case edge(12) ..< edge(15):
|
|
(digits, value) = digitsAndValue(value: value, basePow: 12)
|
|
suffix = { String(localized: "\($0)number.trillion") }
|
|
|
|
default:
|
|
(digits, value) = digitsAndValue(value: value, basePow: 15)
|
|
suffix = { String(localized: "\($0)number.quadrillion") }
|
|
}
|
|
|
|
return (value: value, digits: digits, suffix: suffix, tooSmall: tooSmall)
|
|
}
|
|
|
|
private static func edge(_ power: Int) -> Decimal {
|
|
pow(10, power) - (pow(10, power - 3) / 2)
|
|
}
|
|
|
|
private static func decorated(string: String, suffix: ((String) -> String)? = nil, symbol: String? = nil, signValue: Decimal? = nil, tooSmall: Bool = false) -> String {
|
|
var string = string
|
|
|
|
if let suffix {
|
|
string = suffix(string)
|
|
}
|
|
|
|
if let symbol {
|
|
string = "\(string) \(symbol)"
|
|
}
|
|
|
|
if let signValue {
|
|
var sign = ""
|
|
if !signValue.isZero {
|
|
sign = signValue.isSignMinus ? "-" : "+"
|
|
}
|
|
string = "\(sign)\(string)"
|
|
}
|
|
|
|
if tooSmall {
|
|
string = "< \(string)"
|
|
}
|
|
|
|
return string
|
|
}
|
|
|
|
private static func formattedCurrency(value: Decimal, digits: Int, code: String, symbol: String, suffix: ((String) -> String)? = nil) -> String? {
|
|
currencyFormatter.currencyCode = code
|
|
currencyFormatter.currencySymbol = symbol
|
|
currencyFormatter.internationalCurrencySymbol = symbol
|
|
|
|
guard let pattern = currencyFormatter.string(from: 1 as NSDecimalNumber) else {
|
|
return nil
|
|
}
|
|
|
|
rawFormatter.maximumFractionDigits = digits
|
|
|
|
guard let string = rawFormatter.string(from: value as NSDecimalNumber) else {
|
|
return nil
|
|
}
|
|
|
|
return pattern.replacingOccurrences(of: "1", with: decorated(string: string, suffix: suffix))
|
|
}
|
|
|
|
static func format(percentValue: Decimal, showSign: Bool = true) -> String? {
|
|
let (transformedValue, digits) = transformedFull(value: percentValue, maxDigits: 2)
|
|
|
|
rawFormatter.maximumFractionDigits = digits
|
|
|
|
guard let string = rawFormatter.string(from: transformedValue as NSDecimalNumber) else {
|
|
return nil
|
|
}
|
|
|
|
return decorated(string: string, signValue: showSign ? percentValue : nil) + "%"
|
|
}
|
|
|
|
static func format(currency: Currency, value: Decimal, showSign: Bool = false) -> String? {
|
|
let (transformedValue, digits) = transformedFull(value: value, maxDigits: 18)
|
|
|
|
guard let string = formattedCurrency(value: transformedValue, digits: digits, code: currency.code, symbol: currency.symbol) else {
|
|
return nil
|
|
}
|
|
|
|
return decorated(string: string, signValue: showSign ? value : nil)
|
|
}
|
|
|
|
static func formatShort(currency: Currency, value: Decimal, showSign: Bool = false) -> String? {
|
|
let (transformedValue, digits, suffix, tooSmall) = transformedShort(value: value)
|
|
|
|
guard let string = formattedCurrency(value: transformedValue, digits: digits, code: currency.code, symbol: currency.symbol, suffix: suffix) else {
|
|
return nil
|
|
}
|
|
|
|
return decorated(string: string, signValue: showSign ? value : nil, tooSmall: tooSmall)
|
|
}
|
|
}
|