18

I know how to do it in java (see here), but I couldn't find a swift equivalent for java's ByteBuffer, and consequently its .putDouble(double value) method.

Basically, I'm looking for a function like this:
func doubleToByteArray(value: Double) -> [UInt8]? {
    . . .
}
doubleToByteArray(1729.1729) // should return [64, 155, 4, 177, 12, 178, 149, 234]
Community
  • 1
  • 1
Panini Raman
  • 461
  • 1
  • 5
  • 12

7 Answers7

52
typealias Byte = UInt8

func toByteArray<T>(var value: T) -> [Byte] {
    return withUnsafePointer(&value) {
        Array(UnsafeBufferPointer(start: UnsafePointer<Byte>($0), count: sizeof(T)))
    }
}

toByteArray(1729.1729)
toByteArray(1729.1729 as Float)
toByteArray(1729)
toByteArray(-1729)

But the results are reversed from your expectations (because of endianness):

[234, 149, 178, 12, 177, 4, 155, 64]
[136, 37, 216, 68]
[193, 6, 0, 0, 0, 0, 0, 0]
[63, 249, 255, 255, 255, 255, 255, 255]

Added:

func fromByteArray<T>(value: [Byte], _: T.Type) -> T {
    return value.withUnsafeBufferPointer {
        return UnsafePointer<T>($0.baseAddress).memory
    }
}

let a: Double = 1729.1729
let b = toByteArray(a) // -> [234, 149, 178, 12, 177, 4, 155, 64]
let c = fromByteArray(b, Double.self) // -> 1729.1729

For Xcode8/Swift3.0:

func toByteArray<T>(_ value: T) -> [UInt8] {
    var value = value
    return withUnsafePointer(to: &value) {
        $0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout<T>.size) {
            Array(UnsafeBufferPointer(start: $0, count: MemoryLayout<T>.size))
        }
    }
}

func fromByteArray<T>(_ value: [UInt8], _: T.Type) -> T {
    return value.withUnsafeBufferPointer {
        $0.baseAddress!.withMemoryRebound(to: T.self, capacity: 1) {
            $0.pointee
        }
    }
}

For Xcode8.1/Swift3.0.1

func toByteArray<T>(_ value: T) -> [UInt8] {
    var value = value
    return withUnsafeBytes(of: &value) { Array($0) }
}

func fromByteArray<T>(_ value: [UInt8], _: T.Type) -> T {
    return value.withUnsafeBytes {
        $0.baseAddress!.load(as: T.self)
    }
}
rintaro
  • 48,979
  • 13
  • 123
  • 135
  • 2
    Just use ```toByteArray(1729.1729).reverse()``` to get the order you are looking for. – David Gomez Nov 16 '14 at 05:55
  • 1
    I recommend to use functions defined in `CFByteOrder.h`, for example `toByteArray(CFConvertDoubleHostToSwapped(1729.1729).v)`. – rintaro Nov 16 '14 at 06:28
  • I have added `typealias Byte = UInt8`, because `Byte` isn`t a native data type. :-) – Michael Dorner Dec 21 '15 at 09:53
  • A word of caution if you're using the first function to serialize data going to some non-Swift platform. If you pass it an "optional" Swift value type then it adds an extra byte to the output, presumably to indicate if the value is nil or not. So giving it a UInt64? value produces 9 bytes instead of 8. One way to avoid this would be to change the parameter type of "value" from T to T?, and then replace the first statement with "var value = value!". – RenniePet Jan 01 '17 at 19:26
  • It crashes: http://stackoverflow.com/questions/42252124/fatal-error-load-from-misaligned-raw-pointer – Vyacheslav Feb 15 '17 at 14:29
  • I think `withUnsafeBytes(of: &y, Array.init)` is a more efficient way of converting an UnsafeMutableBufferPointer to an Array. Because the Array initialiser is passed directly. – Damiaan Dufaux Jul 29 '17 at 22:21
6

Well, it wasn't easy, but here it is:

func doubleToByteArray(value: Double) -> [UInt8] {
    let count = sizeof(Double)
    var doubles: [Double] = [value]
    let data = NSData(bytes: doubles, length: count)
    var result = [UInt8](count: count, repeatedValue: 0)
    data.getBytes(&result, length: count)
    return result
}

Use with caution.

vacawama
  • 133,454
  • 26
  • 238
  • 261
4

Here's my updated version to the original solution.

/// input: array of bytes 
/// -> get pointer to byte array (UnsafeBufferPointer<[Byte]>)
/// -> access its base address
/// -> rebind memory to target type T (UnsafeMutablePointer<T>)
/// -> extract and return the value of target type
func binarytotype <T> (_ value: [Byte], _: T.Type) -> T
{
    return value.withUnsafeBufferPointer {
        $0.baseAddress!
          .withMemoryRebound(to: T.self, capacity: 1) {
            $0.pointee
        }
    }
}

/// input type: value of type T
/// -> get pointer to value of T
/// -> rebind memory to the target type, which is a byte array
/// -> create array with a buffer pointer initialized with the     source pointer
/// -> return the resulted array
func typetobinary <T> (_ value: T) -> [Byte]
{
    var mv : T = value
    let s : Int = MemoryLayout<T>.size
    return withUnsafePointer(to: &mv) {
        $0.withMemoryRebound(to: Byte.self, capacity: s) {
            Array(UnsafeBufferPointer(start: $0, count: s))
        }
    }
}

PS: Don't forget to replace Byte with UInt8.

segabor
  • 41
  • 3
4

Solution in swift 3:

public func toByteArray<T>(_ value: T) -> [Byte] {
  let totalBytes = MemoryLayout<T>.size
  var value = value
  return withUnsafePointer(to: &value) { valuePtr in
    return valuePtr.withMemoryRebound(to: Byte.self, capacity: totalBytes) { reboundPtr in
      return Array(UnsafeBufferPointer(start: reboundPtr, count: totalBytes))
    }
  }
}
Nathan Kot
  • 2,294
  • 1
  • 18
  • 30
4

The accepted answers are dangerous because of the fact that the MemoryLayout provides you the size of the static type T!

To workaround the problem you should create a custom protocol and ask for Self in it:

protocol ByteConvertible {}

extension ByteConvertible {

    func toBytes() -> [UInt8] {

        let capacity = MemoryLayout<Self>.size
        var mutableValue = self
        return withUnsafePointer(to: &mutableValue) {

            return $0.withMemoryRebound(to: UInt8.self, capacity: capacity) {

                return Array(UnsafeBufferPointer(start: $0, count: capacity))
            }
        }
    }
}

I mentioned before that the accepted answers are dangerous and here is an example why:

let num = UInt8(42)
MemoryLayout.size(ofValue: num) //=> 1 byte as expected
let any: Any = num
MemoryLayout.size(ofValue: any) //=> 32 bytes which is what will happen in the generic functions from the all the answers 

Swift 3.0

DevAndArtist
  • 4,391
  • 1
  • 20
  • 45
1

The method above works, using Swift 2 but, I discovered a much more simpler and faster method to do this conversion and vice versa:

func binarytotype <T> (value: [UInt8], _: T.Type) -> T
{
    return value.withUnsafeBufferPointer
    {
        return UnsafePointer<T>($0.baseAddress).memory
    }
}

func typetobinary <T> (var value: T) -> [UInt8]
{
    return withUnsafePointer(&value)
    {
        Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>($0), count: sizeof(T)))
    }
}

let a: Double = 0.25
let b: [UInt8] = typetobinary(a) // -> [0, 0, 0, 0, 0, 0, 208, 63]
let c = binarytotype(b, Double.self) // -> 0.25

I have tested it with Xcode 7.2 in the playground.

j.s.com
  • 1,204
  • 12
  • 21
  • 1
    I would like to use this in Swift 3 beta 6 and the new withMemoryRebound command. Does anybody know how to convert it to swift 3 beta 6? – j.s.com Aug 17 '16 at 15:28
1
func byteArray<T>(_ value: T) -> [UInt8] {
    var value = value
    var initialArray = withUnsafeBytes(of: &value) { Array($0) }

    initialArray.reverse()
    var count = initialArray.count
    while initialArray.first == 0 && count > 1 {
        initialArray[0...count - 2] = initialArray[1...count - 1]
        count -= 1
    }
    if initialArray[0] >= 128 {
        var newArray = [UInt8](repeating: 0, count: count + 1)
        newArray[0] = UInt8(0)
        newArray[1...count] = initialArray[0...count - 1]
        return newArray
    } else {
        return Array(initialArray[0...count - 1])
    }
}
Bogdan Razvan
  • 894
  • 9
  • 13