読者です 読者をやめる 読者になる 読者になる

長い文字列をNSDecimalNumberに変換するとおしりが0になる

なにかの金額を入力する画面などで、ユーザーに数字を入力してもらうUIを作ることがある。金額なので、NSDecimalNumberで受けることになる。このとき大きな数字を入力すると、入力とは違う値になることがある。*1

NSDecimalNumber(string: "999999999999999999999999999999999999999").stringValue
// => "999999999999999999999999999999999999990"

これよりも長い文字列を入力すると、0が続く。

NSDecimalNumber(string: "9999999999999999999999999999999999999999").stringValue
// => "9999999999999999999999999999999999999900"

これは、NSDecimalNumberの内部表現による制限である。NSDecimalNumberは、128bitの符合なし整数と32bitの符合つき整数の組で表現されている。(下の定義では_mantissa_exponent。)

struct NSDecimal {
    var _exponent: Int32
    var _length: UInt32
    var _isNegative: UInt32
    var _isCompact: UInt32
    var _reserved: UInt32
    var _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)
    init()
    init(_exponent _exponent: Int32, _length _length: UInt32, _isNegative _isNegative: UInt32, _isCompact _isCompact: UInt32, _reserved _reserved: UInt32, _mantissa _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16))
}

例えば、123.45であれば、12345 * 10^-2として、12345と-2の組で表現する。*2

つまり、_mantissaに対応する値は、128bitで表現できる範囲でなくてはならない。

Rubyで試してみると999999999999999999999999999999999999999を表現するには130bit必要とのことなので、128bitでは足りずに切り捨てられたということがわかる。

> 999999999999999999999999999999999999999.bit_length
=> 130
> 99999999999999999999999999999999999999.bit_length
=> 127

つまり、999999999999999999999999999999999999990というのは、

  • _mantissa = 9999999999999999999999999999999999999
  • _exponent = 1

になっているはず。

*1:UIとしては現実的な桁数に入力を制限するべきである。

*2:多分こうだし、本質的には同等の表現なことはかなり自信があるが、まったく同じ値かどうかはわからない。