❄️

【Swift】Double.ulpOfOne

2024/12/15に公開

公式の説明

The positive difference between 1.0 and the next greater representable number.

1.0と次に大きい数値の間の正の差。

解説

1.0を表現する Double を用意( a )
1.0より大きくて Double で表現可能な、1.0の隣の値を用意( b )
b - a が約2.220446049250313e-16。それを Double.ulpOfOne とする。

仮数部は
a(1)00000略00000
b(1)00000略00001
となる。
b の最後の1の大きさを出力するために、1に2で割る作業を52回すると出来る。

52回2で割るイメージは
100000略00000
010000略00000
001000略00000

000000略00010
000000略00001

52がどこから出てきたかというのは、浮動小数点数の仕様から出てきた。

コード

let d = Double.ulpOfOne
print(d) //2.220446049250313e-16

var e = 1.0
for _ in 1...52 {
    e /= 2.0
}
print(e) //2.220446049250313e-16

de が同じになった。
具体的に値を書き出してみる。

import Foundation

var d1 = 1.0
let d1Data = Data(bytes: &d1, count: MemoryLayout.size(ofValue: d1))
d1Data.withUnsafeBytes { pointer in
    for i in 0..<d1Data.count {
        let byte = pointer[i]
        print(String(format: "%x", byte)) //3f f0 00 00 00 00 00 00
    }
}

var d2 = 1.0 + Double.ulpOfOne
let d2Data = Data(bytes: &d2, count: MemoryLayout.size(ofValue: d2))
d2Data.withUnsafeBytes { pointer in
    for i in 0..<d2Data.count {
        let byte = pointer[i]
        print(String(format: "%x", byte)) //3f f0 00 00 00 00 00 01
    }
}

var d3 = Double.ulpOfOne
let d3Data = Data(bytes: &d3, count: MemoryLayout.size(ofValue: d3))
d3Data.withUnsafeBytes { pointer in
    for i in 0..<d3Data.count {
        let byte = pointer[i]
        print(String(format: "%x", byte)) //3c b0 00 00 00 00 00 00
    }
}

d1d3 の指数部を比較して
0x3ff - 0x3cb = 0x34 = 0b00110100 = 32 + 16 + 4 = 52
52個のシフトです。
1.0の指数部はほとんどのビットが立っているんですね。

使い方

使い所を予想すると、とんでもなく大きい DoubleDouble.ulpOfOne をかけた結果が100になったら、その数に10や20を足しても意味がない、ということがわかるようになる。予想ですが。

Discussion