Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert a Double to Hex notation in Swift

Tags:

hex

swift

How do I convert a very large number into hex?

For example, 647751843213568900000 in hex is 0x231d5cd577654ceab3. I'm able to easily go from hex to double with:

let hex: Double = 0x231d5cd577654ceab3

However I can't work out how to go from Double back to hex. What am I missing?

The following does not work as it overflows when stored as an 'Int':

let hexConverted = String(647751843213568900000, radix: 16)
like image 967
David T Avatar asked Jul 12 '17 12:07

David T


1 Answers

The basic algorithm (Swift 5) is the following:

func representationOf<T: FixedWidthInteger>(_ number: T, base: T) -> String {
    var buffer: [Int] = []
    var n = number
    
    while n > 0 {
        buffer.append(Int(n % base))
        n /= base
    }
    
    return buffer
        .reversed()
        .map { String($0, radix: Int(base)) }
        .joined()
}

print(representationOf(647751843213568900, base: 16))

Of course, this is what String(_:radix:) is doing so there is no need for us to implement it by ourselves.

Your real problem is not the encoding but the representation of big integers.

There are multiple implementations out there already, for example https://github.com/mkrd/Swift-Big-Integer. Some of them already have functions for hex encoding.

In Swift 4 it will be possible to declare your own implementation of higher IntXXX (conforming to FixedWidthInteger) and the problem will become a bit easier:

typealias Int128 = DoubleWidth<Int64>
typealias Int256 = DoubleWidth<Int128>

let longNumber = Int256("231d5cd577654ceab3", radix: 16)!
print(longNumber)
print(String(longNumber, radix: 16))

But unfortunately, the DoubleWidth is not implemented in Xcode 9 Beta 4 yet.

For some values your can also use the Decimal type. Using the algorithm written above:

extension Decimal {
    func rounded(mode: NSDecimalNumber.RoundingMode) -> Decimal {
        var this = self
        var result = Decimal()
        NSDecimalRound(&result, &this, 0, mode)
        
        return result
    }
    
    func integerDivisionBy(_ operand: Decimal) -> Decimal{
        let result = (self / operand)
        return result.rounded(mode: result < 0 ? .up : .down)
    }
    
    func truncatingRemainder(dividingBy operand: Decimal) -> Decimal {
        return self - self.integerDivisionBy(operand) * operand
    }
}

extension Decimal {
    init(_ string: String, base: Int) {
        var decimal: Decimal = 0
        
        let digits = Array(string)
            .map { String($0) }
            .map { Int($0, radix: base)! }
        
        for digit in digits {
            decimal *= Decimal(base)
            decimal += Decimal(digit)
        }
        
        self.init(string: decimal.description)!
    }
}

func representationOf(_ number: Decimal, base: Decimal) -> String {
    var buffer: [Int] = []
    var n = number
    
    while n > 0 {
        buffer.append((n.truncatingRemainder(dividingBy: base) as NSDecimalNumber).intValue)
        n = n.integerDivisionBy(base)
    }
    
    return buffer
        .reversed()
        .map { String($0, radix: (base as NSDecimalNumber).intValue ) }
        .joined()
}

let number = Decimal("231d5cd577654ceab3", base: 16)
print(number) // 647751843213568961203
print(representationOf(number, base: 16)) // 231d5cd577654ceab3

Note how your value got truncated when converted to Double.

like image 183
Sulthan Avatar answered Sep 21 '22 16:09

Sulthan