Swift convert decimal String to UInt8-Array

3.1k Views Asked by At

I have a very long String (600+ characters) holding a big decimal value (yes I know - sounds like a BigInteger) and need the byte representation of this value.

Is there any easy way to archive this with swift?

static func decimalStringToUInt8Array(decimalString:String) -> [UInt8] {
  ...
}
4

There are 4 best solutions below

3
On BEST ANSWER

Edit: Updated for Swift 5

I wrote you a function to convert your number string. This is written in Swift 5 (originally Swift 1.2).

func decimalStringToUInt8Array(_ decimalString: String) -> [UInt8] {

    // Convert input string into array of Int digits
    let digits = Array(decimalString).compactMap { Int(String($0)) }

    // Nothing to process? Return an empty array.
    guard digits.count > 0 else { return [] }

    let numdigits = digits.count

    // Array to hold the result, in reverse order
    var bytes = [UInt8]()

    // Convert array of digits into array of Int values each
    // representing 6 digits of the original number.  Six digits
    // was chosen to work on 32-bit and 64-bit systems.
    // Compute length of first number.  It will be less than 6 if
    // there isn't a multiple of 6 digits in the number.
    var ints = Array(repeating: 0, count: (numdigits + 5)/6)
    var rem = numdigits % 6
    if rem == 0 {
        rem = 6
    }
    var index = 0
    var accum = 0
    for digit in digits {
        accum = accum * 10 + digit
        rem -= 1
        if rem == 0 {
            rem = 6
            ints[index] = accum
            index += 1
            accum = 0
        }
    }

    // Repeatedly divide value by 256, accumulating the remainders.
    // Repeat until original number is zero
    while ints.count > 0 {
        var carry = 0
        for (index, value) in ints.enumerated() {
            var total = carry * 1000000 + value
            carry = total % 256
            total /= 256
            ints[index] = total
        }

        bytes.append(UInt8(truncatingIfNeeded: carry))

        // Remove leading Ints that have become zero.
        while ints.count > 0 && ints[0] == 0 {
            ints.remove(at: 0)
        }
    }

    // Reverse the array and return it
    return bytes.reversed()
}

print(decimalStringToUInt8Array("0"))         // prints "[0]"
print(decimalStringToUInt8Array("255"))       // prints "[255]"
print(decimalStringToUInt8Array("256"))       // prints "[1,0]"
print(decimalStringToUInt8Array("1024"))      // prints "[4,0]"
print(decimalStringToUInt8Array("16777216"))  // prints "[1,0,0,0]"

Here's the reverse function. You'll notice it is very similar:

func uInt8ArrayToDecimalString(_ uint8array: [UInt8]) -> String {

    // Nothing to process? Return an empty string.
    guard uint8array.count > 0 else { return "" }

    // For efficiency in calculation, combine 3 bytes into one Int.
    let numvalues = uint8array.count
    var ints = Array(repeating: 0, count: (numvalues + 2)/3)
    var rem = numvalues % 3
    if rem == 0 {
        rem = 3
    }
    var index = 0
    var accum = 0
    for value in uint8array {
        accum = accum * 256 + Int(value)
        rem -= 1
        if rem == 0 {
            rem = 3
            ints[index] = accum
            index += 1
            accum = 0
        }
    }

    // Array to hold the result, in reverse order
    var digits = [Int]()

    // Repeatedly divide value by 10, accumulating the remainders.
    // Repeat until original number is zero
    while ints.count > 0 {
        var carry = 0
        for (index, value) in ints.enumerated() {
            var total = carry * 256 * 256 * 256 + value
            carry = total % 10
            total /= 10
            ints[index] = total
        }

        digits.append(carry)

        // Remove leading Ints that have become zero.
        while ints.count > 0 && ints[0] == 0 {
            ints.remove(at: 0)
        }
    }

    // Reverse the digits array, convert them to String, and join them
    return digits.reversed().map(String.init).joined()
}

Doing a round trip test to make sure we get back to where we started:

let a = "1234567890987654321333555777999888666444222000111"
let b = decimalStringToUInt8Array(a)
let c = uInt8ArrayToDecimalString(b)
if a == c {
    print("success")
} else {
    print("failure")
}
success

Check that eight 255 bytes is the same as UInt64.max:

print(uInt8ArrayToDecimalString([255, 255, 255, 255, 255, 255, 255, 255]))
print(UInt64.max)
18446744073709551615
18446744073709551615
2
On

You can always do:

let bytes = [UInt8](decimalString.utf8)

If you want the UTF-8 bytes.

1
On

Provided you had division implemented on your decimal string you could divide by 256 repeatedly. The reminder of the first division is the your least significant byte.

Here's an example of division by a scalar in C (assumed the length of the number is stored in A[0] and writes the result in the same array):

void div(int A[], int B)
{
    int i, t = 0;
    for (i = A[0]; i > 0; i--, t %= B)
        A[i] = (t = t * 10 + A[i]) / B;
    for (; A[0] > 1 && !A[A[0]]; A[0]--);
}
0
On

You can use the NSData(int: Int, size: Int) method to get an Int to NSData, and then get the bytes from NSData to an array: [UInt8].

Once you know that, the only thing is to know the size of your array. Darwin comes in handy there with the powfunction. Here is a working example:

func stringToUInt8(string: String) -> [UInt8] { 
if let int = string.toInt() {

 let power: Float = 1.0 / 16
 let size = Int(floor(powf(Float(int), power)) + 1)

 let data = NSData(bytes: &int, length: size)

 var b = [UInt8](count: size, repeatedValue: 0)

 return data.getBytes(&b, length: size)
}
}