Money 是一个便于处理和显示货币及金额的包。
Decimal
类型来表示金额来实现的。 Decimal 不会遭受(可能)不精确的表示形式的影响,这使得浮点数不适合作为金额的载体。Tender
)泛型化其货币来实现的,从而在处理货币类型时启用编译器级别的支持。Money
协议实现的。货币由符合 Currency 协议的无大小写枚举表示。我们使用无大小写枚举,因为我们只想将货币用作类型。 我们不需要货币的实例,因为我们不需要它们,并且无大小写枚举无法实例化。货币具有 code
和 minorUnitScale
。 minorUnitScale
表示主(主要)货币单位的细分。例如:欧元和美元分为美分,因此它们的 minorUnitScale
为 2
。每种货币也有一个名称。我们从平台派生(本地化)名称,因为 Apple 平台提供此信息。
我们为协议中定义的所有属性提供默认实现,以避免代码重复。唯一预计偶尔会被覆盖的实现是 minorUnitScale
的实现。绝大多数货币的单位刻度为 2,但有些货币偏离了这一点。我们提供了一个已知货币的默认列表,我们预计很少需要更新该列表。我们提供的列表基于 Apple 平台已知的常见货币,因此不能保证与 ISO 4217 货币列表完全 100% 相同,但在撰写本文时,差异非常小,可以忽略不计。但是,您的需求可能会有所不同,因此您可以选择根据需要更改列表。
对于金额的表示,我们使用两种相关类型:Money
,一个表示金额的协议;以及 Tender
,一种符合 Money
的类型。 Tender
是泛型化的,它代表的货币,这提供了编译器辅助的类型安全(例如,禁止添加不同货币的 Tender
),而它对 Money
协议的符合性提供了必要的灵活性,可以处理各种货币的集合,以及在编译时货币未知的情况下运行时实例化金额。
将 Money
视为抽象类型,在运行时提供动态灵活性,并将 Tender
视为用于处理的具体金额。
虽然您不能添加或减去不同货币的 Tender
(如果不首先将它们转换为相同的货币,这将毫无意义),或者将一个 Tender
乘以另一个 Tender
,但您可以对 Tender
执行算术运算。例如,您可以将 Tender
乘以一个数字类型:€5.42 * 3.14。
货币已经提供,因此您无需创建货币。但是,如果出于任何原因,您发现需要创建自定义货币,则可以将新的货币类型创建为符合 Currency
协议的枚举。如果您的自定义货币需要的 minorUnitScale
不是标准的 2
,那么您需要在枚举的主体中提供自定义实现
public enum MyCustomCurrency: Currency { public static var minorUnitScale: Int { 4 } }
否则,您可以创建一个带有空主体的枚举
public enum MyCustomCurrency: Currency {}
通过创建 Tender
的实例来创建具体的金额,并提供它泛型化的货币。最好从 Decimal
、Int
或 String
创建 Tender
,而不是从浮点值创建,尽管如果您真的需要,也可以使用它们。
let amount: Decimal = 3.14 // (or, preferably: Decimal(string: "3.14"))
let someMoney1 = Tender<EUR>(amount)
let someMoney2 = Tender<USD> = 5
let someMoney3 = Tender<JPY> = 3000
let someMoney4 = Tender<SEK>("42")
let someMoney5 = Tender<USD>("5,501", locale: Locale(identifier: "nl_NL")
let someMoney6 = Tender<USD>("5.501", locale: Locale(identifier: "en_US")
let ratherNot = Tender<EUR>(3.02) // 3.02 cannot be represented accurately as a floating point number, hence the tender initiated from it will inherently be flawed.
您不能添加或减去不同货币的 Tender
。但是,您可以添加和减去相同货币的 Tender
let sum = someMoney5 + someMoney6
您还可以比较相同货币的 Tender
if someMoney5 < someMoney6 {
…do something…
}
查看 TenderTests 和 TenderAlgebraTests 文件,以获得对可能内容的良好概述。
当然,我们通常需要向用户显示我们的金额。为此,Money
类型提供了一个类型为 Displayable
的 displayable
,它有助于将金额转换为用户友好的字符串。当您想向用户显示金额时,向实例请求 displayable
,并向 Displayable
请求所需的字符串
someMoney5.displayable.formatted
someMoney5.displayable.formattedTruncatingDecimals()
someMoney5.displayable.formattedTruncatingDecimals(for: Locale(identifier: "en_GB")
someMoney5.displayable.formattedWithoutCurrencySymbol()
someMoney5.displayable.formattedWithoutCurrencySymbol(for: Locale(identifier: "pt_PT")
您还可以向 displayable 请求货币符号、小数点分隔符和货币名称。
如果您需要存储具有各种货币的金额的集合,则无法创建 Tender
的集合,因为 Tender
是泛型化的,它代表的货币。 在这种情况下,您应该创建一个 Money
的集合,因为 Money 虽然持有货币,但不是泛型化的。
var variousCurrencies = [any Money]()
variousCurrencies.append(Tender<EUR>(1))
variousCurrencies.append(Tender<USD>(0.5))
此外,如果您需要在运行时创建金额,而您在编译时不知道其货币,请使用 Money
类型来建模您的数据。 使用 MoneyFactory
从金额和货币代码获取具体的 Tender
(类型为 Money
)
let myRuntTimeAmount = Decimal(42)
let myRuntimeCurrency = SupportedCurrency.INR
try MoneyFactory.moneyFrom(amount: myRuntTimeAmount, currency: myRuntimeCurrency)