

Float値に対するfetch条件の生成について教えてください。  
Ingteger値に対しての条件設定は期待した結果が得られたのですが、Float値に対しての条件設定では期待した結果が得られませんでした。  
Float値を検索する場合は別の方法があるのでしょうか？  
もしくはFloat値のデータモデルに問題があるのでしょうか？

今更なのですが、やりたかったことに関して追記します。  
SwiftのFloat型の精度の範囲の値をCoreDataを用いて永続化したデータを検索し、検索結果を得たい。  
とてもシンプルな機能を想定していて、次のような内容は含みません。  
\- プログラム内で数値計算等を行い、そこで得た値を永続化する。  
\- プログラム内で数値計算等を行い、そこで得た値をkeyとする。  
\- Float型の精度の範囲の値外の値をFloat型の値として、永続化および検索keyとする。  
永続化および検索keyとして使用する値は、Float型の精度の範囲の値だけが対象です。

コードに不具合などありましたら教えてください。

(試した結果)

[code]

    条件            |fetchされた値|コメント
    ===============+=============+=====================================
    fvalue < 0.05  |0.05以下     |**0.05未満の値ではない。0.05もfetchされる。なぜ？**
    fvalue <= 0.05 |0.05以下     |期待通り問題なし。
    fvalue == 0.05 |             |**何もれられなかった。0.05が得られないのはなぜ？**
    fvalue > 0.05  |0.06以上     |期待通り問題なし。
    fvalue >= 0.05 |0.06以上     |**0.05以上の値ではない。0.05は得られない。なぜ？**
    ---------------+-------------+-------------------------------------
    ivalue < 5     |5未満        |期待通り問題なし。
    ivalue <= 5    |5以下        |期待通り問題なし。
    ivalue == 5    |5            |期待通り問題なし。
    ivalue > 5     |6以上        |期待通り問題なし。
    ivalue >= 5    |5以上        |期待通り問題なし。
    
[/code]

(Core model)

[code]

    Entities:
    Entity|Attribute|Type
    ======+=========+======
    Pin   |name     |String
          |fvalue    |Float
          |ivalue    |Integer16
    
[/code]

(code)

[code]

    [AppDelegate.swift]
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        for i in 0..<10 {
            autoreleasepool { (result) in
                let pin = NSEntityDescription.insertNewObject(forEntityName: "Pin", into: managedObjectContext) as! Pin
                pin.name = "pin\(i)"
                pin.fvalue = NSNumber(value: Float(i) * 0.01)
                pin.ivalue = NSNumber(value: i)
    
                do {
                    try managedObjectContext.save()
                } catch {
                    let nserror = error as NSError
                    NSApplication.shared().presentError(nserror)
                }
            }
            managedObjectContext.refreshAllObjects()
        }
    
        do {
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Pin")
            fetchRequest.predicate = NSPredicate(format: "fvalue < 0.05")
            //fetchRequest.predicate = NSPredicate(format: "ivalue < 5")
            let sortDiscriptor = NSSortDescriptor(key: "name", ascending: true)
            fetchRequest.sortDescriptors = [sortDiscriptor]
            let pins = try managedObjectContext.fetch(fetchRequest) as! [Pin]
            for pin in pins {
                print("name: \(pin.name!), fvalue: \(pin.fvalue!), ivalue: \(pin.ivalue!)")
            }
        } catch let error as NSError {
            fatalError("\(error)")
        }
    }
    
[/code]


CC BY-SA 3.0
2017-02-06T15:04:14.737
32432
2017-11-03T17:19:14.490
2017-02-13T14:07:39.913
20521
20521
question
swift swift3 coredata
Float値に対するfetch条件の生成について教えてください


Core Dataのfetch方法の問題ではなく、2進浮動小数点数の問題です。

あなたと同じ結果は、Core Dataを使用しなくても、再現できます。

(2進浮動小数点数の問題を論ずるのにちょうど良いので、`0..<10`ではなく、`0...10`にしてあります。)

[code]

    import Foundation
    
    let floatArr: [NSNumber] = (0...10).map{NSNumber(value: Float($0) * 0.01)}
    print(floatArr) //-> [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.09999999]
    var predicate = NSPredicate(format: "SELF < 0.05")
    print(floatArr.filter{predicate.evaluate(with: $0)}) //-> [0, 0.01, 0.02, 0.03, 0.04, 0.05]
    predicate = NSPredicate(format: "SELF <= 0.05")
    print(floatArr.filter{predicate.evaluate(with: $0)}) //-> [0, 0.01, 0.02, 0.03, 0.04, 0.05]
    predicate = NSPredicate(format: "SELF == 0.05")
    print(floatArr.filter{predicate.evaluate(with: $0)}) //-> []
    predicate = NSPredicate(format: "SELF > 0.05")
    print(floatArr.filter{predicate.evaluate(with: $0)}) //-> [0.06, 0.07, 0.08, 0.09, 0.09999999]
    predicate = NSPredicate(format: "SELF >= 0.05")
    print(floatArr.filter{predicate.evaluate(with: $0)}) //-> [0.06, 0.07, 0.08, 0.09, 0.09999999]
    
[/code]

`0.01`のように10進法で有限の桁数で表現出来る小数も、2進法で表現すると無限小数(循環小数)になります。(`_`は何か数字が入るのではなく、単に繰り返しの範囲を表しています。)

0.00_00001010001111010111_00001010001111010111_00001010001111010111_...

`Float`型ではこのうち24bit(最初の`1`と続く23bit)を使い、残りの桁は丸められてしまうので、`Float`型でこの値を精確に表現することはできません。

上記で10番目の値が`0.1`ではなく`0.09999999`と表示されているのにはお気づきだと思いますが、`0.05`と表示されている5番目の値も実際には`0.05`ではありません。

[code]

    print(Float(5) * 0.01 - 0.05) //-> -3.72529e-09
    
[/code]

(上記の例の場合、`0.05`は理論上の精確な値の **0.05**
を表すのではなく、`0.05`と言う10進表記に一番近い`Float`型の値を表すのですが、`Float(5) * 0.01`はそれとも一致しません。)

`NSNumber`型を10進数の文字列として表示する際には四捨五入により一見正しい値を保持しているように見えるのですが、`Float`型としての値は実際にはそれらの値を精確には表せていないのです。

`debugPrint`を使うと、そのような微小な差異を出来るだけ10進表示にも反映して表示してくれます。

[code]

    for i in 0...10 {
        debugPrint(Float(i) * 0.01)
    }
    
[/code]

**0.0  
0.00999999978  
0.0199999996  
0.0299999993  
0.0399999991  
0.049999997  
0.0599999987  
0.0700000003  
0.0799999982  
0.0899999961  
0.099999994**

* * *

**_もしくはFloat値のデータモデルに問題があるのでしょうか？_**

上記で示したように、10進表現の小数で境界値を精確に指定する必要のある用途では、2進浮動小数点型のfloatやdoubleを使用せずに、
**必ずdecimalを使用してください** (コード側では`NSNumber`ではなく`NSDecimalNumber`)。


CC BY-SA 3.0
2017-02-06T17:59:07.293
32438
2017-02-06T17:59:07.293
13972
32432
answer


二進数の実数値扱いに問題があるとの指摘を頂きましたので、試行錯誤の結果、質問に挙げたコードを以下のように修正することにしました。この場合は動いていそうですが、精度の問題つきまとうのでしょうね。

(coredataへの値の設定)

[code]

    pin.fvalue = NSNumber(value: Float(Double(i) * 0.01))
    
[/code]

(prediateの生成)

[code]

    fetchRequest.predicate = NSPredicate(format: "fvalue = %f", 0.05)
    
[/code]

* * *

以下、試行錯誤の一部

二進数の実数値扱いに問題があるとの指摘を頂いたので実数値の扱いについて試行してみました。

(1) 質問で提示したコードと同様の場合

[code]

    let f1 = Float(5) * 0.01
    let fn1 = NSNumber(value: f1)
    
[/code]

デバッガで値を見てみると以下の通りでした。  
f1 Float 0.049999997  
fn1 NSNumber Float(0.050000)

(2) 定数の場合

[code]

    let f2 = 0.05
    let fn2 = NSNumber(value: f2)
    
[/code]

f2 Double 0.050000000000000003  
fn2 NSNumber Double(0.05)

Doubleとして扱われるようです。

(3) 整数と実数の演算結果では。変数の整数と実数の演算は型変換を行う必要がありますが、定数は演算できるのですね。

[code]

    let f3 = 5 * 0.01
    let fn3 = NSNumber(value: f3)
    
[/code]

f3 Double 0.050000000000000003  
fn3 NSNumber Double(0.05)  
(1)ではFloatと定数の演算ではFloatになっていましたが、結果はDoubleとなってます。

(4) (3)の結果から、FloatではなくDoubleに合わせることに。

[code]

    let f4 = Double(5) * 0.01
    let fn4 = NSNumber(value: f4)
    
[/code]

f4 Double 0.050000000000000003  
fn4 NSNumber Double(0.05)

[code]

    (5) NSNumberへはFloatで。
    let f5 = Double(5) * 0.01
    let fn5 = NSNumber(value: Float(f5))
    
[/code]

f5 Double 0.050000000000000003  
fn5 NSNumber Float(0.050000)


CC BY-SA 3.0
2017-02-11T08:02:38.153
32586
2017-02-11T08:02:38.153
20521
32432
answer