2

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?

KingHodor
  • 537
  • 4
  • 17
  • What is the question, "How to convert NSDecimalNumber to byte array?" or "what happens if the number is bigger than Uint64?"? – Willeke Dec 07 '18 at 00:40
  • How to convert NSDecimalNumber to byte array if NSDecimalNumber is bigger than Uint64? I 've just changed the question. Sorry for the misleading. – KingHodor Dec 07 '18 at 00:49
  • Can you give an example of a `value` and the expexted `bytes`? Is `value` always a whole number? – Willeke Dec 07 '18 at 00:53
  • for example if the value is "59785897542892656787456", the value has to be [12, 169, 0, 0, 0, 0, 0, 0,0 ,0]. Like new BigInteger("59785897542892656787456").toByteArray in java. – KingHodor Dec 07 '18 at 00:54
  • Take a look at `value.decimalValue` and its `_mantissa`. – Willeke Dec 07 '18 at 01:13
  • @Rob `_mantissa` of `NSDecimal` is 128 binary positions. – Willeke Dec 07 '18 at 04:17
  • Good point. I was thrown off by by the `NSDecimalNumber.init(mantissa:exponent:isNegative:)`, whose `mantissa` is `UInt64`. But the mantissa is, actually 128 bits, capable of handling decimal integer up to 38 digits long. But my point, that it can't handle arbitrary length integers, whereas other representations can, still stands. – Rob Dec 07 '18 at 11:30

2 Answers2

2

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

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

You are passing value.uint64Value and not value. So the number will be wrapped around zero to fit the NSDecimal into a UInt64 format.

Passing "18446744073709551616" (which is the string corresponding to UInt64.max + 1) is equivalent to passing "0". Passing "18446744073709551617" (which is the string corresponding to UInt64.max + 2) is equivalent to passing "1".

ielyamani
  • 17,807
  • 10
  • 55
  • 90
  • passing NSDecimalNumber does not give the correct result. – KingHodor Dec 07 '18 at 00:45
  • This answers your original question explaining what happens when the number is bigger than UInt64. – ielyamani Dec 07 '18 at 00:50
  • Sorry for the misleading. – KingHodor Dec 07 '18 at 00:53
  • 1
    Is `MemoryLayout.size` the size of a pointer? The size of a `NSDecimalNumber` object is irrelevant. It is an object-oriented wrapper around `NSDecimal` (`Decimal` in Swift). `NSDecimalNumber` and `NSDecimal` can handle strings `"18446744073709551616"` and `"18446744073709551617"`. Method `uint64Value` can't convert these values to `UInt64`. – Willeke Dec 07 '18 at 07:47