Skip to content

Commit

Permalink
Fixed formatting to number #224
Browse files Browse the repository at this point in the history
  • Loading branch information
v57 committed Oct 23, 2018
1 parent 143d9b5 commit a310b35
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 73 deletions.
95 changes: 95 additions & 0 deletions web3swift/Convenience/UInt256.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,101 @@ extension BigUInt {
}
self = mainPart
}
/// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits",
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
///
/// Returns nil of formatting is not possible to satisfy.
public func string(units: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String {
return string(numberDecimals: units.decimals, formattingDecimals: decimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific)
}

/// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits",
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
/// Fallbacks to scientific format if higher precision is required.
///
/// Returns nil of formatting is not possible to satisfy.
public func string(numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String {
guard self != 0 else { return "0" }
let unitDecimals = numberDecimals
var toDecimals = formattingDecimals
if unitDecimals < toDecimals {
toDecimals = unitDecimals
}
let divisor = BigUInt(10).power(unitDecimals)
let (quotient, remainder) = quotientAndRemainder(dividingBy: divisor)
var fullRemainder = String(remainder)
let fullPaddedRemainder = fullRemainder.leftPadding(toLength: unitDecimals, withPad: "0")
let remainderPadded = fullPaddedRemainder[0 ..< toDecimals]
if remainderPadded == String(repeating: "0", count: toDecimals) {
if quotient != 0 {
return String(quotient)
} else if fallbackToScientific {
var firstDigit = 0
for char in fullPaddedRemainder {
if char == "0" {
firstDigit = firstDigit + 1
} else {
let firstDecimalUnit = String(fullPaddedRemainder[firstDigit ..< firstDigit+1])
var remainingDigits = ""
let numOfRemainingDecimals = fullPaddedRemainder.count - firstDigit - 1
if numOfRemainingDecimals <= 0 {
remainingDigits = ""
} else if numOfRemainingDecimals > formattingDecimals {
let end = firstDigit+1+formattingDecimals > fullPaddedRemainder.count ? fullPaddedRemainder.count : firstDigit+1+formattingDecimals
remainingDigits = String(fullPaddedRemainder[firstDigit+1 ..< end])
} else {
remainingDigits = String(fullPaddedRemainder[firstDigit+1 ..< fullPaddedRemainder.count])
}
fullRemainder = firstDecimalUnit
if !remainingDigits.isEmpty {
fullRemainder += decimalSeparator + remainingDigits
}
firstDigit = firstDigit + 1
break
}
}
return fullRemainder + "e-" + String(firstDigit)
}
}
if toDecimals == 0 {
return String(quotient)
} else {
return String(quotient) + decimalSeparator + remainderPadded
}
}
}

extension BigInt {
/// Returns .description to not confuse
public func string() -> String {
return description
}
/// Formats a BigInt object to String. The supplied number is first divided into integer and decimal part based on "units",
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
/// Fallbacks to scientific format if higher precision is required.
public func string(numberDecimals: Int, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String {

let formatted = magnitude.string(numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific)
switch sign {
case .plus:
return formatted
case .minus:
return "-" + formatted
}
}

/// Formats a BigInt object to String. The supplied number is first divided into integer and decimal part based on "units",
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
public func string(units: Web3Units, decimals: Int = 4, decimalSeparator: String = ".") -> String {

let formatted = magnitude.string(numberDecimals: units.decimals, formattingDecimals: decimals, decimalSeparator: decimalSeparator)
switch sign {
case .plus:
return formatted
case .minus:
return "-" + formatted
}
}
}

public struct NaturalUnits {
Expand Down
66 changes: 12 additions & 54 deletions web3swift/Web3/Web3+Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,80 +194,38 @@ extension Web3.Utils {
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
///
/// Returns nil of formatting is not possible to satisfy.
public static func formatToEthereumUnits(_ bigNumber: BigInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".") -> String? {
let magnitude = bigNumber.magnitude
guard let formatted = formatToEthereumUnits(magnitude, toUnits: toUnits, decimals: decimals, decimalSeparator: decimalSeparator) else { return nil }
switch bigNumber.sign {
case .plus:
return formatted
case .minus:
return "-" + formatted
}
@available(*,deprecated: 2.0,message: "Use number.string(units:formattingDecimals:decimalSeparator:fallbackToScientific")
public static func formatToEthereumUnits(_ bigNumber: BigInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".") -> String {
return bigNumber.string(units: toUnits, decimals: decimals, decimalSeparator: decimalSeparator)
}

/// Formats a BigInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits",
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
/// Fallbacks to scientific format if higher precision is required.
///
/// Returns nil of formatting is not possible to satisfy.
public static func formatToPrecision(_ bigNumber: BigInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String? {
let magnitude = bigNumber.magnitude
guard let formatted = formatToPrecision(magnitude, numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific) else { return nil }
switch bigNumber.sign {
case .plus:
return formatted
case .minus:
return "-" + formatted
}
@available(*,deprecated: 2.0,message: "Use number.string(numberDecimals:formattingDecimals:decimalSeparator:fallbackToScientific")
public static func formatToPrecision(_ bigNumber: BigInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String {
return bigNumber.string(numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific)
}

/// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits",
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
///
/// Returns nil of formatting is not possible to satisfy.
public static func formatToEthereumUnits(_ bigNumber: BigUInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String? {
return formatToPrecision(bigNumber, numberDecimals: toUnits.decimals, formattingDecimals: decimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific)
@available(*,deprecated: 2.0,message: "Use number.string(units:formattingDecimals:decimalSeparator:fallbackToScientific")
public static func formatToEthereumUnits(_ bigNumber: BigUInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String {
return bigNumber.string(units: toUnits, decimals: decimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific)
}

/// Formats a BigUInt object to String. The supplied number is first divided into integer and decimal part based on "toUnits",
/// then limit the decimal part to "decimals" symbols and uses a "decimalSeparator" as a separator.
/// Fallbacks to scientific format if higher precision is required.
///
/// Returns nil of formatting is not possible to satisfy.
public static func formatToPrecision(_ bigNumber: BigUInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String? {
if bigNumber == 0 {
return "0"
}
let unitDecimals = numberDecimals
var toDecimals = formattingDecimals
if unitDecimals < toDecimals {
toDecimals = unitDecimals
}
let divisor = BigUInt(10).power(unitDecimals)
let (quotient, remainder) = bigNumber.quotientAndRemainder(dividingBy: divisor)
let fullRemainder = String(remainder)
let fullPaddedRemainder = fullRemainder.leftPadding(toLength: unitDecimals, withPad: "0")
let remainderPadded = fullPaddedRemainder[0 ..< toDecimals]
if remainderPadded == String(repeating: "0", count: toDecimals) {
if quotient != 0 {
return String(quotient)
} else if fallbackToScientific {
var firstDigit = 0
for char in fullPaddedRemainder {
if char == "0" {
firstDigit = firstDigit + 1
} else {
firstDigit = firstDigit + 1
break
}
}
return fullRemainder + "e-" + String(firstDigit)
}
}
if toDecimals == 0 {
return String(quotient)
}
return String(quotient) + decimalSeparator + remainderPadded
@available(*,deprecated: 2.0,message: "Use number.string(numberDecimals:formattingDecimals:decimalSeparator:fallbackToScientific")
public static func formatToPrecision(_ bigNumber: BigUInt, numberDecimals: Int = 18, formattingDecimals: Int = 4, decimalSeparator: String = ".", fallbackToScientific: Bool = false) -> String {
return bigNumber.string(numberDecimals: numberDecimals, formattingDecimals: formattingDecimals, decimalSeparator: decimalSeparator, fallbackToScientific: fallbackToScientific)
}

/// Recover the Ethereum address from recoverable secp256k1 signature. Message is first hashed using the "personal hash" protocol.
Expand Down
4 changes: 2 additions & 2 deletions web3swiftTests/ERC20Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ class ERC20Tests: XCTestCase {

let contract = try web3.contract(Web3.Utils.erc20ABI, at: "0x45245bc59219eeaaf6cd3f382e078a461ff9de7b")
var options = Web3Options()
options.from = EthereumAddress("0x6394b37Cf80A7358b38068f0CA4760ad49983a1B")
let addressOfUser = EthereumAddress("0x6394b37Cf80A7358b38068f0CA4760ad49983a1B")
options.from = "0x6394b37Cf80A7358b38068f0CA4760ad49983a1B"
let addressOfUser = "0x6394b37Cf80A7358b38068f0CA4760ad49983a1B"
let tokenBalance = try contract.method("balanceOf", args: addressOfUser, options: options).call(options: nil).uint256()
print(tokenBalance)
}
Expand Down
4 changes: 2 additions & 2 deletions web3swiftTests/InfuraTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class InfuraTests: XCTestCase {
let web3 = Web3(infura: .mainnet)
let address = EthereumAddress("0x6394b37Cf80A7358b38068f0CA4760ad49983a1B")
let balance = try web3.eth.getBalance(address: address)
let balString = Web3.Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 3)
print(balString ?? "nil")
let balString = balance.string(units: .eth, decimals: 3)
print(balString)
}

func testGetBlockByHash() throws {
Expand Down
2 changes: 1 addition & 1 deletion web3swiftTests/MainTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Tests: XCTestCase {
}

func testMakePrivateKey() {
let privateKey = SECP256K1.generatePrivateKey()
let privateKey = Data.random(length: 32)
let publicKey = try? SECP256K1.privateToPublic(privateKey: privateKey)
XCTAssert(publicKey != nil, "Failed to create new private key")
}
Expand Down
40 changes: 26 additions & 14 deletions web3swiftTests/NumberFormattingUtilTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,55 @@ import XCTest
class NumberFormattingUtilTests: XCTestCase {
func testNumberFormattingUtil() {
let balance = BigInt("-1000000000000000000")!
let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssert(formatted == "-1")
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssertEqual(formatted, "-1")
}

func testNumberFormattingUtil2() {
let balance = BigInt("-1000000000000000")!
let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssert(formatted == "-0,0010")
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssertEqual(formatted, "-0,0010")
}

func testNumberFormattingUtil3() {
let balance = BigInt("-1000000000000")!
let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssert(formatted == "-0,0000")
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssertEqual(formatted, "-0,0000")
}

func testNumberFormattingUtil4() {
let balance = BigInt("-1000000000000")!
let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",")
XCTAssert(formatted == "-0,000001000")
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",")
XCTAssertEqual(formatted, "-0,000001000")
}

func testNumberFormattingUtil5() {
let balance = BigInt("-1")!
let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",", fallbackToScientific: true)
XCTAssert(formatted == "-1e-18")
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",", fallbackToScientific: true)
XCTAssertEqual(formatted, "-1e-18")
}

func testNumberFormattingUtil6() {
let balance = BigInt("0")!
let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",")
XCTAssert(formatted == "0")
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",")
XCTAssertEqual(formatted, "0")
}

func testNumberFormattingUtil7() {
let balance = BigInt("-1100000000000000000")!
let formatted = Web3.Utils.formatToPrecision(balance, numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssert(formatted == "-1,1000")
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssertEqual(formatted, "-1,1000")
}

func testNumberFormattingUtil8() {
let balance = BigInt("100")!
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",", fallbackToScientific: true)
XCTAssertEqual(formatted, "1,00e-16")
}

func testNumberFormattingUtil9() {
let balance = BigInt("1000000")!
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",", fallbackToScientific: true)
XCTAssertEqual(formatted, "1,0000e-12")
}
}

0 comments on commit a310b35

Please sign in to comment.