Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decimal and Foundation #122

Merged
merged 5 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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