/dev/trouble
Eric Roller's blog

Monday, March 6, 2017

Decimal.init(value: UInt64) crashes for very large values

Puzzled by a crash in my application, on a line of code that looked reasonable, it became necessary to dig a little deeper in how some of my Decimal numbers are created:

let newValue = Decimal.init(binary.maskedUInt64Value)

After testing a series of different 64-bit integer values, I found this:

(lldb) po Decimal.init(UInt64.max - 1024)
▿ 18446744073709547520
  ▿ _mantissa : 8 elements
    - .0 : 38912
    - .1 : 39321
    - .2 : 39321
    - .3 : 6553
    - .4 : 0
    - .5 : 0
    - .6 : 0
    - .7 : 0

(lldb) po Decimal.init(UInt64.max - 1023)
error: warning: couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value couldn't be evaluated

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been returned to the state before expression evaluation.

It turns out, any UInt64 value in the range from (UInt64.max - 1023) to UInt64.max crashes Decimal.init(value: UInt64). So, for the time being, I need to prevent values within that range. Here is a possible solution:

let intValue = binary.maskedUInt64Value
var newValue: Decimal
if intValue > UInt64.max - 1024 {
    newValue = Decimal.init(UInt64.max - 1024)    // larger values crash!
    newValue.add(Decimal.init(intValue - (UInt64.max - 1024)))
} else {
    newValue = Decimal.init(intValue)
}

Building on this, one can employ an extension with a custom initializer. In places where such very large numbers may occur, one could then use: Decimal.init(protected: binary.maskedUInt64Value).

extension Decimal {
    init(protected value: UInt64) {
        if value > UInt64.max - 1024 {
            self.init(UInt64.max - 1024)     // larger values crash!
            self.add(Decimal.init(value - (UInt64.max - 1024)))
        } else {
            self.init(value)
        }
    }
}

rdar://30857559