Skip to content

Commit

Permalink
Decimal and Foundation (#122)
Browse files Browse the repository at this point in the history
* add CustomDebugStringConvertible

* reduce Foundation dependency

* if foundation is available, add Decimal functionality

* more concise floating point init

* dry up data initialization
  • Loading branch information
mredig authored Oct 31, 2024
1 parent 793a7fa commit a7ee114
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 100 deletions.
10 changes: 10 additions & 0 deletions Sources/Comparable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
156 changes: 70 additions & 86 deletions Sources/Data Conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
// Copyright © 2016-2017 Károly Lőrentey.
//

#if canImport(Foundation)
import Foundation
#endif

extension BigUInt {
//MARK: NSData Conversion
Expand Down Expand Up @@ -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<UInt8>.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 {
Expand All @@ -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
}
77 changes: 69 additions & 8 deletions Sources/Floating Point Conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
// Copyright © 2016-2017 Károly Lőrentey.
//

#if canImport(Foundation)
import Foundation
#endif

extension BigUInt {
public init?<T: BinaryFloatingPoint>(exactly source: T) {
guard source.isFinite else { return nil }
Expand All @@ -22,23 +26,69 @@ extension BigUInt {
public init<T: BinaryFloatingPoint>(_ 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?<T: BinaryFloatingPoint>(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<T: BinaryFloatingPoint>(_ 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 {
Expand Down Expand Up @@ -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
}
}
}
22 changes: 18 additions & 4 deletions Sources/String Conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,20 +221,34 @@ 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
}
}

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
}
}
Loading

0 comments on commit a7ee114

Please sign in to comment.