Skip to content

Commit a7ee114

Browse files
authored
Decimal and Foundation (#122)
* add CustomDebugStringConvertible * reduce Foundation dependency * if foundation is available, add Decimal functionality * more concise floating point init * dry up data initialization
1 parent 793a7fa commit a7ee114

6 files changed

+242
-100
lines changed

Sources/Comparable.swift

+10
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,19 @@
66
// Copyright © 2016-2017 Károly Lőrentey.
77
//
88

9+
#if canImport(Foundation)
910
import Foundation
11+
#endif
1012

1113
extension BigUInt: Comparable {
14+
#if !canImport(Foundation)
15+
public enum ComparisonResult: Sendable, Comparable, Hashable {
16+
case orderedDescending
17+
case orderedSame
18+
case orderedAscending
19+
}
20+
#endif
21+
1222
//MARK: Comparison
1323

1424
/// Compare `a` to `b` and return an `NSComparisonResult` indicating their order.

Sources/Data Conversion.swift

+70-86
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
// Copyright © 2016-2017 Károly Lőrentey.
77
//
88

9+
#if canImport(Foundation)
910
import Foundation
11+
#endif
1012

1113
extension BigUInt {
1214
//MARK: NSData Conversion
@@ -43,70 +45,52 @@ extension BigUInt {
4345
assert(c == 0 && word == 0 && index == -1)
4446
}
4547

46-
47-
/// Initializes an integer from the bits stored inside a piece of `Data`.
48-
/// The data is assumed to be in network (big-endian) byte order.
49-
public init(_ data: Data) {
50-
// This assumes Word is binary.
48+
/// Return a `UnsafeRawBufferPointer` buffer that contains the base-256 representation of this integer, in network (big-endian) byte order.
49+
public func serializeToBuffer() -> UnsafeRawBufferPointer {
50+
// This assumes Digit is binary.
5151
precondition(Word.bitWidth % 8 == 0)
5252

53-
self.init()
53+
let byteCount = (self.bitWidth + 7) / 8
5454

55-
let length = data.count
56-
guard length > 0 else { return }
57-
let bytesPerDigit = Word.bitWidth / 8
58-
var index = length / bytesPerDigit
59-
var c = bytesPerDigit - length % bytesPerDigit
60-
if c == bytesPerDigit {
61-
c = 0
62-
index -= 1
63-
}
64-
let word: Word = data.withUnsafeBytes { buffPtr in
65-
var word: Word = 0
66-
let p = buffPtr.bindMemory(to: UInt8.self)
67-
for byte in p {
68-
word <<= 8
69-
word += Word(byte)
70-
c += 1
71-
if c == bytesPerDigit {
72-
self[index] = word
73-
index -= 1
74-
c = 0
75-
word = 0
55+
let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: byteCount)
56+
57+
guard byteCount > 0 else { return UnsafeRawBufferPointer(start: buffer.baseAddress, count: 0) }
58+
59+
var i = byteCount - 1
60+
for var word in self.words {
61+
for _ in 0 ..< Word.bitWidth / 8 {
62+
buffer[i] = UInt8(word & 0xFF)
63+
word >>= 8
64+
if i == 0 {
65+
assert(word == 0)
66+
break
7667
}
68+
i -= 1
7769
}
78-
return word
7970
}
80-
assert(c == 0 && word == 0 && index == -1)
71+
return UnsafeRawBufferPointer(start: buffer.baseAddress, count: byteCount)
72+
}
73+
74+
#if canImport(Foundation)
75+
/// Initializes an integer from the bits stored inside a piece of `Data`.
76+
/// The data is assumed to be in network (big-endian) byte order.
77+
public init(_ data: Data) {
78+
self = data.withUnsafeBytes({ buffer in
79+
BigUInt(buffer)
80+
})
8181
}
8282

8383
/// Return a `Data` value that contains the base-256 representation of this integer, in network (big-endian) byte order.
8484
public func serialize() -> Data {
85-
// This assumes Digit is binary.
86-
precondition(Word.bitWidth % 8 == 0)
87-
88-
let byteCount = (self.bitWidth + 7) / 8
85+
let buffer = serializeToBuffer()
86+
defer { buffer.deallocate() }
87+
guard
88+
let pointer = buffer.baseAddress.map(UnsafeMutableRawPointer.init(mutating:))
89+
else { return Data() }
8990

90-
guard byteCount > 0 else { return Data() }
91-
92-
var data = Data(count: byteCount)
93-
data.withUnsafeMutableBytes { buffPtr in
94-
let p = buffPtr.bindMemory(to: UInt8.self)
95-
var i = byteCount - 1
96-
for var word in self.words {
97-
for _ in 0 ..< Word.bitWidth / 8 {
98-
p[i] = UInt8(word & 0xFF)
99-
word >>= 8
100-
if i == 0 {
101-
assert(word == 0)
102-
break
103-
}
104-
i -= 1
105-
}
106-
}
107-
}
108-
return data
91+
return Data(bytes: pointer, count: buffer.count)
10992
}
93+
#endif
11094
}
11195

11296
extension BigInt {
@@ -133,47 +117,47 @@ extension BigInt {
133117

134118
self.magnitude = BigUInt(UnsafeRawBufferPointer(rebasing: buffer.dropFirst(1)))
135119
}
136-
120+
121+
/// 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)
122+
public func serializeToBuffer() -> UnsafeRawBufferPointer {
123+
// Create a data object for the magnitude portion of the BigInt
124+
let magnitudeBuffer = self.magnitude.serializeToBuffer()
125+
126+
// Similar to BigUInt, a value of 0 should return an empty buffer
127+
guard magnitudeBuffer.count > 0 else { return magnitudeBuffer }
128+
129+
// Create a new buffer for the signed BigInt value
130+
let newBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: magnitudeBuffer.count + 1, alignment: 8)
131+
let magnitudeSection = UnsafeMutableRawBufferPointer(rebasing: newBuffer[1...])
132+
magnitudeSection.copyBytes(from: magnitudeBuffer)
133+
magnitudeBuffer.deallocate()
134+
135+
// The first byte should be 0 for a positive value, or 1 for a negative value
136+
// i.e., the sign bit is the LSB
137+
newBuffer[0] = self.sign == .plus ? 0 : 1
138+
139+
return UnsafeRawBufferPointer(start: newBuffer.baseAddress, count: newBuffer.count)
140+
}
141+
142+
#if canImport(Foundation)
137143
/// Initializes an integer from the bits stored inside a piece of `Data`.
138144
/// The data is assumed to be in network (big-endian) byte order with a first
139145
/// byte to represent the sign (0 for positive, 1 for negative)
140146
public init(_ data: Data) {
141-
// This assumes Word is binary.
142-
// This is the same assumption made when initializing BigUInt from Data
143-
precondition(Word.bitWidth % 8 == 0)
144-
145-
self.init()
146-
147-
// Serialized data for a BigInt should contain at least 2 bytes: one representing
148-
// the sign, and another for the non-zero magnitude. Zero is represented by an
149-
// empty Data struct, and negative zero is not supported.
150-
guard data.count > 1, let firstByte = data.first else { return }
151-
152-
// The first byte gives the sign
153-
// This byte is compared to a bitmask to allow additional functionality to be added
154-
// to this byte in the future.
155-
self.sign = firstByte & 0b1 == 0 ? .plus : .minus
156-
157-
// The remaining bytes are read and stored as the magnitude
158-
self.magnitude = BigUInt(data.dropFirst(1))
147+
self = data.withUnsafeBytes({ buffer in
148+
BigInt(buffer)
149+
})
159150
}
160151

161152
/// 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)
162153
public func serialize() -> Data {
163-
// Create a data object for the magnitude portion of the BigInt
164-
let magnitudeData = self.magnitude.serialize()
165-
166-
// Similar to BigUInt, a value of 0 should return an initialized, empty Data struct
167-
guard magnitudeData.count > 0 else { return magnitudeData }
168-
169-
// Create a new Data struct for the signed BigInt value
170-
var data = Data(capacity: magnitudeData.count + 1)
171-
172-
// The first byte should be 0 for a positive value, or 1 for a negative value
173-
// i.e., the sign bit is the LSB
174-
data.append(self.sign == .plus ? 0 : 1)
175-
176-
data.append(magnitudeData)
177-
return data
154+
let buffer = serializeToBuffer()
155+
defer { buffer.deallocate() }
156+
guard
157+
let pointer = buffer.baseAddress.map(UnsafeMutableRawPointer.init(mutating:))
158+
else { return Data() }
159+
160+
return Data(bytes: pointer, count: buffer.count)
178161
}
162+
#endif
179163
}

Sources/Floating Point Conversion.swift

+69-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
// Copyright © 2016-2017 Károly Lőrentey.
77
//
88

9+
#if canImport(Foundation)
10+
import Foundation
11+
#endif
12+
913
extension BigUInt {
1014
public init?<T: BinaryFloatingPoint>(exactly source: T) {
1115
guard source.isFinite else { return nil }
@@ -22,23 +26,69 @@ extension BigUInt {
2226
public init<T: BinaryFloatingPoint>(_ source: T) {
2327
self.init(exactly: source.rounded(.towardZero))!
2428
}
29+
30+
#if canImport(Foundation)
31+
public init?(exactly source: Decimal) {
32+
guard source.isFinite else { return nil }
33+
guard !source.isZero else { self = 0; return }
34+
guard source.sign == .plus else { return nil }
35+
assert(source.floatingPointClass == .positiveNormal)
36+
guard source.exponent >= 0 else { return nil }
37+
let intMaxD = Decimal(UInt.max)
38+
let intMaxB = BigUInt(UInt.max)
39+
var start = BigUInt()
40+
var value = source
41+
while value >= intMaxD {
42+
start += intMaxB
43+
value -= intMaxD
44+
}
45+
start += BigUInt((value as NSNumber).uintValue)
46+
self = start
47+
}
48+
49+
public init?(truncating source: Decimal) {
50+
guard source.isFinite else { return nil }
51+
guard !source.isZero else { self = 0; return }
52+
guard source.sign == .plus else { return nil }
53+
assert(source.floatingPointClass == .positiveNormal)
54+
let intMaxD = Decimal(UInt.max)
55+
let intMaxB = BigUInt(UInt.max)
56+
var start = BigUInt()
57+
var value = source
58+
while value >= intMaxD {
59+
start += intMaxB
60+
value -= intMaxD
61+
}
62+
start += BigUInt((value as NSNumber).uintValue)
63+
self = start
64+
}
65+
#endif
2566
}
2667

2768
extension BigInt {
2869
public init?<T: BinaryFloatingPoint>(exactly source: T) {
29-
switch source.sign{
30-
case .plus:
31-
guard let magnitude = BigUInt(exactly: source) else { return nil }
32-
self = BigInt(sign: .plus, magnitude: magnitude)
33-
case .minus:
34-
guard let magnitude = BigUInt(exactly: -source) else { return nil }
35-
self = BigInt(sign: .minus, magnitude: magnitude)
36-
}
70+
guard let magnitude = BigUInt(exactly: source.magnitude) else { return nil }
71+
let sign = BigInt.Sign(source.sign)
72+
self.init(sign: sign, magnitude: magnitude)
3773
}
3874

3975
public init<T: BinaryFloatingPoint>(_ source: T) {
4076
self.init(exactly: source.rounded(.towardZero))!
4177
}
78+
79+
#if canImport(Foundation)
80+
public init?(exactly source: Decimal) {
81+
guard let magnitude = BigUInt(exactly: source.magnitude) else { return nil }
82+
let sign = BigInt.Sign(source.sign)
83+
self.init(sign: sign, magnitude: magnitude)
84+
}
85+
86+
public init?(truncating source: Decimal) {
87+
guard let magnitude = BigUInt(truncating: source.magnitude) else { return nil }
88+
let sign = BigInt.Sign(source.sign)
89+
self.init(sign: sign, magnitude: magnitude)
90+
}
91+
#endif
4292
}
4393

4494
extension BinaryFloatingPoint where RawExponent: FixedWidthInteger, RawSignificand: FixedWidthInteger {
@@ -71,3 +121,14 @@ extension BinaryFloatingPoint where RawExponent: FixedWidthInteger, RawSignifica
71121
self.init(BigInt(sign: .plus, magnitude: value))
72122
}
73123
}
124+
125+
extension BigInt.Sign {
126+
public init(_ sign: FloatingPointSign) {
127+
switch sign {
128+
case .plus:
129+
self = .plus
130+
case .minus:
131+
self = .minus
132+
}
133+
}
134+
}

Sources/String Conversion.swift

+18-4
Original file line numberDiff line numberDiff line change
@@ -221,20 +221,34 @@ extension BigInt: CustomStringConvertible {
221221
}
222222
}
223223

224+
extension BigUInt: CustomDebugStringConvertible {
225+
/// Return the decimal representation of this integer.
226+
public var debugDescription: String {
227+
let text = String(self)
228+
return text + " (\(self.bitWidth) bits)"
229+
}
230+
}
231+
232+
extension BigInt: CustomDebugStringConvertible {
233+
/// Return the decimal representation of this integer.
234+
public var debugDescription: String {
235+
let text = String(self)
236+
return text + " (\(self.magnitude.bitWidth) bits)"
237+
}
238+
}
239+
224240
extension BigUInt: CustomPlaygroundDisplayConvertible {
225241

226242
/// Return the playground quick look representation of this integer.
227243
public var playgroundDescription: Any {
228-
let text = String(self)
229-
return text + " (\(self.bitWidth) bits)"
244+
debugDescription
230245
}
231246
}
232247

233248
extension BigInt: CustomPlaygroundDisplayConvertible {
234249

235250
/// Return the playground quick look representation of this integer.
236251
public var playgroundDescription: Any {
237-
let text = String(self)
238-
return text + " (\(self.magnitude.bitWidth) bits)"
252+
debugDescription
239253
}
240254
}

0 commit comments

Comments
 (0)