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

Added improved LosslessStringConvertible init() with "^=" bug fix. #261

Closed
wants to merge 4 commits into from
Closed
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
128 changes: 90 additions & 38 deletions Sources/BigIntModule/BigInt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ public struct BigInt: SignedInteger {
internal var _isNegative: Bool {
words[words.endIndex - 1] > Int.max
}

private static let _digits: [BigInt] = (0 ... 36).map {
BigInt(_uncheckedWords: [UInt(bitPattern: $0)])
}

private static let _digitRadix = BigInt(_uncheckedWords: [0, 1])
}

// MARK: - Basic Behaviors
Expand Down Expand Up @@ -96,39 +90,73 @@ extension BigInt: LosslessStringConvertible {
public init?(_ description: String) {
self.init(description, radix: 10)
}



////////////////////////////////////////////////////////////////////////////
///
/// NEW CODE STARTS

private static func _pow(_ lhs: UInt, _ rhs: UInt) -> UInt {
// guard rhs>=0 else { return 0 } /* lhs ** (-rhs) = 0 */
var lexp = rhs
var x = lhs
var y = UInt(1)
while lexp > 0 {
if !lexp.isMultiple(of: 2) { y*=x }
lexp >>= 1
if lexp > 0 { x=x*x } // could be 2X faster with squared function
}
return y
}

private func _maxDigits(forRadix radix:Int) -> Int {
switch radix {
case 2: return 62
case 8: return 20
case 10: return 18
case 16: return 14
default: return 11 // safe but not optimal for other radices
}
}

/// Speeds the time to initialize a BigInt by about a factor of 16 for
/// the test case of the string for 512! using radix 10. A radix 36 test
/// was 10X faster. BTW, the factorial test code was sped up by almost 2
/// times (the string to number code accounted for a large part of the
/// total time).
public init?<T>(_ description: T, radix: Int = 10) where T: StringProtocol {
precondition(2 ... 36 ~= radix, "Radix not in range 2 ... 36")

self = 0

let isNegative = description.hasPrefix("-")
let hasPrefix = isNegative || description.hasPrefix("+")
let utf8 = description.utf8.dropFirst(hasPrefix ? 1 : 0)
guard !utf8.isEmpty else { return nil }

for var byte in utf8 {
switch byte {
case UInt8(ascii: "0") ... UInt8(ascii: "9"):
byte -= UInt8(ascii: "0")
case UInt8(ascii: "A") ... UInt8(ascii: "Z"):
byte -= UInt8(ascii: "A")
byte += 10
case UInt8(ascii: "a") ... UInt8(ascii: "z"):
byte -= UInt8(ascii: "a")
byte += 10
default:
var str = description.dropFirst(hasPrefix ? 1 : 0)
guard !str.isEmpty else { return nil }

/// Main speed-up is due to converting chunks of string via
/// the Int64() initializer instead of a character at a time.
/// We also get free radix digit checks.
let maxDigits = _maxDigits(forRadix: radix)
while !str.isEmpty {
let block = str.prefix(maxDigits)
let size = block.count
str.removeFirst(size)
if let word = UInt(block, radix: radix) {
self *= BigInt(Self._pow(UInt(radix), UInt(size)))
self += BigInt(word)
} else {
return nil
}
guard byte < radix else { return nil }
self *= BigInt._digits[radix]
self += BigInt._digits[Int(byte)]
}

if isNegative {
self.negate()
}
}

/// NEW CODE ENDS
///
//////////////////////////////////////////////////////////////////////////////////////////////////
}

extension BigInt: Decodable {
Expand Down Expand Up @@ -158,8 +186,8 @@ extension BigInt: Encodable {
extension BigInt: ExpressibleByIntegerLiteral {

public init(integerLiteral value: Int) {
if value >= 0, value < BigInt._digits.count {
self = BigInt._digits[value]
if value >= 0, value <= UInt.max {
words = [UInt(value)] // No need for a table lookup here
} else {
words = [UInt(bitPattern: value)]
}
Expand Down Expand Up @@ -310,6 +338,7 @@ extension BigInt: SignedNumeric {

public mutating func negate() {
var isOverflow = true
let isNegative = self._isNegative
for i in 0 ..< words.count {
if isOverflow {
(words[i], isOverflow) = (~words[i]).addingReportingOverflow(1)
Expand All @@ -319,6 +348,12 @@ extension BigInt: SignedNumeric {
}

BigInt._dropExcessWords(words: &words)
if self != Self.zero && self._isNegative == isNegative {
// Corner case where numbers like `0x8000000000000000 ... 0000`
// remain unchanged after negation so we make sure any negative
// numbers are truly negated into positive numbers
if isNegative { words.append(0) } // make the number positive
}
}

@inlinable
Expand Down Expand Up @@ -372,8 +407,8 @@ extension BigInt: BinaryInteger {
}

public init<T>(_ source: T) where T: BinaryInteger {
if source >= 0, source < BigInt._digits.count {
self = BigInt._digits[Int(source)]
if source >= 0 && source < Int.max {
words = [UInt(source)]
} else {
words = Words(source.words)
if source > 0 && source.words[source.words.endIndex - 1] > Int.max {
Expand All @@ -390,7 +425,7 @@ extension BigInt: BinaryInteger {
}

public init<T>(truncatingIfNeeded source: T) where T: BinaryInteger {
words = Words(source.words)
self.init(source) //words = Words(source.words)
}

public var bitWidth: Int { words.count * UInt.bitWidth }
Expand Down Expand Up @@ -422,7 +457,6 @@ extension BigInt: BinaryInteger {
@inlinable
public static func % (lhs: BigInt, rhs: BigInt) -> BigInt {
let (_, result) = _div(lhs: lhs, rhs: rhs)

return result
}

Expand Down Expand Up @@ -464,7 +498,7 @@ extension BigInt: BinaryInteger {
BigInt._signExtend(lhsWords: &lhs.words, rhsWords: &rhsWords)

for i in 0 ..< rhsWords.count {
lhs.words[i] &= rhsWords[i]
lhs.words[i] ^= rhsWords[i]
}

BigInt._dropExcessWords(words: &lhs.words)
Expand Down Expand Up @@ -634,11 +668,17 @@ extension BigInt {
/// See _The Art of Computer Programming_ volume 2 by Donald Knuth, Section 4.3.1: The Classical Algorithms
@usableFromInline
internal static func _div(lhs: BigInt, rhs: BigInt) -> (quotient: BigInt, remainder: BigInt) {
precondition(rhs != _digits[0], "Division by zero error!")
precondition(rhs != 0, "Division by zero error!")

// Speed up single-word divisions
if lhs.words.count == 1, rhs.words.count == 1 {
let (quot, rem) = Int(bitPattern: lhs.words[0]).quotientAndRemainder(dividingBy: Int(bitPattern: rhs.words[0]))
return (BigInt(_uncheckedWords: [UInt(bitPattern: quot)]), BigInt(_uncheckedWords: [UInt(bitPattern: rem)]))
// check for corner case that causes overflow: Int.min / -1
let lhsInt = Int(bitPattern: lhs.words[0])
let rhsInt = Int(bitPattern: rhs.words[0])
if !(lhsInt == Int.min && rhsInt == -1) {
let (quot, rem) = lhsInt.quotientAndRemainder(dividingBy: rhsInt)
return (BigInt(_uncheckedWords: [UInt(bitPattern: quot)]), BigInt(_uncheckedWords: [UInt(bitPattern: rem)]))
}
}

let lhsIsNeg = lhs._isNegative
Expand All @@ -663,7 +703,13 @@ extension BigInt {
}

BigInt._dropExcessWords(words: &quot)
return (quotient: BigInt(_uncheckedWords: quot), remainder: BigInt(r))
// signs are based on the Int definitions
switch (lhsIsNeg, rhsIsNeg) {
case (false, true): return (-BigInt(_uncheckedWords: quot), BigInt(r))
case (false, false): return ( BigInt(_uncheckedWords: quot), BigInt(r))
case (true, false): return (-BigInt(_uncheckedWords: quot), -BigInt(r))
case (true, true): return ( BigInt(_uncheckedWords: quot), -BigInt(r))
}
}

while rhsWords[rhsWords.endIndex - 1] == 0 {
Expand Down Expand Up @@ -770,8 +816,14 @@ extension BigInt {

BigInt._dropExcessWords(words: &quot)
BigInt._dropExcessWords(words: &rem)

return (BigInt(_uncheckedWords: quot), BigInt(_uncheckedWords: rem))

// signs are based on the Int definitions
switch (lhsIsNeg, rhsIsNeg) {
case (false, true): return (-BigInt(_uncheckedWords: quot), BigInt(_uncheckedWords: rem))
case (false, false): return ( BigInt(_uncheckedWords: quot), BigInt(_uncheckedWords: rem))
case (true, false): return (-BigInt(_uncheckedWords: quot), -BigInt(_uncheckedWords: rem))
case (true, true): return ( BigInt(_uncheckedWords: quot), -BigInt(_uncheckedWords: rem))
}
}

private static func _signExtend(lhsWords: inout Words, rhsWords: inout Words) {
Expand Down
Loading