Skip to content

Commit

Permalink
improve Decimal conversion performance using _mantissa (#124)
Browse files Browse the repository at this point in the history
* improve Decimal conversion performance using `_mantissa`

* fix linux builds by keeping the decimal string init in a compiler directive

* more efficient zero out assurance after buffer creation

* Decimal (from bigint) initializer
  • Loading branch information
mredig authored Nov 11, 2024
1 parent 97a80fb commit 114343a
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Sources/Data Conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ extension BigUInt {
let byteCount = (self.bitWidth + 7) / 8

let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: byteCount)
buffer.initialize(repeating: 0)

guard byteCount > 0 else { return UnsafeRawBufferPointer(start: buffer.baseAddress, count: 0) }

Expand All @@ -69,6 +68,8 @@ extension BigUInt {
i -= 1
}
}
let zeroOut = UnsafeMutableBufferPointer<UInt8>(start: buffer.baseAddress, count: i)
zeroOut.initialize(repeating: 0)
return UnsafeRawBufferPointer(start: buffer.baseAddress, count: byteCount)
}

Expand Down
55 changes: 55 additions & 0 deletions Sources/Floating Point Conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,20 @@ extension BigUInt {
guard integer.sign == .plus else { return nil }
assert(integer.floatingPointClass == .positiveNormal)

#if os(Linux)
// `Decimal._mantissa` has an internal access level on linux, and it might get
// deprecated in the future, so keeping the string implementation around for now.
let significand = BigUInt("\(integer.significand)")!
#else
let significand = {
var start = BigUInt(0)
for (place, value) in integer.significand.mantissaParts.enumerated() {
guard value > 0 else { continue }
start += (1 << (place * 16)) * BigUInt(value)
}
return start
}()
#endif
let exponent = BigUInt(10).power(integer.exponent)

self = significand * exponent
Expand Down Expand Up @@ -124,3 +137,45 @@ extension BigInt.Sign {
}
}
}

#if canImport(Foundation)
public extension Decimal {
init(_ value: BigUInt) {
guard
value < BigUInt(exactly: Decimal.greatestFiniteMagnitude)!
else {
self = .greatestFiniteMagnitude
return
}
guard !value.isZero else { self = 0; return }

self.init(string: "\(value)")!
}

init(_ value: BigInt) {
if value >= 0 {
self.init(BigUInt(value))
} else {
self.init(value.magnitude)
self *= -1
}
}
}
#endif

#if canImport(Foundation) && !os(Linux)
private extension Decimal {
var mantissaParts: [UInt16] {
[
_mantissa.0,
_mantissa.1,
_mantissa.2,
_mantissa.3,
_mantissa.4,
_mantissa.5,
_mantissa.6,
_mantissa.7,
]
}
}
#endif
12 changes: 12 additions & 0 deletions Tests/BigIntTests/BigIntTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ class BigIntTests: XCTestCase {
test(BigInt(1) << 1024, Float.infinity)
test(BigInt(words: convertWords([0, 0xFFFFFF0000000000, 0])),
Float.greatestFiniteMagnitude)

XCTAssertEqual(Decimal(BigInt(0)), 0)
XCTAssertEqual(Decimal(BigInt(20)), 20)
XCTAssertEqual(Decimal(BigInt(123456789)), 123456789)
XCTAssertEqual(Decimal(BigInt(exactly: Decimal.greatestFiniteMagnitude)!), .greatestFiniteMagnitude)
XCTAssertEqual(Decimal(BigInt(exactly: Decimal.greatestFiniteMagnitude)! * 2), .greatestFiniteMagnitude)
XCTAssertEqual(Decimal(-BigInt(0)), 0)
XCTAssertEqual(Decimal(-BigInt(20)), -20)
XCTAssertEqual(Decimal(-BigInt(123456789)), -123456789)
XCTAssertEqual(Decimal(-BigInt(exactly: Decimal.greatestFiniteMagnitude)!), -.greatestFiniteMagnitude)
XCTAssertEqual(Decimal(-BigInt(exactly: Decimal.greatestFiniteMagnitude)! * 2), -.greatestFiniteMagnitude)

}

func testTwosComplement() {
Expand Down
7 changes: 7 additions & 0 deletions Tests/BigIntTests/BigUIntTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ class BigUIntTests: XCTestCase {
test(BigUInt(0x8000027FFFFFFFFF as UInt64), 0x800002p40 as Float)
test(BigUInt(0x8000028000000000 as UInt64), 0x800002p40 as Float)
test(BigUInt(0x800002FFFFFFFFFF as UInt64), 0x800002p40 as Float)

XCTAssertEqual(Decimal(BigUInt(0)), 0)
XCTAssertEqual(Decimal(BigUInt(20)), 20)
XCTAssertEqual(Decimal(BigUInt(123456789)), 123456789)
XCTAssertEqual(Decimal(BigUInt(exactly: Decimal.greatestFiniteMagnitude)!), .greatestFiniteMagnitude)
XCTAssertEqual(Decimal(BigUInt(exactly: Decimal.greatestFiniteMagnitude)! * 2), .greatestFiniteMagnitude)
}

func testInit_Misc() {
Expand Down Expand Up @@ -688,6 +694,7 @@ class BigUIntTests: XCTestCase {
test(BigUInt(), [])
test(BigUInt(1), [0x01])
test(BigUInt(2), [0x02])
test(BigUInt(0x010203), [0x1, 0x2, 0x3])
test(BigUInt(0x0102030405060708), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
test(BigUInt(0x01) << 64 + BigUInt(0x0203040506070809), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09])
}
Expand Down

0 comments on commit 114343a

Please sign in to comment.