[272] Decimals and Zatoshi type (#330)
[272] Decimals and Zatoshi type (330) - Zatoshi type implemented - conversions implemented - 'from' convert methods refactored to better readable syntax - fixed issue with rounding Decimal (in Zatoshi -> Decimal conversion) - rounded -> roundedZec - comments resolved - zcashNumberFormatter by default
This commit is contained in:
parent
9bed9d5927
commit
bab1dc6f82
|
@ -101,6 +101,9 @@
|
|||
9E2F1C8F280EDE09004E65FE /* Drawer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8E280EDE09004E65FE /* Drawer.swift */; };
|
||||
9E37A2B827C8F59F00AE57B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */; };
|
||||
9E391124283E4CAC0073DD9A /* ImportWalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E391123283E4CAC0073DD9A /* ImportWalletTests.swift */; };
|
||||
9E391129283F74590073DD9A /* Zatoshi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E391128283F74590073DD9A /* Zatoshi.swift */; };
|
||||
9E39112A283F90F10073DD9A /* Double+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */; };
|
||||
9E39112E283F91600073DD9A /* ZatoshiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E39112D283F91600073DD9A /* ZatoshiTests.swift */; };
|
||||
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */; };
|
||||
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; };
|
||||
9E5BF63F2819542C00BA3F17 /* TransactionHistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF63E2819542C00BA3F17 /* TransactionHistoryTests.swift */; };
|
||||
|
@ -138,7 +141,6 @@
|
|||
9EAFEB9228081E9400199FC9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874EF273C4DE200F0E875 /* HomeView.swift */; };
|
||||
9EBEF87A27CE369800B4F343 /* RecoveryPhraseValidationFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseValidationFlowView.swift */; };
|
||||
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */; };
|
||||
9EDDEA8C28250F9C00B4100C /* Double+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */; };
|
||||
9EDDEAA22829610D00B4100C /* CurrencySelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEA9F2829610D00B4100C /* CurrencySelectionTests.swift */; };
|
||||
9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEAA02829610D00B4100C /* TransactionAmountInputTests.swift */; };
|
||||
9EDDEAA42829610D00B4100C /* TransactionAddressInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEAA12829610D00B4100C /* TransactionAddressInputTests.swift */; };
|
||||
|
@ -291,6 +293,8 @@
|
|||
9E2F1C8E280EDE09004E65FE /* Drawer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Drawer.swift; sourceTree = "<group>"; };
|
||||
9E37A2B727C8F59F00AE57B3 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
9E391123283E4CAC0073DD9A /* ImportWalletTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportWalletTests.swift; sourceTree = "<group>"; };
|
||||
9E391128283F74590073DD9A /* Zatoshi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zatoshi.swift; sourceTree = "<group>"; };
|
||||
9E39112D283F91600073DD9A /* ZatoshiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZatoshiTests.swift; sourceTree = "<group>"; };
|
||||
9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicDesignModifier.swift; sourceTree = "<group>"; };
|
||||
9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantButtonStyles.swift; sourceTree = "<group>"; };
|
||||
9E5BF63B2818305D00BA3F17 /* TransactionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionState.swift; sourceTree = "<group>"; };
|
||||
|
@ -834,6 +838,7 @@
|
|||
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */,
|
||||
2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */,
|
||||
9E2F1C832809B606004E65FE /* DebugMenu.swift */,
|
||||
9E391128283F74590073DD9A /* Zatoshi.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
@ -956,6 +961,7 @@
|
|||
9EAFEB852805A23100199FC9 /* WrappedSecItemTests.swift */,
|
||||
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */,
|
||||
9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */,
|
||||
9E39112D283F91600073DD9A /* ZatoshiTests.swift */,
|
||||
);
|
||||
path = UtilTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1302,7 +1308,9 @@
|
|||
9EBEF87A27CE369800B4F343 /* RecoveryPhraseValidationFlowView.swift in Sources */,
|
||||
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */,
|
||||
0DDB6A5127737D4A0012A410 /* RecoveryPhraseBackupFailedView.swift in Sources */,
|
||||
9E391129283F74590073DD9A /* Zatoshi.swift in Sources */,
|
||||
0D6D628B276A528E002FB4CC /* DropDelegate.swift in Sources */,
|
||||
9E39112A283F90F10073DD9A /* Double+Zcash.swift in Sources */,
|
||||
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */,
|
||||
F9971A5327680DD000A2DB75 /* ProfileStore.swift in Sources */,
|
||||
669FDAEB272C23C2007B9422 /* CircularFrameBadge.swift in Sources */,
|
||||
|
@ -1354,7 +1362,6 @@
|
|||
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
|
||||
9EAFEB84280597B700199FC9 /* WrappedSecItem.swift in Sources */,
|
||||
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */,
|
||||
9EDDEA8C28250F9C00B4100C /* Double+Zcash.swift in Sources */,
|
||||
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */,
|
||||
9E5BF6462821028C00BA3F17 /* WrappedUserDefaults.swift in Sources */,
|
||||
F9971A6B27680E1000A2DB75 /* WalletInfoStore.swift in Sources */,
|
||||
|
@ -1403,6 +1410,7 @@
|
|||
9E01F8282833CDA0000EFC57 /* ScanTests.swift in Sources */,
|
||||
9EDDEAA42829610D00B4100C /* TransactionAddressInputTests.swift in Sources */,
|
||||
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
|
||||
9E39112E283F91600073DD9A /* ZatoshiTests.swift in Sources */,
|
||||
9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */,
|
||||
9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */,
|
||||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
|
||||
|
|
|
@ -19,6 +19,8 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
|||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||
) -> Bool {
|
||||
// set the default behavior for the NSDecimalNumber
|
||||
NSDecimalNumber.defaultBehavior = Zatoshi.decimalHandler
|
||||
appViewStore.send(.appDelegate(.didFinishLaunching))
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -14,6 +14,6 @@ extension Double {
|
|||
}
|
||||
|
||||
func asZecString() -> String {
|
||||
NumberFormatter.zcashFormatter.string(from: NSNumber(value: self)) ?? ""
|
||||
NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: self)) ?? ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,6 @@ extension Int64 {
|
|||
}
|
||||
|
||||
func asZecString() -> String {
|
||||
NumberFormatter.zcashFormatter.string(from: NSNumber(value: self.asHumanReadableZecBalance())) ?? ""
|
||||
NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: self.asHumanReadableZecBalance())) ?? ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ extension String {
|
|||
}
|
||||
|
||||
extension NumberFormatter {
|
||||
static let zcashFormatter: NumberFormatter = {
|
||||
static let zcashNumberFormatter: NumberFormatter = {
|
||||
var formatter = NumberFormatter()
|
||||
formatter.maximumFractionDigits = 8
|
||||
formatter.maximumIntegerDigits = 8
|
||||
|
@ -22,7 +22,7 @@ extension NumberFormatter {
|
|||
|
||||
extension String {
|
||||
var doubleValue: Double? {
|
||||
return NumberFormatter.zcashFormatter.number(from: self)?.doubleValue
|
||||
return NumberFormatter.zcashNumberFormatter.number(from: self)?.doubleValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// Zatoshi.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 26.05.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Zatoshi {
|
||||
enum Constants {
|
||||
static let oneZecInZatoshi: Int64 = 100_000_000
|
||||
static let maxZecSupply: Int64 = 21_000_000
|
||||
static let maxZatoshi: Int64 = Constants.oneZecInZatoshi * Constants.maxZecSupply
|
||||
}
|
||||
|
||||
static var decimalHandler = NSDecimalNumberHandler(
|
||||
roundingMode: NSDecimalNumber.RoundingMode.bankers,
|
||||
scale: 8,
|
||||
raiseOnExactness: true,
|
||||
raiseOnOverflow: true,
|
||||
raiseOnUnderflow: true,
|
||||
raiseOnDivideByZero: true
|
||||
)
|
||||
|
||||
@Clamped(-Constants.maxZatoshi...Constants.maxZatoshi)
|
||||
var amount: Int64 = 0
|
||||
|
||||
/// Converts `Zatoshi` to `NSDecimalNumber`
|
||||
var decimalValue: NSDecimalNumber {
|
||||
NSDecimalNumber(decimal: Decimal(amount) / Decimal(Constants.oneZecInZatoshi))
|
||||
}
|
||||
|
||||
/// Converts `Zatoshi` to human readable format, up to 8 fraction digits
|
||||
var decimalString: String {
|
||||
decimalValue.roundedZec.stringValue
|
||||
}
|
||||
|
||||
/// Converts `Decimal` to `Zatoshi`
|
||||
static func from(decimal: Decimal) -> Zatoshi {
|
||||
let roundedZec = NSDecimalNumber(decimal: decimal).roundedZec
|
||||
let zec2zatoshi = Decimal(Constants.oneZecInZatoshi) * roundedZec.decimalValue
|
||||
return Zatoshi(amount: NSDecimalNumber(decimal: zec2zatoshi).int64Value)
|
||||
}
|
||||
|
||||
/// Converts `String` to `Zatoshi`
|
||||
static func from(decimalString: String, formatter: NumberFormatter = NumberFormatter.zcashNumberFormatter) -> Zatoshi? {
|
||||
if let number = formatter.number(from: decimalString) {
|
||||
return Zatoshi.from(decimal: number.decimalValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
static func + (left: Zatoshi, right: Zatoshi) -> Zatoshi {
|
||||
Zatoshi(amount: left.amount + right.amount)
|
||||
}
|
||||
|
||||
static func - (left: Zatoshi, right: Zatoshi) -> Zatoshi {
|
||||
Zatoshi(amount: left.amount - right.amount)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSDecimalNumber {
|
||||
/// Converts `NSDecimalNumber` to human readable format, up to 8 fraction digits
|
||||
var decimalString: String {
|
||||
self.rounding(accordingToBehavior: Zatoshi.decimalHandler).stringValue
|
||||
}
|
||||
|
||||
/// Round the decimal to 8 fraction digits
|
||||
var roundedZec: NSDecimalNumber {
|
||||
self.rounding(accordingToBehavior: Zatoshi.decimalHandler)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
//
|
||||
// ZatoshiTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 26.05.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
|
||||
class ZatoshiTests: XCTestCase {
|
||||
func testLowerBound() throws {
|
||||
let number = Zatoshi(amount: -Zatoshi.Constants.maxZatoshi - 1)
|
||||
|
||||
XCTAssertEqual(
|
||||
-Zatoshi.Constants.maxZatoshi,
|
||||
number.amount,
|
||||
"Zatoshi tests: `testLowerBound` the value is expected to be clamped to lower bound but it's \(number.amount)"
|
||||
)
|
||||
}
|
||||
|
||||
func testUpperBound() throws {
|
||||
let number = Zatoshi(amount: Zatoshi.Constants.maxZatoshi + 1)
|
||||
|
||||
XCTAssertEqual(
|
||||
Zatoshi.Constants.maxZatoshi,
|
||||
number.amount,
|
||||
"Zatoshi tests: `testUpperBound` the value is expected to be clamped to upper bound but it's \(number.amount)"
|
||||
)
|
||||
}
|
||||
|
||||
func testAddingZatoshi() throws {
|
||||
let numberA1 = Zatoshi(amount: 100_000)
|
||||
let numberB1 = Zatoshi(amount: 200_000)
|
||||
let result1 = numberA1 + numberB1
|
||||
|
||||
XCTAssertEqual(
|
||||
result1.amount,
|
||||
Zatoshi(amount: 300_000).amount,
|
||||
"Zatoshi tests: `testAddingZatoshi` the value is expected to be 300_000 but it's \(result1.amount)"
|
||||
)
|
||||
|
||||
let numberA2 = Zatoshi(amount: -100_000)
|
||||
let numberB2 = Zatoshi(amount: 200_000)
|
||||
let result2 = numberA2 + numberB2
|
||||
|
||||
XCTAssertEqual(
|
||||
result2.amount,
|
||||
Zatoshi(amount: 100_000).amount,
|
||||
"Zatoshi tests: `testAddingZatoshi` the value is expected to be 100_000 but it's \(result2.amount)"
|
||||
)
|
||||
|
||||
let numberA3 = Zatoshi(amount: 100_000)
|
||||
let numberB3 = Zatoshi(amount: -200_000)
|
||||
let result3 = numberA3 + numberB3
|
||||
|
||||
XCTAssertEqual(
|
||||
result3.amount,
|
||||
Zatoshi(amount: -100_000).amount,
|
||||
"Zatoshi tests: `testAddingZatoshi` the value is expected to be -100_000 but it's \(result3.amount)"
|
||||
)
|
||||
|
||||
let number = Zatoshi(amount: Zatoshi.Constants.maxZatoshi)
|
||||
let result4 = number + number
|
||||
|
||||
XCTAssertEqual(
|
||||
result4.amount,
|
||||
Zatoshi.Constants.maxZatoshi,
|
||||
"Zatoshi tests: `testAddingZatoshi` the value is expected to be clapmed to upper bound but it's \(result4.amount)"
|
||||
)
|
||||
}
|
||||
|
||||
func testSubtractingZatoshi() throws {
|
||||
let numberA1 = Zatoshi(amount: 100_000)
|
||||
let numberB1 = Zatoshi(amount: 200_000)
|
||||
let result1 = numberA1 - numberB1
|
||||
|
||||
XCTAssertEqual(
|
||||
result1.amount,
|
||||
Zatoshi(amount: -100_000).amount,
|
||||
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be -100_000 but it's \(result1.amount)"
|
||||
)
|
||||
|
||||
let numberA2 = Zatoshi(amount: -100_000)
|
||||
let numberB2 = Zatoshi(amount: 200_000)
|
||||
let result2 = numberA2 - numberB2
|
||||
|
||||
XCTAssertEqual(
|
||||
result2.amount,
|
||||
Zatoshi(amount: -300_000).amount,
|
||||
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be -300_000 but it's \(result2.amount)"
|
||||
)
|
||||
|
||||
let numberA3 = Zatoshi(amount: 100_000)
|
||||
let numberB3 = Zatoshi(amount: -200_000)
|
||||
let result3 = numberA3 - numberB3
|
||||
|
||||
XCTAssertEqual(
|
||||
result3.amount,
|
||||
Zatoshi(amount: 300_000).amount,
|
||||
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be 300_000 but it's \(result3.amount)"
|
||||
)
|
||||
|
||||
let number = Zatoshi(amount: -Zatoshi.Constants.maxZatoshi)
|
||||
let result4 = number + number
|
||||
|
||||
XCTAssertEqual(
|
||||
result4.amount,
|
||||
-Zatoshi.Constants.maxZatoshi,
|
||||
"Zatoshi tests: `testSubtractingZatoshi` the value is expected to be clapmed to lower bound but it's \(result4.amount)"
|
||||
)
|
||||
}
|
||||
|
||||
func testHumanReadable() throws {
|
||||
// result of this division is 1.4285714285714285714285714285714285714
|
||||
let number = Zatoshi.from(decimal: Decimal(200.0 / 140.0))
|
||||
|
||||
// IMPORTANT: the INTERNAL value of number is still 1.4285714285714285714285714285714285714!!!
|
||||
// but decimalString is rounding it to maximumFractionDigits set to be 8
|
||||
|
||||
// We can't compare it to double value 1.42857143 (or even Decimal(1.42857143))
|
||||
// so we convert it to string, in that case we are prooving it to be rendered
|
||||
// to the user exactly the way we want
|
||||
XCTAssertEqual(
|
||||
number.decimalString,
|
||||
"1.42857143",
|
||||
"Zatoshi tests: the value is expected to be 1.42857143 but it's \(number.decimalString)"
|
||||
)
|
||||
}
|
||||
|
||||
func testUSDtoZecToUSD() throws {
|
||||
// The price of zec is $140, we want to send $200
|
||||
let usd2zec = NSDecimalNumber(decimal: 200.0 / 140.0)
|
||||
|
||||
XCTAssertEqual(
|
||||
usd2zec.decimalString,
|
||||
"1.42857143",
|
||||
"Zatoshi tests: `testUSDtoZatoshiToUSD` the value is expected to be 1.42857143 but it's \(usd2zec.decimalString)"
|
||||
)
|
||||
|
||||
// convert it back
|
||||
let zec2usd = NSDecimalNumber(decimal: usd2zec.decimalValue * 140.0)
|
||||
|
||||
XCTAssertEqual(
|
||||
zec2usd.decimalString,
|
||||
"200",
|
||||
"Zatoshi tests: `testUSDtoZatoshiToUSD` the value is expected to be 200 but it's \(zec2usd.decimalString)"
|
||||
)
|
||||
}
|
||||
|
||||
func testStringToZatoshi() throws {
|
||||
if let number = Zatoshi.from(decimalString: "200.0") {
|
||||
XCTAssertEqual(
|
||||
number.decimalString,
|
||||
"200",
|
||||
"Zatoshi tests: `testStringToZec` the value is expected to be 200 but it's \(number.decimalString)"
|
||||
)
|
||||
} else {
|
||||
XCTFail("Zatoshi tests: `testStringToZatoshi` failed to convert number.")
|
||||
}
|
||||
|
||||
if let number = Zatoshi.from(decimalString: "0.02836478949923") {
|
||||
XCTAssertEqual(
|
||||
number.amount,
|
||||
2_836_479,
|
||||
"Zatoshi tests: the value is expected to be 2_836_478 but it's \(number.amount)"
|
||||
)
|
||||
} else {
|
||||
XCTFail("Zatoshi tests: `testStringToZatoshi` failed to convert number.")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue