diff --git a/Sources/Comparable.swift b/Sources/Comparable.swift index a5c98ff..dc17b2d 100644 --- a/Sources/Comparable.swift +++ b/Sources/Comparable.swift @@ -6,9 +6,19 @@ // Copyright © 2016-2017 Károly Lőrentey. // +#if canImport(Foundation) import Foundation +#endif extension BigUInt: Comparable { + #if !canImport(Foundation) + public enum ComparisonResult: Sendable, Comparable, Hashable { + case orderedDescending + case orderedSame + case orderedAscending + } + #endif + //MARK: Comparison /// Compare `a` to `b` and return an `NSComparisonResult` indicating their order. diff --git a/Sources/Data Conversion.swift b/Sources/Data Conversion.swift index 25c6552..b74e683 100644 --- a/Sources/Data Conversion.swift +++ b/Sources/Data Conversion.swift @@ -6,7 +6,9 @@ // Copyright © 2016-2017 Károly Lőrentey. // +#if canImport(Foundation) import Foundation +#endif extension BigUInt { //MARK: NSData Conversion @@ -43,70 +45,52 @@ extension BigUInt { assert(c == 0 && word == 0 && index == -1) } - - /// Initializes an integer from the bits stored inside a piece of `Data`. - /// The data is assumed to be in network (big-endian) byte order. - public init(_ data: Data) { - // This assumes Word is binary. + /// Return a `UnsafeRawBufferPointer` buffer that contains the base-256 representation of this integer, in network (big-endian) byte order. + public func serializeToBuffer() -> UnsafeRawBufferPointer { + // This assumes Digit is binary. precondition(Word.bitWidth % 8 == 0) - self.init() + let byteCount = (self.bitWidth + 7) / 8 - let length = data.count - guard length > 0 else { return } - let bytesPerDigit = Word.bitWidth / 8 - var index = length / bytesPerDigit - var c = bytesPerDigit - length % bytesPerDigit - if c == bytesPerDigit { - c = 0 - index -= 1 - } - let word: Word = data.withUnsafeBytes { buffPtr in - var word: Word = 0 - let p = buffPtr.bindMemory(to: UInt8.self) - for byte in p { - word <<= 8 - word += Word(byte) - c += 1 - if c == bytesPerDigit { - self[index] = word - index -= 1 - c = 0 - word = 0 + let buffer = UnsafeMutableBufferPointer.allocate(capacity: byteCount) + + guard byteCount > 0 else { return UnsafeRawBufferPointer(start: buffer.baseAddress, count: 0) } + + var i = byteCount - 1 + for var word in self.words { + for _ in 0 ..< Word.bitWidth / 8 { + buffer[i] = UInt8(word & 0xFF) + word >>= 8 + if i == 0 { + assert(word == 0) + break } + i -= 1 } - return word } - assert(c == 0 && word == 0 && index == -1) + return UnsafeRawBufferPointer(start: buffer.baseAddress, count: byteCount) + } + + #if canImport(Foundation) + /// Initializes an integer from the bits stored inside a piece of `Data`. + /// The data is assumed to be in network (big-endian) byte order. + public init(_ data: Data) { + self = data.withUnsafeBytes({ buffer in + BigUInt(buffer) + }) } /// Return a `Data` value that contains the base-256 representation of this integer, in network (big-endian) byte order. public func serialize() -> Data { - // This assumes Digit is binary. - precondition(Word.bitWidth % 8 == 0) - - let byteCount = (self.bitWidth + 7) / 8 + let buffer = serializeToBuffer() + defer { buffer.deallocate() } + guard + let pointer = buffer.baseAddress.map(UnsafeMutableRawPointer.init(mutating:)) + else { return Data() } - guard byteCount > 0 else { return Data() } - - var data = Data(count: byteCount) - data.withUnsafeMutableBytes { buffPtr in - let p = buffPtr.bindMemory(to: UInt8.self) - var i = byteCount - 1 - for var word in self.words { - for _ in 0 ..< Word.bitWidth / 8 { - p[i] = UInt8(word & 0xFF) - word >>= 8 - if i == 0 { - assert(word == 0) - break - } - i -= 1 - } - } - } - return data + return Data(bytes: pointer, count: buffer.count) } + #endif } extension BigInt { @@ -133,47 +117,47 @@ extension BigInt { self.magnitude = BigUInt(UnsafeRawBufferPointer(rebasing: buffer.dropFirst(1))) } - + + /// Return a `Data` value that contains the base-256 representation of this integer, in network (big-endian) byte order and a prepended byte to indicate the sign (0 for positive, 1 for negative) + public func serializeToBuffer() -> UnsafeRawBufferPointer { + // Create a data object for the magnitude portion of the BigInt + let magnitudeBuffer = self.magnitude.serializeToBuffer() + + // Similar to BigUInt, a value of 0 should return an empty buffer + guard magnitudeBuffer.count > 0 else { return magnitudeBuffer } + + // Create a new buffer for the signed BigInt value + let newBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: magnitudeBuffer.count + 1, alignment: 8) + let magnitudeSection = UnsafeMutableRawBufferPointer(rebasing: newBuffer[1...]) + magnitudeSection.copyBytes(from: magnitudeBuffer) + magnitudeBuffer.deallocate() + + // The first byte should be 0 for a positive value, or 1 for a negative value + // i.e., the sign bit is the LSB + newBuffer[0] = self.sign == .plus ? 0 : 1 + + return UnsafeRawBufferPointer(start: newBuffer.baseAddress, count: newBuffer.count) + } + + #if canImport(Foundation) /// Initializes an integer from the bits stored inside a piece of `Data`. /// The data is assumed to be in network (big-endian) byte order with a first /// byte to represent the sign (0 for positive, 1 for negative) public init(_ data: Data) { - // This assumes Word is binary. - // This is the same assumption made when initializing BigUInt from Data - precondition(Word.bitWidth % 8 == 0) - - self.init() - - // Serialized data for a BigInt should contain at least 2 bytes: one representing - // the sign, and another for the non-zero magnitude. Zero is represented by an - // empty Data struct, and negative zero is not supported. - guard data.count > 1, let firstByte = data.first else { return } - - // The first byte gives the sign - // This byte is compared to a bitmask to allow additional functionality to be added - // to this byte in the future. - self.sign = firstByte & 0b1 == 0 ? .plus : .minus - - // The remaining bytes are read and stored as the magnitude - self.magnitude = BigUInt(data.dropFirst(1)) + self = data.withUnsafeBytes({ buffer in + BigInt(buffer) + }) } /// Return a `Data` value that contains the base-256 representation of this integer, in network (big-endian) byte order and a prepended byte to indicate the sign (0 for positive, 1 for negative) public func serialize() -> Data { - // Create a data object for the magnitude portion of the BigInt - let magnitudeData = self.magnitude.serialize() - - // Similar to BigUInt, a value of 0 should return an initialized, empty Data struct - guard magnitudeData.count > 0 else { return magnitudeData } - - // Create a new Data struct for the signed BigInt value - var data = Data(capacity: magnitudeData.count + 1) - - // The first byte should be 0 for a positive value, or 1 for a negative value - // i.e., the sign bit is the LSB - data.append(self.sign == .plus ? 0 : 1) - - data.append(magnitudeData) - return data + let buffer = serializeToBuffer() + defer { buffer.deallocate() } + guard + let pointer = buffer.baseAddress.map(UnsafeMutableRawPointer.init(mutating:)) + else { return Data() } + + return Data(bytes: pointer, count: buffer.count) } + #endif } diff --git a/Sources/Floating Point Conversion.swift b/Sources/Floating Point Conversion.swift index 6c2395a..8c4b767 100644 --- a/Sources/Floating Point Conversion.swift +++ b/Sources/Floating Point Conversion.swift @@ -6,6 +6,10 @@ // Copyright © 2016-2017 Károly Lőrentey. // +#if canImport(Foundation) +import Foundation +#endif + extension BigUInt { public init?(exactly source: T) { guard source.isFinite else { return nil } @@ -22,23 +26,69 @@ extension BigUInt { public init(_ source: T) { self.init(exactly: source.rounded(.towardZero))! } + + #if canImport(Foundation) + public init?(exactly source: Decimal) { + guard source.isFinite else { return nil } + guard !source.isZero else { self = 0; return } + guard source.sign == .plus else { return nil } + assert(source.floatingPointClass == .positiveNormal) + guard source.exponent >= 0 else { return nil } + let intMaxD = Decimal(UInt.max) + let intMaxB = BigUInt(UInt.max) + var start = BigUInt() + var value = source + while value >= intMaxD { + start += intMaxB + value -= intMaxD + } + start += BigUInt((value as NSNumber).uintValue) + self = start + } + + public init?(truncating source: Decimal) { + guard source.isFinite else { return nil } + guard !source.isZero else { self = 0; return } + guard source.sign == .plus else { return nil } + assert(source.floatingPointClass == .positiveNormal) + let intMaxD = Decimal(UInt.max) + let intMaxB = BigUInt(UInt.max) + var start = BigUInt() + var value = source + while value >= intMaxD { + start += intMaxB + value -= intMaxD + } + start += BigUInt((value as NSNumber).uintValue) + self = start + } + #endif } extension BigInt { public init?(exactly source: T) { - switch source.sign{ - case .plus: - guard let magnitude = BigUInt(exactly: source) else { return nil } - self = BigInt(sign: .plus, magnitude: magnitude) - case .minus: - guard let magnitude = BigUInt(exactly: -source) else { return nil } - self = BigInt(sign: .minus, magnitude: magnitude) - } + guard let magnitude = BigUInt(exactly: source.magnitude) else { return nil } + let sign = BigInt.Sign(source.sign) + self.init(sign: sign, magnitude: magnitude) } public init(_ source: T) { self.init(exactly: source.rounded(.towardZero))! } + + #if canImport(Foundation) + public init?(exactly source: Decimal) { + guard let magnitude = BigUInt(exactly: source.magnitude) else { return nil } + let sign = BigInt.Sign(source.sign) + self.init(sign: sign, magnitude: magnitude) + } + + public init?(truncating source: Decimal) { + guard let magnitude = BigUInt(truncating: source.magnitude) else { return nil } + let sign = BigInt.Sign(source.sign) + self.init(sign: sign, magnitude: magnitude) + } + #endif } extension BinaryFloatingPoint where RawExponent: FixedWidthInteger, RawSignificand: FixedWidthInteger { @@ -71,3 +121,14 @@ extension BinaryFloatingPoint where RawExponent: FixedWidthInteger, RawSignifica self.init(BigInt(sign: .plus, magnitude: value)) } } + +extension BigInt.Sign { + public init(_ sign: FloatingPointSign) { + switch sign { + case .plus: + self = .plus + case .minus: + self = .minus + } + } +} diff --git a/Sources/String Conversion.swift b/Sources/String Conversion.swift index fd1b47f..cef9f30 100644 --- a/Sources/String Conversion.swift +++ b/Sources/String Conversion.swift @@ -221,12 +221,27 @@ extension BigInt: CustomStringConvertible { } } +extension BigUInt: CustomDebugStringConvertible { + /// Return the decimal representation of this integer. + public var debugDescription: String { + let text = String(self) + return text + " (\(self.bitWidth) bits)" + } +} + +extension BigInt: CustomDebugStringConvertible { + /// Return the decimal representation of this integer. + public var debugDescription: String { + let text = String(self) + return text + " (\(self.magnitude.bitWidth) bits)" + } +} + extension BigUInt: CustomPlaygroundDisplayConvertible { /// Return the playground quick look representation of this integer. public var playgroundDescription: Any { - let text = String(self) - return text + " (\(self.bitWidth) bits)" + debugDescription } } @@ -234,7 +249,6 @@ extension BigInt: CustomPlaygroundDisplayConvertible { /// Return the playground quick look representation of this integer. public var playgroundDescription: Any { - let text = String(self) - return text + " (\(self.magnitude.bitWidth) bits)" + debugDescription } } diff --git a/Tests/BigIntTests/BigIntTests.swift b/Tests/BigIntTests/BigIntTests.swift index 51e46e3..c46c022 100644 --- a/Tests/BigIntTests/BigIntTests.swift +++ b/Tests/BigIntTests/BigIntTests.swift @@ -8,6 +8,7 @@ import XCTest @testable import BigInt +import Foundation class BigIntTests: XCTestCase { typealias Word = BigInt.Word @@ -90,7 +91,43 @@ class BigIntTests: XCTestCase { XCTAssertEqual(BigInt(truncatingIfNeeded: -42), -42) XCTAssertEqual(BigInt(truncatingIfNeeded: 42), 42) } - + + func testInit_Decimal() throws { + XCTAssertEqual(BigInt(exactly: Decimal(0)), 0) + XCTAssertEqual(BigInt(exactly: Decimal(Double.nan)), nil) + XCTAssertEqual(BigInt(exactly: Decimal(10)), 10) + XCTAssertEqual(BigInt(exactly: Decimal(1000)), 1000) + XCTAssertEqual(BigInt(exactly: Decimal(1000.1)), nil) + XCTAssertEqual(BigInt(exactly: Decimal(1000.9)), nil) + XCTAssertEqual(BigInt(exactly: Decimal(1001.5)), nil) + XCTAssertEqual(BigInt(exactly: Decimal(UInt.max) + 5), "18446744073709551620") + XCTAssertEqual(BigInt(exactly: (Decimal(UInt.max) + 5.5)), nil) + XCTAssertEqual(BigInt(truncating: Decimal(0)), 0) + XCTAssertEqual(BigInt(truncating: Decimal(Double.nan)), nil) + XCTAssertEqual(BigInt(truncating: Decimal(10)), 10) + XCTAssertEqual(BigInt(truncating: Decimal(1000)), 1000) + XCTAssertEqual(BigInt(truncating: Decimal(1000.1)), 1000) + XCTAssertEqual(BigInt(truncating: Decimal(1000.9)), 1000) + XCTAssertEqual(BigInt(truncating: Decimal(1001.5)), 1001) + XCTAssertEqual(BigInt(truncating: Decimal(UInt.max) + 5), "18446744073709551620") + XCTAssertEqual(BigInt(truncating: (Decimal(UInt.max) + 5.5)), "18446744073709551620") + + XCTAssertEqual(BigInt(exactly: -Decimal(10)), -10) + XCTAssertEqual(BigInt(exactly: -Decimal(1000)), -1000) + XCTAssertEqual(BigInt(exactly: -Decimal(1000.1)), nil) + XCTAssertEqual(BigInt(exactly: -Decimal(1000.9)), nil) + XCTAssertEqual(BigInt(exactly: -Decimal(1001.5)), nil) + XCTAssertEqual(BigInt(exactly: -(Decimal(UInt.max) + 5)), "-18446744073709551620") + XCTAssertEqual(BigInt(exactly: -(Decimal(UInt.max) + 5.5)), nil) + XCTAssertEqual(BigInt(truncating: -Decimal(10)), -10) + XCTAssertEqual(BigInt(truncating: -Decimal(1000)), -1000) + XCTAssertEqual(BigInt(truncating: -Decimal(1000.1)), -1000) + XCTAssertEqual(BigInt(truncating: -Decimal(1000.9)), -1000) + XCTAssertEqual(BigInt(truncating: -Decimal(1001.5)), -1001) + XCTAssertEqual(BigInt(truncating: -(Decimal(UInt.max) + 5)), "-18446744073709551620") + XCTAssertEqual(BigInt(truncating: -(Decimal(UInt.max) + 5.5)), "-18446744073709551620") + } + func testInit_Buffer() { func test(_ b: BigInt, _ d: Array, file: StaticString = #file, line: UInt = #line) { d.withUnsafeBytes { buffer in diff --git a/Tests/BigIntTests/BigUIntTests.swift b/Tests/BigIntTests/BigUIntTests.swift index f839c29..08412ac 100644 --- a/Tests/BigIntTests/BigUIntTests.swift +++ b/Tests/BigIntTests/BigUIntTests.swift @@ -156,7 +156,43 @@ class BigUIntTests: XCTestCase { check(BigUInt(Double(sign: .plus, exponent: 2 * Word.bitWidth, significand: 1.0)), nil, [0, 0, 1]) } - + + func testInit_Decimal() throws { + XCTAssertEqual(BigUInt(exactly: Decimal(0)), 0) + XCTAssertEqual(BigUInt(exactly: Decimal(Double.nan)), nil) + XCTAssertEqual(BigUInt(exactly: Decimal(10)), 10) + XCTAssertEqual(BigUInt(exactly: Decimal(1000)), 1000) + XCTAssertEqual(BigUInt(exactly: Decimal(1000.1)), nil) + XCTAssertEqual(BigUInt(exactly: Decimal(1000.9)), nil) + XCTAssertEqual(BigUInt(exactly: Decimal(1001.5)), nil) + XCTAssertEqual(BigUInt(exactly: Decimal(UInt.max) + 5), "18446744073709551620") + XCTAssertEqual(BigUInt(exactly: (Decimal(UInt.max) + 5.5)), nil) + XCTAssertEqual(BigUInt(truncating: Decimal(0)), 0) + XCTAssertEqual(BigUInt(truncating: Decimal(Double.nan)), nil) + XCTAssertEqual(BigUInt(truncating: Decimal(10)), 10) + XCTAssertEqual(BigUInt(truncating: Decimal(1000)), 1000) + XCTAssertEqual(BigUInt(truncating: Decimal(1000.1)), 1000) + XCTAssertEqual(BigUInt(truncating: Decimal(1000.9)), 1000) + XCTAssertEqual(BigUInt(truncating: Decimal(1001.5)), 1001) + XCTAssertEqual(BigUInt(truncating: Decimal(UInt.max) + 5), "18446744073709551620") + XCTAssertEqual(BigUInt(truncating: (Decimal(UInt.max) + 5.5)), "18446744073709551620") + + XCTAssertEqual(BigUInt(exactly: -Decimal(10)), nil) + XCTAssertEqual(BigUInt(exactly: -Decimal(1000)), nil) + XCTAssertEqual(BigUInt(exactly: -Decimal(1000.1)), nil) + XCTAssertEqual(BigUInt(exactly: -Decimal(1000.9)), nil) + XCTAssertEqual(BigUInt(exactly: -Decimal(1001.5)), nil) + XCTAssertEqual(BigUInt(exactly: -Decimal(UInt.max) + 5), nil) + XCTAssertEqual(BigUInt(exactly: -(Decimal(UInt.max) + 5.5)), nil) + XCTAssertEqual(BigUInt(truncating: -Decimal(10)), nil) + XCTAssertEqual(BigUInt(truncating: -Decimal(1000)), nil) + XCTAssertEqual(BigUInt(truncating: -Decimal(1000.1)), nil) + XCTAssertEqual(BigUInt(truncating: -Decimal(1000.9)), nil) + XCTAssertEqual(BigUInt(truncating: -Decimal(1001.5)), nil) + XCTAssertEqual(BigUInt(truncating: -Decimal(UInt.max) + 5), nil) + XCTAssertEqual(BigUInt(truncating: -(Decimal(UInt.max) + 5.5)), nil) + } + func testInit_Buffer() { func test(_ b: BigUInt, _ d: Array, file: StaticString = #file, line: UInt = #line) { d.withUnsafeBytes { buffer in