Number Base Converter — Swift source
Convert numbers between binary, octal, decimal and hexadecimal. BigInt-powered, so it handles arbitrarily large values without precision loss.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// number-base — Swift port: arbitrary-precision base conversion (bases 2-36).
import Foundation
/// Arbitrary-precision integer: little-endian base-2^32 limbs; empty == zero.
struct Big {
var neg = false
var mag: [UInt32] = []
var isZero: Bool { mag.isEmpty }
/// this = this * mul + add — one Horner step over the limbs.
mutating func mulAdd(_ mul: UInt32, _ add: UInt32) {
var carry = UInt64(add)
for i in mag.indices {
let t = UInt64(mag[i]) * UInt64(mul) + carry
mag[i] = UInt32(truncatingIfNeeded: t)
carry = t >> 32
}
while carry > 0 { mag.append(UInt32(truncatingIfNeeded: carry)); carry >>= 32 }
}
/// this /= d; returns this % d (used to peel least-significant digits).
mutating func divmod(_ d: UInt32) -> UInt32 {
var rem: UInt64 = 0
for i in mag.indices.reversed() {
let cur = rem << 32 | UInt64(mag[i])
mag[i] = UInt32(cur / UInt64(d))
rem = cur % UInt64(d)
}
while let last = mag.last, last == 0 { mag.removeLast() }
return UInt32(rem)
}
}
private let digits = Array("0123456789abcdefghijklmnopqrstuvwxyz")
/// Map a lowercase character to its value; nil when not a digit.
func digitValue(_ c: Character) -> Int? {
guard let a = c.asciiValue else { return nil }
switch a {
case 48...57: return Int(a - 48) // '0'-'9'
case 97...122: return Int(a - 87) // 'a'-'z'
default: return nil
}
}
/// Parse `s` in `base` (2-36); nil on invalid input. A radix prefix is
/// stripped only when it agrees with the base.
func parseBigInt(_ s: String, base: Int) -> Big? {
guard (2...36).contains(base) else { return nil }
var t = s.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
var b = Big()
if t.hasPrefix("-") { b.neg = true; t.removeFirst() }
else if t.hasPrefix("+") { t.removeFirst() }
if (t.hasPrefix("0x") && base == 16) || (t.hasPrefix("0b") && base == 2) ||
(t.hasPrefix("0o") && base == 8) { t.removeFirst(2) }
guard !t.isEmpty else { return nil }
for c in t {
guard let d = digitValue(c), d < base else { return nil }
b.mulAdd(UInt32(base), UInt32(d)) // Horner's method
}
if b.isZero { b.neg = false } // normalize -0
return b
}
/// Render `value` in `base` (2-36); "" for an unsupported base. Sign of zero never emitted.
func formatBigInt(_ value: Big, base: Int) -> String {
guard (2...36).contains(base) else { return "" }
var t = value
if t.isZero { return "0" }
let neg = t.neg
var out: [Character] = []
while !t.isZero { out.append(digits[Int(t.divmod(UInt32(base)))]) }
if neg { out.append("-") }
return String(out.reversed())
}
/// value(fromBase) -> toBase; nil when it does not parse.
func convertBase(_ value: String, fromBase: Int, toBase: Int) -> String? {
guard let n = parseBigInt(value, base: fromBase) else { return nil }
return formatBigInt(n, base: toBase)
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →