1

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] {
  ...
}
chrstnwhlrt
  • 331
  • 1
  • 4
  • 18
  • Swift (or Foundation) does not have a library for dealing with big integers, but see http://stackoverflow.com/questions/25531914/biginteger-equivalent-in-swift. – Martin R Jun 10 '15 at 17:55
  • I saw those libs but non of them is compatible to the current swift and allows conversions from and to unit8-array – chrstnwhlrt Jun 11 '15 at 14:53
  • Well, the answer to your question *"Is there any easy way to archive this with swift?"* is simply *"No, there isn't"*. – Swift does not provide this, so you have to write your own code or use 3rd-party libs. As Swift constantly changes, *any* 3rd-party code probably has to be adapted. – Martin R Jun 11 '15 at 15:20

4 Answers4

4

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
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • THIS IS **AMAZING** !!! Thank you so much! We created a method on our own, but yours just works and our application confirms the result.. this is really awesome, thank you! We also have a method created to convert this uint8-array back to a big string, but somehow it produces another string with your method in the middle. Do you have a clean way to convert the resulting array back to a big decimal string @vacawama? Thank you again, this really helped us out! – chrstnwhlrt Jun 14 '15 at 15:52
  • I added the inverse function. – vacawama Jun 14 '15 at 17:20
  • **AMAZING, SAVED MY DAY!** TYVM – Bogdan Razvan Nov 29 '18 at 09:28
1

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)
}
}
HHK
  • 1,352
  • 1
  • 15
  • 25
0

You can always do:

let bytes = [UInt8](decimalString.utf8)

If you want the UTF-8 bytes.

Hans Kröner
  • 61
  • 2
  • 9
  • This gives me the bytes of the string as utf-8, but I need its value as bytes. For example: let decimalString = "123456789"; The its hex value would be 0x75BCD15 and its binary value would be ...0001 0101, which should also be the first and the second entry of the byte array. – chrstnwhlrt Jun 10 '15 at 16:58
  • Ahhh! My bad, I didn't understand that's what you meant by "big decimal value." – Hans Kröner Jun 10 '15 at 17:41
0

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]--);
}