Skip to content

Commit 3dce8b8

Browse files
committed
feat: add parse units
1 parent 417f30e commit 3dce8b8

File tree

3 files changed

+164
-8
lines changed

3 files changed

+164
-8
lines changed

Sources/BasedUtils/BasedUtils.swift

+70
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,52 @@ public class BasedUtils {
4141
return "\(negative ? "-" : "")\(integer.isEmpty ? "0" : integer)\(fraction.isEmpty ? "" : ".\(fraction)")"
4242
}
4343

44+
/// Returns BigInt representation of a given string and decimals.
45+
///
46+
/// Implemented after viem's parseUnits:
47+
/// https://github.com/wevm/viem/blob/main/src/utils/unit/parseUnits.ts
48+
///
49+
public static func parseUnits(_ value: String, _ decimals: Int) -> BigInt {
50+
let components = value.components(separatedBy: ".")
51+
var integer = components[0]
52+
var fraction = components.count > 1 ? components[1] : "0"
53+
54+
let negative = integer.starts(with: "-")
55+
if negative { integer = integer.slice(from: 1) }
56+
57+
fraction = fraction.removeTrailingZeros()
58+
59+
if decimals == 0 {
60+
if round(Double(".\(fraction)") ?? 0) == 1 {
61+
integer = BigInt.plusOne(integer).description
62+
}
63+
fraction = ""
64+
} else if fraction.count > decimals {
65+
let left = fraction.slice(from: 0, to: decimals - 1)
66+
let unit = fraction.slice(from: decimals - 1, to: decimals)
67+
let right = fraction.slice(from: decimals)
68+
69+
let rounded = round(Double("\(unit).\(right)") ?? 0)
70+
if rounded > 9 {
71+
fraction = "\(BigInt.plusOne(left).description)0".padStart(left.count + 1, with: "0")
72+
} else {
73+
fraction = "\(left)\(Int(rounded))"
74+
}
75+
76+
if fraction.count > decimals {
77+
fraction = fraction.slice(from: 1)
78+
integer = BigInt.plusOne(integer).description
79+
}
80+
81+
fraction = fraction.slice(from: 0, to: decimals)
82+
} else {
83+
fraction = fraction.padEnd(decimals, with: "0")
84+
}
85+
86+
let bn = "\(negative ? "-" : "")\(integer)\(fraction)"
87+
return BigInt(bn) ?? BigInt(0)
88+
}
89+
4490
}
4591

4692
public extension BasedUtils.UnitType {
@@ -54,8 +100,32 @@ public extension BasedUtils.UnitType {
54100

55101
// Private
56102

103+
private extension BigInt {
104+
105+
init(orZero value: String) {
106+
if let bigIntValue = BigInt(value) {
107+
self = bigIntValue
108+
} else {
109+
self = BigInt(0)
110+
}
111+
}
112+
113+
static func plusOne(_ value: String) -> BigInt {
114+
return BigInt(orZero: value) + BigInt(1)
115+
}
116+
117+
}
118+
57119
private extension String {
58120

121+
func padEnd(_ length: Int, with pad: String = " ") -> String {
122+
guard self.count < length else { return self }
123+
let padCount = length - self.count
124+
let repeatedPad = String(repeating: pad, count: (padCount + pad.count - 1) / pad.count)
125+
let endIndex = repeatedPad.index(repeatedPad.startIndex, offsetBy: padCount)
126+
return self + repeatedPad[..<endIndex]
127+
}
128+
59129
func padStart(_ length: Int, with pad: Character = " ") -> String {
60130
let padCount = length - self.count
61131
guard padCount > 0 else { return self }

Tests/BasedUtilsTests/FormatUnitsTests.swift

-8
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,6 @@ import XCTest
1212

1313
final class FormatUnitsTests: XCTestCase {
1414

15-
override func setUpWithError() throws {
16-
// Put setup code here. This method is called before the invocation of each test method in the class.
17-
}
18-
19-
override func tearDownWithError() throws {
20-
// Put teardown code here. This method is called after the invocation of each test method in the class.
21-
}
22-
2315
func testFormatUnits() throws {
2416
XCTAssertEqual(BasedUtils.formatUnits(BigInt("69"), 0), "69")
2517
XCTAssertEqual(BasedUtils.formatUnits(BigInt("69"), 5), "0.00069")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//
2+
// ParseUnitsTests.swift
3+
//
4+
//
5+
// Created by Jann Driessen on 06.06.24.
6+
//
7+
8+
import BigInt
9+
import XCTest
10+
11+
@testable import BasedUtils
12+
13+
final class ParseUnitsTests: XCTestCase {
14+
15+
func testParseUnits() throws {
16+
XCTAssertEqual(BasedUtils.parseUnits("69", 1), BigInt("690"))
17+
XCTAssertEqual(BasedUtils.parseUnits("13", 5), BigInt("1300000"))
18+
XCTAssertEqual(BasedUtils.parseUnits("420", 10), BigInt("4200000000000"))
19+
XCTAssertEqual(BasedUtils.parseUnits("20", 9), BigInt("20000000000"))
20+
XCTAssertEqual(BasedUtils.parseUnits("40", 18), BigInt("40000000000000000000"))
21+
XCTAssertEqual(BasedUtils.parseUnits("1.2345", 4), BigInt("12345"))
22+
XCTAssertEqual(BasedUtils.parseUnits("1.0045", 4), BigInt("10045"))
23+
XCTAssertEqual(BasedUtils.parseUnits("1.2345000", 4), BigInt("12345"))
24+
XCTAssertEqual(BasedUtils.parseUnits("6942069420.12345678912345", 18), BigInt("6942069420123456789123450000"))
25+
XCTAssertEqual(BasedUtils.parseUnits("6942069420.00045678912345", 18), BigInt("6942069420000456789123450000"))
26+
XCTAssertEqual(BasedUtils.parseUnits("6942123123123069420.1234544444678912345", 50), BigInt("694212312312306942012345444446789123450000000000000000000000000000000"))
27+
XCTAssertEqual(BasedUtils.parseUnits("-69", 1), BigInt("-690"))
28+
XCTAssertEqual(BasedUtils.parseUnits("1.2345", 4), BigInt("12345"))
29+
XCTAssertEqual(BasedUtils.parseUnits("-6942069420.12345678912345", 18), BigInt("-6942069420123456789123450000"))
30+
XCTAssertEqual(BasedUtils.parseUnits("-6942123123123069420.1234544444678912345", 50), BigInt("-694212312312306942012345444446789123450000000000000000000000000000000"))
31+
}
32+
33+
func testParseUnitsDecimalsZero() throws {
34+
XCTAssertEqual(BasedUtils.parseUnits("69.2352112312312451512412341231", 0), BigInt("69"))
35+
XCTAssertEqual(BasedUtils.parseUnits("69.5952141234124125231523412312", 0), BigInt("70"))
36+
XCTAssertEqual(BasedUtils.parseUnits("12301000000000000020000", 0), BigInt("12301000000000000020000"))
37+
XCTAssertEqual(BasedUtils.parseUnits("12301000000000000020000.123", 0), BigInt("12301000000000000020000"))
38+
XCTAssertEqual(BasedUtils.parseUnits("12301000000000000020000.5", 0), BigInt("12301000000000000020001"))
39+
XCTAssertEqual(BasedUtils.parseUnits("99999999999999999999999.5", 0), BigInt("100000000000000000000000"))
40+
}
41+
42+
func testParseUnitsDecimalsSmallerFractionLength() throws {
43+
XCTAssertEqual(BasedUtils.parseUnits("69.23521", 0), BigInt("69"))
44+
XCTAssertEqual(BasedUtils.parseUnits("69.56789", 0), BigInt("70"))
45+
XCTAssertEqual(BasedUtils.parseUnits("69.23521", 1), BigInt("692"))
46+
XCTAssertEqual(BasedUtils.parseUnits("69.23521", 2), BigInt("6924"))
47+
XCTAssertEqual(BasedUtils.parseUnits("69.23221", 2), BigInt("6923"))
48+
XCTAssertEqual(BasedUtils.parseUnits("69.23621", 3), BigInt("69236"))
49+
XCTAssertEqual(BasedUtils.parseUnits("999999.99999", 3), BigInt("1000000000"))
50+
XCTAssertEqual(BasedUtils.parseUnits("699999.99999", 3), BigInt("700000000"))
51+
XCTAssertEqual(BasedUtils.parseUnits("699999.98999", 3), BigInt("699999990"))
52+
XCTAssertEqual(BasedUtils.parseUnits("699959.99999", 3), BigInt("699960000"))
53+
XCTAssertEqual(BasedUtils.parseUnits("699099.99999", 3), BigInt("699100000"))
54+
XCTAssertEqual(BasedUtils.parseUnits("100000.000999", 3), BigInt("100000001"))
55+
XCTAssertEqual(BasedUtils.parseUnits("100000.990999", 3), BigInt("100000991"))
56+
XCTAssertEqual(BasedUtils.parseUnits("69.00221", 3), BigInt("69002"))
57+
XCTAssertEqual(BasedUtils.parseUnits("1.0536059576998882", 7), BigInt("10536060"))
58+
XCTAssertEqual(BasedUtils.parseUnits("1.0053059576998882", 7), BigInt("10053060"))
59+
XCTAssertEqual(BasedUtils.parseUnits("1.0000000900000000", 7), BigInt("10000001"))
60+
XCTAssertEqual(BasedUtils.parseUnits("1.0000009900000000", 7), BigInt("10000010"))
61+
XCTAssertEqual(BasedUtils.parseUnits("1.0000099900000000", 7), BigInt("10000100"))
62+
XCTAssertEqual(BasedUtils.parseUnits("1.0000092900000000", 7), BigInt("10000093"))
63+
XCTAssertEqual(BasedUtils.parseUnits("1.5536059576998882", 7), BigInt("15536060"))
64+
XCTAssertEqual(BasedUtils.parseUnits("1.0536059476998882", 7), BigInt("10536059"))
65+
XCTAssertEqual(BasedUtils.parseUnits("1.4545454545454545", 7), BigInt("14545455"))
66+
XCTAssertEqual(BasedUtils.parseUnits("1.1234567891234567", 7), BigInt("11234568"))
67+
XCTAssertEqual(BasedUtils.parseUnits("1.8989898989898989", 7), BigInt("18989899"))
68+
XCTAssertEqual(BasedUtils.parseUnits("9.9999999999999999", 7), BigInt(
69+
"100000000"))
70+
XCTAssertEqual(BasedUtils.parseUnits("0.0536059576998882", 7), BigInt("536060"))
71+
XCTAssertEqual(BasedUtils.parseUnits("0.0053059576998882", 7), BigInt("53060"))
72+
XCTAssertEqual(BasedUtils.parseUnits("0.0000000900000000", 7), BigInt("1"))
73+
XCTAssertEqual(BasedUtils.parseUnits("0.0000009900000000", 7), BigInt("10"))
74+
XCTAssertEqual(BasedUtils.parseUnits("0.0000099900000000", 7), BigInt("100"))
75+
XCTAssertEqual(BasedUtils.parseUnits("0.0000092900000000", 7), BigInt("93"))
76+
XCTAssertEqual(BasedUtils.parseUnits("0.0999999999999999", 7), BigInt("1000000"))
77+
XCTAssertEqual(BasedUtils.parseUnits("0.0099999999999999", 7), BigInt("100000"))
78+
XCTAssertEqual(BasedUtils.parseUnits("0.00000000059", 9), BigInt("1"))
79+
XCTAssertEqual(BasedUtils.parseUnits("0.0000000003", 9), BigInt("0"))
80+
XCTAssertEqual(BasedUtils.parseUnits("69.00000000000", 9), BigInt("69000000000"))
81+
XCTAssertEqual(BasedUtils.parseUnits("69.00000000019", 9), BigInt("69000000000"))
82+
XCTAssertEqual(BasedUtils.parseUnits("69.00000000059", 9), BigInt("69000000001"))
83+
XCTAssertEqual(BasedUtils.parseUnits("69.59000000059", 9), BigInt("69590000001"))
84+
XCTAssertEqual(BasedUtils.parseUnits("69.59000002359", 9), BigInt("69590000024"))
85+
}
86+
87+
func testPerformanceExample() throws {
88+
// This is an example of a performance test case.
89+
self.measure {
90+
// Put the code you want to measure the time of here.
91+
}
92+
}
93+
94+
}

0 commit comments

Comments
 (0)