Skip to content

Commit

Permalink
Zeroes now strips when converting BigUInt to string #200
Browse files Browse the repository at this point in the history
Added .stripZeroesOptions to BigUInt.number.string(units:decimals:decimalSeparator:options)
  • Loading branch information
v57 committed Oct 23, 2018
1 parent a310b35 commit 0d5e44f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 71 deletions.
111 changes: 60 additions & 51 deletions web3swift/Convenience/UInt256.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
import BigInt
import Foundation


extension BigUInt {


public init?(_ string: String, units: Web3Units) {
self.init(string, decimals: units.decimals)
}
Expand All @@ -30,23 +33,31 @@ extension BigUInt {
}
self = mainPart
}


public struct StringOptions: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let fallbackToScientific = StringOptions(rawValue: 0b1)
public static let stripZeroes = StringOptions(rawValue: 0b10)
public static let `default`: StringOptions = [.stripZeroes]
}
/// 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)
/// default: decimals: 18, decimalSeparator: ".", options: .stripZeroes
public func string(units: Web3Units, decimals: Int = 18, decimalSeparator: String = ".", options: StringOptions = .default) -> String {
return string(unitDecimals: units.decimals, decimals: decimals, decimalSeparator: decimalSeparator, options: options)
}

/// 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 {
/// default: decimals: 18, decimalSeparator: ".", options: .stripZeroes
public func string(unitDecimals: Int, decimals: Int = 18, decimalSeparator: String = ".", options: StringOptions = .default) -> String {
guard self != 0 else { return "0" }
let unitDecimals = numberDecimals
var toDecimals = formattingDecimals
var toDecimals = decimals
if unitDecimals < toDecimals {
toDecimals = unitDecimals
}
Expand All @@ -55,56 +66,60 @@ extension BigUInt {
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 {
let offset = remainderPadded.reversed().firstIndex(where: { $0 != "0" })?.base

if let offset = offset {
if toDecimals == 0 {
return String(quotient)
} else if fallbackToScientific {
var firstDigit = 0
for char in fullPaddedRemainder {
if char == "0" {
firstDigit = firstDigit + 1
} else if options.contains(.stripZeroes) {
return String(quotient) + decimalSeparator + remainderPadded[..<offset]
} else {
return String(quotient) + decimalSeparator + remainderPadded
}
} else if quotient != 0 || !options.contains(.fallbackToScientific) {
return String(quotient)
} else {
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 > decimals {
let end = firstDigit+1+decimals > fullPaddedRemainder.count ? fullPaddedRemainder.count : firstDigit+1+decimals
remainingDigits = String(fullPaddedRemainder[firstDigit+1 ..< end])
} 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
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
return fullRemainder + "e-" + String(firstDigit)
}
}
}

extension BigInt {
public typealias StringOptions = BigUInt.StringOptions
/// 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)
/// default: decimals: 18, decimalSeparator: ".", options: .stripZeroes
public func string(unitDecimals: Int, decimals: Int = 18, decimalSeparator: String = ".", options: StringOptions = .default) -> String {
let formatted = magnitude.string(unitDecimals: unitDecimals, decimals: decimals, decimalSeparator: decimalSeparator, options: options)
switch sign {
case .plus:
return formatted
Expand All @@ -115,15 +130,9 @@ extension BigInt {

/// 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
}
/// default: decimals: 18, decimalSeparator: ".", options: .stripZeroes
public func string(units: Web3Units, decimals: Int = 18, decimalSeparator: String = ".", options: StringOptions = .default) -> String {
return string(unitDecimals: units.decimals, decimals: decimals, decimalSeparator: decimalSeparator, options: options)
}
}

Expand Down
26 changes: 19 additions & 7 deletions web3swift/Web3/Web3+Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ 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.
@available(*,deprecated: 2.0,message: "Use number.string(units:formattingDecimals:decimalSeparator:fallbackToScientific")
@available(*,deprecated: 2.0,message: "Use number.string(units:decimals:decimalSeparator:options:)")
public static func formatToEthereumUnits(_ bigNumber: BigInt, toUnits: Web3Units = .eth, decimals: Int = 4, decimalSeparator: String = ".") -> String {
return bigNumber.string(units: toUnits, decimals: decimals, decimalSeparator: decimalSeparator)
}
Expand All @@ -204,28 +204,40 @@ extension Web3.Utils {
/// Fallbacks to scientific format if higher precision is required.
///
/// Returns nil of formatting is not possible to satisfy.
@available(*,deprecated: 2.0,message: "Use number.string(numberDecimals:formattingDecimals:decimalSeparator:fallbackToScientific")
@available(*,deprecated: 2.0,message: "Use number.string(unitDecimals:formattingDecimals:decimalSeparator:options:)")
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)
var options = BigUInt.StringOptions.default
if fallbackToScientific {
options.insert(.fallbackToScientific)
}
return bigNumber.string(unitDecimals: numberDecimals, decimals: formattingDecimals, decimalSeparator: decimalSeparator, options: options)
}

/// 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.
@available(*,deprecated: 2.0,message: "Use number.string(units:formattingDecimals:decimalSeparator:fallbackToScientific")
@available(*,deprecated: 2.0,message: "Use number.string(units:formattingDecimals:decimalSeparator:options:)")
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)
var options = BigUInt.StringOptions.default
if fallbackToScientific {
options.insert(.fallbackToScientific)
}
return bigNumber.string(units: toUnits, decimals: decimals, decimalSeparator: decimalSeparator, options: options)
}

/// 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.
@available(*,deprecated: 2.0,message: "Use number.string(numberDecimals:formattingDecimals:decimalSeparator:fallbackToScientific")
@available(*,deprecated: 2.0,message: "Use number.string(unitDecimals:formattingDecimals:decimalSeparator:options:)")
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)
var options = BigUInt.StringOptions.default
if fallbackToScientific {
options.insert(.fallbackToScientific)
}
return bigNumber.string(unitDecimals: numberDecimals, decimals: formattingDecimals, decimalSeparator: decimalSeparator, options: options)
}

/// Recover the Ethereum address from recoverable secp256k1 signature. Message is first hashed using the "personal hash" protocol.
Expand Down
26 changes: 13 additions & 13 deletions web3swiftTests/NumberFormattingUtilTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,55 @@ import XCTest
class NumberFormattingUtilTests: XCTestCase {
func testNumberFormattingUtil() {
let balance = BigInt("-1000000000000000000")!
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
let formatted = balance.string(unitDecimals: 18, decimals: 4, decimalSeparator: ",")
XCTAssertEqual(formatted, "-1")
}

func testNumberFormattingUtil2() {
let balance = BigInt("-1000000000000000")!
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 4, decimalSeparator: ",")
XCTAssertEqual(formatted, "-0,0010")
let formatted = balance.string(unitDecimals: 18, decimals: 4, decimalSeparator: ",")
XCTAssertEqual(formatted, "-0,001")
}

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

func testNumberFormattingUtil4() {
let balance = BigInt("-1000000000000")!
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",")
XCTAssertEqual(formatted, "-0,000001000")
let formatted = balance.string(unitDecimals: 18, decimals: 9, decimalSeparator: ",")
XCTAssertEqual(formatted, "-0,000001")
}

func testNumberFormattingUtil5() {
let balance = BigInt("-1")!
let formatted = balance.string(numberDecimals: 18, formattingDecimals: 9, decimalSeparator: ",", fallbackToScientific: true)
let formatted = balance.string(unitDecimals: 18, decimals: 9, decimalSeparator: ",", options: [.stripZeroes,.fallbackToScientific])
XCTAssertEqual(formatted, "-1e-18")
}

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

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

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

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

0 comments on commit 0d5e44f

Please sign in to comment.