Search code examples
arraysswiftnsdecimalnumber

How to convert NSDecimalNumber to byte array if NSDecimalNumber is bigger than Uint64?


I want to convert an NSDecimalNumber to a byte array. Also, I do not want to use any library for that like BigInt, BInt, etc.

I tried this one:

static func toByteArray<T>(_ value: T) -> [UInt8] {
    var value = value
    return withUnsafeBytes(of: &value) { Array($0) }
}
let value =  NSDecimalNumber(string: "...")
let bytes = toByteArray(value.uint64Value)

If the number is not bigger than Uint64, it works great. But, what if it is, how can we convert it?


Solution

  • The problem is obviously the use of uint64Value, which obviously cannot represent any value greater than UInt64.max, and your example, 59,785,897,542,892,656,787,456, is larger than that.

    If you want to grab the byte representations of the 128 bit mantissa, you can use _mantissa tuple of UInt16 words of Decimal, and convert them to bytes if you want. E.g.

    extension Decimal {
        var byteArray: [UInt8] {
            return [_mantissa.0,
                    _mantissa.1,
                    _mantissa.2,
                    _mantissa.3,
                    _mantissa.4,
                    _mantissa.5,
                    _mantissa.6,
                    _mantissa.7]
                .flatMap { [UInt8($0 & 0xff), UInt8($0 >> 8)] }
        }
    }
    

    And

    if let foo = Decimal(string: "59785897542892656787456") {
        print(foo.byteArray)
    }
    

    Returning:

    [0, 0, 0, 0, 0, 0, 0, 0, 169, 12, 0, 0, 0, 0, 0, 0]

    This, admittedly, only defers the problem, breaking the 64-bit limit of your uint64Value approach, but is still constrained to the inherent 128-bit limit of NSDecimalNumber/Decimal. To capture numbers greater than 128 bits, you'd need a completely different representation.


    NB: This also assumes that the exponent is 0. If, however, you had some large number, e.g. 4.2e101 (4.2 * 10101), the exponent will be 100 and the mantissa will simply be 42, which I bet is probably not what you want in your byte array. Then again, this is an example of a number that is too large to represent as a single 128 bit integer, anyway:

    if let foo = Decimal(string: "4.2e101") {
        print(foo.byteArray)
        print(foo.exponent)
    }
    

    Yielding:

    [42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    100