代码之家  ›  专栏  ›  技术社区  ›  Robert

浮点数表示背后的奥秘

  •  0
  • Robert  · 技术社区  · 6 年前

    “为什么一个浮点数在JSON中表示正确(如我所料),而另一个没有…”

    在本例中,从字符串到十进制再到数字的JSON的转换:“98.39”从人类的角度来看是完全可以预测的,但是数字:“98.40”看起来并不漂亮。。。

    我的问题是,有人能给我解释一下吗, 为什么从字符串到十进制的转换对一个浮点数有效,而对另一个浮点数无效。

    字符串->。。。基于二进制的转换…->到Double对于这两种情况具有不同的精度。


    我的游乐场代码:

    struct Price: Encodable {
        let amount: Decimal
    }
    
    func printJSON(from string: String) {
        let decimal = Decimal(string: string)!
        let price = Price(amount: decimal)
    
        //Encode Person Struct as Data
        let encodedData = try? JSONEncoder().encode(price)
    
        //Create JSON
        var json: Any?
        if let data = encodedData {
            json = try? JSONSerialization.jsonObject(with: data, options: [])
        }
    
        //Print JSON Object
        if let json = json {
            print("Person JSON:\n" + String(describing: json) + "\n")
        }
    }
    
    let stringPriceOK =     "98.39"
    let stringPriceNotOK =  "98.40"
    let stringPriceNotOK2 = "98.99"
    
    printJSON(from: stringPriceOK)
    printJSON(from: stringPriceNotOK)
    printJSON(from: stringPriceNotOK2)
    /*
     ------------------------------------------------
     // OUTPUT:
     Person JSON:
     {
     amount = "98.39";
     }
    
     Person JSON:
     {
     amount = "98.40000000000001";
     }
    
     Person JSON:
     {
     amount = "98.98999999999999";
     }
     ------------------------------------------------
     */
    

    “98.39”->十进制->字符串-结果为 "98.39" 同样的转换链: "98.40000000000001"

    非常感谢所有的回复!

    3 回复  |  直到 6 年前
        1
  •  4
  •   rob mayoff    4 年前

    这纯粹是 NSNumber 打印本身。

    JSONSerialization NSDictionary , NSArray NSString , 数字对象 "amount" 钥匙, 将其解析为 double 把它包起来 .

    description 方法打印自身。

    返回的对象 是一个 . String(describing:) 转换 字典 String 通过发送 描述 字典 工具 描述 描述 它的每个键和值,包括 数字对象 的值 “金额”

    这个 实施 描述 格式a 双重的 printf 说明符 %0.16g . (我用拆卸器检查了一下)关于 g

    最接近98.39的双精度是98.3900 0000 0005 6843 4188 6080 8014 8696 8994 1406 25。所以呢 %0.16克 将其格式化为 %0.14f (为什么是14,而不是16,请参见标准),这给出了 "98.39000000000000" ,然后去掉后面的零,给出 "98.39"

    最接近98.40的双精度是98.4000 0000 00056 8434 1886 0808 0148 6968 9941 4062 5。所以呢 将其格式化为 %0.14华氏度 "98.40000000000001" (因为四舍五入),并且没有要切掉的尾随零。

    所以当你打印 JSONSerialization.jsonObject(with:options:) ,98.40的小数位数很多,但98.39的小数位数只有两个。

    如果您从JSON对象中提取金额并将其转换为Swift的本机 Double 键入,然后打印 双倍 双倍 实现一个更智能的格式化算法,打印最短的字符串,在解析时生成完全相同的字符串 双倍 .

    import Foundation
    
    struct Price: Encodable {
        let amount: Decimal
    }
    
    func printJSON(from string: String) {
        let decimal = Decimal(string: string)!
        let price = Price(amount: decimal)
    
        let data = try! JSONEncoder().encode(price)
        let jsonString = String(data: data, encoding: .utf8)!
        let jso = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
        let nsNumber = jso["amount"] as! NSNumber
        let double = jso["amount"] as! Double
    
        print("""
        Original string: \(string)
            json: \(jsonString)
            jso: \(jso)
            amount as NSNumber: \(nsNumber)
            amount as Double: \(double)
    
        """)
    }
    
    printJSON(from: "98.39")
    printJSON(from: "98.40")
    printJSON(from: "98.99")
    

    结果:

    Original string: 98.39
        json: {"amount":98.39}
        jso: ["amount": 98.39]
        amount as NSNumber: 98.39
        amount as Double: 98.39
    
    Original string: 98.40
        json: {"amount":98.4}
        jso: ["amount": 98.40000000000001]
        amount as NSNumber: 98.40000000000001
        amount as Double: 98.4
    
    Original string: 98.99
        json: {"amount":98.99}
        jso: ["amount": 98.98999999999999]
        amount as NSNumber: 98.98999999999999
        amount as Double: 98.99
    

    请注意,实际的JSON(在标记为 json: )还有雨燕 双倍 -[NSNumber description] jso: amount as NSNumber: )对某些值使用额外的数字。

        2
  •  3
  •   Simon Byrne    6 年前

    尤其是最近的 double (IEEE二进制64)值为98.40时为98.400000000000005684843418860880801486968994140625,四舍五入到16位有效数字时为98.40000000000001。

    为什么是16个重要数字?这是个好问题,因为16位有效数字不足以唯一地标识所有浮点值,例如。 0.056183066649934776 0.05618306664993478 与16位有效数字相同,但对应不同的值。奇怪的是你的代码现在打印出来了

    ["amount": 0.056183066649934998]
    

    对于两者来说,这是17个重要的数字,但实际上是一个完全不同的数字 units in the last place . 我不知道那里发生了什么。

    https://www.exploringbinary.com/number-of-digits-required-for-round-trip-conversions/ 有关二进制-十进制转换所需位数的详细信息。

        3
  •  0
  •   old_timer    6 年前
    #include <stdio.h>
    int main ( void )
    {
        float f;
        double d;
    
        f=98.39F;
        d=98.39;
    
        printf("%f\n",f);
        printf("%lf\n",d);
        return(1);
    }
    98.389999
    98.390000
    

    正如西蒙指出的,这根本不是一个谜。这就是电脑的工作原理,你用一台2进制的机器来做10进制的事情。就像1/3是一个非常简单的数字,但在十进制中它是0.3333。永远,不准确,也不漂亮,但在基地3它会像0.1漂亮和干净的东西。例如,以10为基数的数字与以21/10为基数的数字不匹配。

    float fun0 ( void )
    {
        return(98.39F);
    }
    double fun1 ( void )
    {
        return(98.39);
    }
    00000000 <fun0>:
       0:   e59f0000    ldr r0, [pc]    ; 8 <fun0+0x8>
       4:   e12fff1e    bx  lr
       8:   42c4c7ae    sbcmi   ip, r4, #45613056   ; 0x2b80000
    
    0000000c <fun1>:
       c:   e59f0004    ldr r0, [pc, #4]    ; 18 <fun1+0xc>
      10:   e59f1004    ldr r1, [pc, #4]    ; 1c <fun1+0x10>
      14:   e12fff1e    bx  lr
      18:   c28f5c29    addgt   r5, pc, #10496  ; 0x2900
      1c:   405898f5    ldrshmi r9, [r8], #-133 ; 0xffffff7b
    
    42c4c7ae  single
    405898f5c28f5c29  double
    
    0 10000101 10001001100011110101110
    0 10000000101 1000100110001111010111000010100011110101110000101001
    
    10001001100011110101110
    1000100110001111010111000010100011110101110000101001