From 114343a705df4725dfe7ab8a2a326b8883cfd79c Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 11 Nov 2024 13:17:20 -0600 Subject: [PATCH] improve Decimal conversion performance using `_mantissa` (#124) * 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 --- Sources/Data Conversion.swift | 3 +- Sources/Floating Point Conversion.swift | 55 +++++++++++++++++++++++++ Tests/BigIntTests/BigIntTests.swift | 12 ++++++ Tests/BigIntTests/BigUIntTests.swift | 7 ++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/Sources/Data Conversion.swift b/Sources/Data Conversion.swift index 613e74b..3b35fe8 100644 --- a/Sources/Data Conversion.swift +++ b/Sources/Data Conversion.swift @@ -53,7 +53,6 @@ extension BigUInt { let byteCount = (self.bitWidth + 7) / 8 let buffer = UnsafeMutableBufferPointer.allocate(capacity: byteCount) - buffer.initialize(repeating: 0) guard byteCount > 0 else { return UnsafeRawBufferPointer(start: buffer.baseAddress, count: 0) } @@ -69,6 +68,8 @@ extension BigUInt { i -= 1 } } + let zeroOut = UnsafeMutableBufferPointer(start: buffer.baseAddress, count: i) + zeroOut.initialize(repeating: 0) return UnsafeRawBufferPointer(start: buffer.baseAddress, count: byteCount) } diff --git a/Sources/Floating Point Conversion.swift b/Sources/Floating Point Conversion.swift index 3120fd3..5a5e8b2 100644 --- a/Sources/Floating Point Conversion.swift +++ b/Sources/Floating Point Conversion.swift @@ -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 @@ -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 diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 6f71a20..76da4a5 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -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() { diff --git a/Tests/BigIntTests/BigUIntTests.swift b/Tests/BigIntTests/BigUIntTests.swift index 4b4a826..244901c 100644 --- a/Tests/BigIntTests/BigUIntTests.swift +++ b/Tests/BigIntTests/BigUIntTests.swift @@ -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() { @@ -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]) }