Search code examples
javaswifthexbigintegerbigint

Int8 array to signed hex string doesn't work when first byte is negative


I am trying to convert a [UInt8] to an hex string like it can be done in java like this:

byte[] bytes = {...};
System.out.println(new BigInteger(bytes).toString(16));

My swift solution:

let bytes: [Int8] = [...]
print(bytes.map({ String(format: "%02hhx", $0) }).joined())

But there are two cases:

  • If the first byte is positive: both java and swift give me the same result
  • If the first byte is negative: java gives me a negative number (as expected), but swift gives me a positive number...

Example:

byte[] bytes = {-100, -21, -46, 47, -99, 39, 67, 53, 62, -2, -23, 104, -15, 117, -9, 40, -31, 70, 4, 28};
System.out.println(new BigInteger(bytes).toString(16));
// -63142dd062d8bccac10116970e8a08d71eb9fbe4
let bytes: [Int8] = [-100, -21, -46, 47, -99, 39, 67, 53, 62, -2, -23, 104, -15, 117, -9, 40, -31, 70, 4, 28]
print(bytes.map({ String(format: "%02hhx", $0) }).joined())
// 9cebd22f9d2743353efee968f175f728e146041c

But

byte[] bytes = {112, -84, -89, 120, -123, 118, -50, -7, -115, -97, -127, 41, -71, 52, -4, 105, -5, -80, 115, 86};
System.out.println(new BigInteger(bytes).toString(16));
// 70aca7788576cef98d9f8129b934fc69fbb07356
let bytes: [Int8] = [112, -84, -89, 120, -123, 118, -50, -7, -115, -97, -127, 41, -71, 52, -4, 105, -5, -80, 115, 86]
print(bytes.map({ String(format: "%02hhx", $0) }).joined())
// 70aca7788576cef98d9f8129b934fc69fbb07356

Swift Playground screenshot

What is wrong with the swift implementation?


Solution

  • I finally wrote my own solution, iterating the bytes and combining the string. Tested with different arrays, and works for both positive and negative hex.

    extension Data {
    
        func toSignedHexString() -> String {
            // Create an empty string
            var result = ""
            var first: Int8 = 0
    
            // Iterate bytes
            var bytes = map { byte in
                // Convert to Int8
                return Int8(bitPattern: byte)
            }
            while !bytes.isEmpty {
                // Get and remove the first byte
                let byte = bytes.removeFirst()
    
                // Check if this byte is the first byte
                if result.isEmpty && first == 0 {
                    // Save the first byte
                    first = byte
                } else if result.isEmpty && first != 0 {
                    // Convert two first bytes to hex
                    result.append(String(Int32(first + 1) * 256 + Int32(byte) + (first < 0 ? 1 : 0), radix: 16, uppercase: false))
                } else {
                    // Convert it to hex
                    result.append(String(format: "%02hhx", first < 0 ? (Int32(bytes.isEmpty ? 256 : 255) - Int32(byte)) % 256 : byte))
                }
            }
    
            // Return the final result
            return result
        }
    
    }
    

    Test code:

    let bytes = Data([-100, -21, -46, 47, -99, 39, 67, 53, 62, -2, -23, 104, -15, 117, -9, 40, -31, 70, 4, 28].map({ UInt8(bitPattern: $0) }))
    print(bytes.toSignedHexString() == "-63142dd062d8bccac10116970e8a08d71eb9fbe4")
    // true
    
    let bytes2 = Data([112, -84, -89, 120, -123, 118, -50, -7, -115, -97, -127, 41, -71, 52, -4, 105, -5, -80, 115, 86].map({ UInt8(bitPattern: $0) }))
    print(bytes2.toSignedHexString() == "70aca7788576cef98d9f8129b934fc69fbb07356")
    // true