首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >浮点数表示背后的奥秘

浮点数表示背后的奥秘
EN

Stack Overflow用户
提问于 2018-08-29 20:09:57
回答 3查看 841关注 0票数 0

我在为我的应用程序测试一些简单的解决方案,我遇到了一些问题出现在我的脑海中.“为什么一个浮点数用JSON正确表示(正如我所预料的那样),而另一个则不是.?”

在这种情况下,从String转换到Decimal,然后转换为JSON的数字:"98.39“从人类的角度来看是完全可以预测的,但是数字:"98.40”看起来并不那么漂亮.

我的问题是,请有人向我解释一下,为什么从字符串到十进制的转换与我所期望的一个浮点数一样工作,而对于另一个浮点数则不是。

我有很多关于浮点数错误的文章,但是我不知道字符串->是如何处理的.基于二进制的转换.->对两种情况都有不同的精度。

我的操场代码:

代码语言:javascript
复制
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.40”-> Decimal -> String --结果为。

非常感谢大家的回应!

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-08-29 22:14:46

这纯粹是一个NSNumber如何打印自己的工件。

JSONSerialization在objects中实现,并使用objects对象(NSDictionaryNSArrayNSStringNSNumber等)。要表示它从JSON反序列化的值,请执行以下操作。由于JSON包含一个以小数点作为"amount"键值的裸数字,JSONSerialization将其解析为double并将其包装在NSNumber中。

这些Objective类中的每一个都实现了一个用于打印自身的description方法。

JSONSerialization返回的对象是一个NSDictionaryString(describing:)通过发送NSDictionary方法将NSDictionary转换为StringNSDictionary通过向每个键和值发送description来实现description,包括"amount"键的NSNumber值。

NSNumber实现的description使用printf说明符%0.16g格式化double值。(我使用反汇编程序进行检查。)关于g说明符,C标准说

最后,除非使用#标志,否则从结果的小数部分删除任何尾随零,如果没有小数部分,则小数点宽字符被删除。

最接近98.39的两倍是98.3900 0000 0005 6843 4188 6080 8014 8696 8994 1406 25。因此,%0.16g将其格式化为%0.14f (参见标准的原因是14,而不是16),这给出了"98.39000000000000",然后去掉了尾随零,给出了"98.39"

与98.40最接近的双倍是98.4000 0000 0056 8434 1886 0808 0148 6968 9941 4062 5。因此,%0.16g格式为%0.14f,它给出了"98.40000000000001" (由于四舍五入),并且没有尾随零点需要砍掉。

这就是为什么当你打印JSONSerialization.jsonObject(with:options:)的结果时,98.40的小数位数很多,98.39的小数只有两位。

如果从JSON对象中提取金额并将其转换为Swift的本机Double类型,然后打印这些Double类型,则输出的时间要短得多,因为Double实现了一种更智能的格式设置算法,该算法输出最短的字符串,解析后产生的Double完全相同。

试试这个:

代码语言:javascript
复制
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")

结果:

代码语言:javascript
复制
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:的行上)和Swift Double版本在所有情况下都使用最少的数字。使用-[NSNumber description] (标记为jso:amount as NSNumber:)的行对某些值使用额外的数字。

票数 4
EN

Stack Overflow用户

发布于 2018-08-29 21:43:23

在某种程度上,JSON表示似乎将值存储为二进制浮点。

特别是,最接近于98.40的98.400000000000005684341886080801486968994140625,(IEEE binary64)值将是double,当舍入到16个显着数字时,该值为98.40000000000001。

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

代码语言:javascript
复制
["amount": 0.056183066649934998]

两者都是,这是17个重要的数字,但实际上是一个完全错误的数值,减去32 单位在最后一个地方。我不知道那里发生了什么事。

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

票数 4
EN

Stack Overflow用户

发布于 2018-08-29 22:05:27

代码语言:javascript
复制
#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

这一点也不像西蒙指出的那样是个谜。这正是计算机的工作方式--你正在使用base 2机器来做基础10的事情。就像1/3是一个非常简单的数字,但在基数10是0.3333333。永远,不准确也不漂亮,但在基数3,它将是大约0.1漂亮和干净。例如,基数10的数字与基数2 1/10不相配。

代码语言:javascript
复制
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

只要看看他们之间的曼蒂萨清楚这是不会解决到一个确切的数字,所以四舍五入和格式印刷与更多的四舍五入.

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52085385

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档