Open3

Swift: Doubleで-0があるのにNumberFormatterは-0を返さない件

kabeyakabeya

Swiftでは、Double型に-0があります。

let zero = ceil(-0.1)
print("zero: \(zero)")  // zero: -0.0
print("equal(+): \(zero == 0.0)") // equal(+): true
print("equal(-): \(zero == -0.0)") // equal(-): true

これはこれでありがたいのですが、NumberFormatterがこいつを可逆変換しません。

let str = formatter.string(from: -0.0)!
print("str: \(str)") // str: -0
let num = formatter.number(from: str)!
print("num: \(num)") // num: 0

数値から文字に変換するときは、-0→"-0"に変換するくせに、文字から数値に変換するときは"-0"→0に変換します。
控えめに言ってクソな動きと言えるのではないかと思います。
Doubleの同値判定は==では難しいので、表示形式が決まっているなら、以下のように文字に変換して同じなら同じ、と済ますのが早いと思っていましたが、1つやっかいな処理が必要そうです。

let a = 0.1
let b = 0.2
let c = 0.3
let a_plus_b = a + b
print("a+b: ", String(format: "%.20f", a_plus_b)) // a+b:  0.30000000000000004441
print("c: ", String(format: "%.20f", c)) // c:  0.29999999999999998890
print("a+b==c: \((a_plus_b) == c)") // a+b==c: false

let formatter = NumberFormatter()
formatter.minimumFractionDigits = 20
formatter.maximumFractionDigits = 50 // ←これはあってもなくても同じ
formatter.roundingMode = .ceiling // ←これもあってもなくても同じ

let str_a_plus_b = formatter.string(from: NSNumber(floatLiteral: a_plus_b))!
let str_c = formatter.string(from: NSNumber(floatLiteral: c))!
print("a+b: ", str_a_plus_b) // a+b:  0.30000000000000000000
print("c: ", str_c) // c:  0.30000000000000000000
print("a+b==c: \((str_a_plus_b) == str_c)") // a+b==c: true

NumberFormatterの文字列変換も、いったいどういう理屈で0.30000000000000000000を生成するのかよく分かりませんが。

kabeyakabeya

Doubleの同値判定は==では難しいので、表示形式が決まっているなら、以下のように文字に変換して同じなら同じ、と済ますのが早いと思っていましたが、1つやっかいな処理が必要そうです。

なんかちょっと話の筋が見えにくいことになってました。

-0が必要なケースで、-0と0とを分けて扱いたい、というときに困る、という話でした。

kabeyakabeya

0と-0の符号はvalue < 0とかでは判定できませんが、.signで判断できます。

let minus_zero = -0.0
print("isNegative?: \(minus_zero < 0)") // isNegative?: false
print("sign: \(minus_zero.sign)") // sign: minus
let normal_zero = 0.0
print("sign: \(normal_zero.sign)") // sign: plus