Arbitrary-precision arithmetic for Swift, in Swift — with no dependencies.
import BigNum
BigInt(2).power(256) // 115792089237316195423570985008687907853269984665640564039457584007913129639936
BigInt(3).power(BigInt(1) << 100, mod: 1000000007) // 870513414 -- Python's pow(b, e, m)
1.over(3) + 1.over(6) // (1/2) -- `over` turns two integers into a fraction
BigRat(1,3) + BigRat(1,3) + BigRat(1,3) == 1 // true. Not "true to 17 digits".
BigFloat.sqrt(2) // 1.414213562373095048801688724209698078569
BigRat.exp(1, precision:256) // e to 256 bitsswift build fetches nothing. The whole package is this repository plus the Swift
standard library and the platform's libm.
| Type | Is | Use it when |
|---|---|---|
BigInt |
Arbitrary-precision signed integer, two's complement | Integers that must not overflow |
BigUInt |
Its unsigned counterpart | Magnitudes, bit patterns wider than 64 |
BigRat |
Exact rational, BigInt over BigInt |
You need 1/3 + 1/3 + 1/3 to be exactly 1 |
BigFloat |
Binary floating point with an arbitrary mantissa | You need many digits, but bounded storage |
BigInt and BigUInt are ordinary SignedInteger and UnsignedInteger
conformances, so they behave the way Int and UInt do — including &, |,
^, ~ and an arithmetic >>. BigRat and BigFloat are FloatingPoint, and
both conform to Real, so the whole of <math.h> is available on them as static
functions.
BigRat and BigFloat divide the same job differently. A BigRat is exact:
it is a fraction, and it stays one, so its numerator and denominator grow without
bound as you compute. A BigFloat keeps a fixed number of significant bits, so
its storage stays put and its arithmetic rounds. The one-line version:
BigRat(1)/BigRat(3) * 3 == 1 // true -- exact
BigFloat(1)/BigFloat(3) * 3 == 1 // false -- rounded to `precision` bitsover(_:) is the idiomatic way to build a rational. It reads as the fraction bar —
a.over(b) is a over b — and it is defined on the integers, so you rarely need
to name a rational type at all:
1.over(3) // (1/3) -- an IntRat, because 1 is an Int
BigInt(1).over(3) // (1/3) -- a BigRat, because the numerator is a BigInt
Int8(1).over(2) // (1/2) -- a FixedWidthRational<Int8>
6.over(4) // (3/2) -- reduced on construction
6.over(-4) // (-3/2) -- the sign moves to the numeratorThe numerator's type picks the rational's type. An Int gives you an IntRat,
a BigInt gives you a BigRat, and any other FixedWidthRationalElement gives
you the FixedWidthRational over it. So over is how you choose between bounded
and unbounded arithmetic — by choosing what you call it on:
1.over(3) + 1.over(6) // (1/2), in two Ints
BigInt(1).over(3) + BigInt(1).over(6) // (1/2), in two BigInts that can growBecause they are ordinary fractions, the special values come out of the arithmetic rather than from anywhere special:
BigInt(1).over(0) // (1/0) -- infinity
BigInt(0).over(0) // (0/0) -- NaN
BigInt(3260954456333195553).over(2305843009213693952).toDouble() // 1.4142135623730951
BigRat.sqrt(2).toDouble() // the same DoubleOn a rational rather than an integer, over divides — the same reading of the
fraction bar, one level up:
BigRat(1,2).over(BigRat(1,3)) // (3/2), the same as `/`
BigRat(1,2).over(BigInt(3)) // (1/6), dividing by a bare numerator typeEvery lossy operation takes an optional precision: in bits. Omit it and the
type's precision static is used, which starts at 128:
BigFloat.sqrt(2) // 1.414213562373095048801688724209698078569
BigFloat.sqrt(2, precision:32) // 1.41421356215141713619
BigFloat.sqrt(2, precision:256) // 1.414213562373095048801688724209698078569671875376948073176679737990732478462102
BigFloat.precision = 256 // or move the default
BigFloat.sqrt(2) // now 256 bitsNote the 32-bit answer above: it is the exact decimal expansion of a value that is only accurate to 32 bits, so it stops agreeing with √2 after about ten digits. Precision bounds the error, not the number of digits printed.
Unlike Double, neither type overflows where the answer exists:
Double.exp(1000) // inf
BigFloat.exp(1000) // 197007111401704699388887 ... and 411 more digitstoString(_:radix:) renders three ways, and description and debugDescription
are built from it:
let q = BigRat.sqrt(2)
q.toString() // +1.414213562373095048801688724209698078569
q.toString(.fraction) // (+240615969168004511545033772477625056927/170141183460469231731687303715884105728)
q.toString(.exponent) // +0x1.6a09e667f3bcc908b2fb1366ea957d3ep0
q.toString(.fraction, radix:16) // the same ratio in hex -- what a BigRat debugs as.point takes any radix; .fraction announces a non-decimal one with
0x/0o/0b; .exponent is hexadecimal by definition — its p counts bits,
the way C's %a prints a double. BigInt and BigUInt have their own
toString(radix:uppercase:), and both parse back with init?(_:radix:).
Add to the dependencies section:
.package(url: "https://github.com/dankogai/swift-bignum.git", .branch("main"))and to your target:
.target(name: "YourPackage", dependencies: ["BigNum"])Then import BigNum. That is the only import you need — BigInt comes with it.
git clone https://github.com/dankogai/swift-bignum.git && cd swift-bignum && swift testmacOS.playground has a page per type — Synopsis, BigInt, BigRat, BigFloat,
Precision, and a Scratch page to work in. Open Package.swift in Xcode first so
the BigNum module is built, then open the playground.
- BigInt.md —
BigIntandBigUInt: representation, bit twiddling, division semantics, number theory - BigRat.md —
BigRat: exact rationals, theFloatingPointconformance,IntRatand the other fixed-width rationals - BigFloat.md —
BigFloat: mantissa and scale, rounding, truncation, parsing
Swift 6 or 5, macOS or Linux.
No dependencies. Up to version 5.x there were two:
BigIntandBigUIntcame from attaswift/BigInt, which had to be re-exported with@_exported importforimport BigNumalone to be enough — an undocumented corner of the language to be resting a public API on. They are now part of this package, andBigIntis stored in two's complement rather than sign-and-magnitude, which is what makeswords, the bitwise operators and>>fall out correctly instead of needing to be re-derived.Realand the three protocols it inherits came from apple/swift-numerics, which this package wanted forElementaryFunctionsalone. Apple's ownBigIntnever arrived there to justify carrying the rest. They are now declared in Real.swift and ElementaryFunctions.swift.- Versions before that depended on dankogai/swift-floatingpoint for the
FloatingPointMathprotocols, replaced byElementaryFunctions.
Code written against either former dependency keeps compiling: the protocol
requirement sets are unchanged, and BigInt and BigUInt keep the names and the
API attaswift gave them. Two deliberate breaks:
Codable— aBigIntnow encodes as a base-16 string rather than attaswift's sign-and-words form, so archives written by 5.x will not decode.as*conversions are nowto*()methods —asDoublebecametoDouble(), matching thetoString()that was already there. LikewiseasBigRat,asMixed,asIntRat,asBigFloat.