【问题标题】:Roundtripping double to text and back往返文本和返回
【发布时间】:2009-12-12 16:48:15
【问题描述】:

使用 Cocoa 或基本 c,我如何将双精度转换为字符串表示,然后将其转换回完全相同的双精度。可读性并不重要,重要的是准确性。 例如,这是否可行:
双 a,b;
a=某个值;
b=[[[NSNumber numberWithDouble:a] stringValue] doubleValue];

【问题讨论】:

    标签: c cocoa printf nsnumber


    【解决方案1】:

    [编辑] 实际上在 C99 中有一种方法可以做到这一点——使用 %a 格式说明符。这会以十六进制科学计数法打印出 double,因此不会损失精度。结果是准确的。示例:

    double d1 = 3.14159;
    char str[64];
    sprintf(str, "%a", d1);
    // str is now something like "0x1.921f9f01b866ep+1"
    ...
    double d2;
    sscanf(str, "%la", &d2);
    assert(d2 == d1);
    

    原答案如下:


    大多数双字符串转换不能保证这一点。您可以尝试以非常高的精度打印出双精度数(双精度数可以存储大约 16 个十进制数字),但仍有小概率出现浮点错误:

    // Not guaranteed to work:
    double d = ...;
    char str[64];
    sprintf(str, "%.30g", d);
    ...
    double d2;
    sscanf(str, "%g", &d2);
    

    这将达到非常高的精度,但它仍然可能有几个 ULP(最后一个单位)的错误。

    您在做什么要求您在使用字符串时产生 100% 完全可重现的值?我强烈建议你重新考虑你想要做什么。如果您真的需要精确转换,只需将双精度数存储为 8 个二进制字节。如果需要以可打印的格式存储,请将二进制数据转换为十六进制(16 字节)或 base 64。

    【讨论】:

    • 谢谢,使用 %a 似乎可以完成这项工作。使用 Cocoa 我设法拉出以下内容: double a=pi;双乙; b=[[NSScannerscannerWithString:[NSString stringWithFormat:@"%a",a]] scanHexDouble:&b] 结果 a==b
    【解决方案2】:

    正常的字符串转换几乎不可避免地会丢失准确性。如果它必须是一个字符串,您可能希望将其地址放入指向 char 的指针中,然后以十六进制写出各个字节。读回它们是类似的——从十六进制转换为字节,然后以正确的顺序将字节放入内存。

    【讨论】:

      【解决方案3】:

      要在 Cocoa 中创建和使用“普通”(即十进制)字符串表示,请使用 NSString's stringWithFormat: methoddouble 生成字符串,并使用 NSScanner's scanDouble: method 将字符串转换为 double

      但是,正如 Jerry Coffin 所说,这可能会失去准确性。写出原始字节可以在同一台机器上工作,但它不是一种独立于架构的方式,因此如果数据从一台机器传输到另一台机器,就会产生问题。

      更好的方法是将值包装在 NSNumber 实例中。这是一个有效的属性列表对象。如果您使用 NSPropertyListSerialization 类将其编码为二进制 plist,则应该保留所有精度并且其他机器可以读取。

      【讨论】:

        【解决方案4】:

        一种方法是简单地将字符串表示生成为比浮点类型可以处理的更多有效数字;例如17 位十进制数字足以准确表示 IEEE-754 双精度数。这将保证您获得相同的数值(如果您的运行时库的转换例程得到正确实现),但缺点是经常给您提供不必要的数字。

        Python 3.1 最近添加了一种算法,可以将任何浮点值转换为最短的字符串,并保证返回到相同的值;如果您有兴趣,可以查看the implementation。不过,它很毛茸茸。

        【讨论】:

          【解决方案5】:

          由于嵌入的零,您不能安全地将双精度复制到“字符串”。假设您的替身在内存中保存为 0x43 0x63 0x00 0x00 0x00 0x00 0x00 0x01 ... 作为 C string 即“抄送”,最后一个 0x01 是“丢失”。

          您可以将 double 转换为 unsigned char array 并返回

          double val = 42.42;
          unsigned char val_char[sizeof (double)];
          
          /* ... */
          
          /* copy double to unsigned char array */
          memcpy(val_char, &val, sizeof val);
          
          /* ... */
          
          /* copy unsigned char array to double */
          memcpy(&val, val_char, sizeof val);
          

          以打印unsigned char array为例

          printf("0x%02x", val_char[0]);
          for (k = 1; k < sizeof (double); k++) {
              printf(" 0x%02x", val_char[k]);
          }
          puts("");
          

          阅读

          fgets(buf, sizeof buf, stdin);
          for (k = 0; k < sizeof (double); k++) {
              long tmp;
              char *buf_end;
              tmp = strtol(buf, buf_end, 0);
              buf = buf_end + 1;
              char_val[k] = tmp;
          }
          

          【讨论】:

            【解决方案6】:

            没有办法做到便携。

            上面的解决方案——将双精度重新解释为原始位并打印它们——是一种不错的方法;在相关说明中,如果您需要更多可读性,您可以将尾数与指数分开并以十进制打印。然而,即使这些也不能保证将双精度打印到完美精度:例如,在 x86 架构上,编译器会经常优化浮点代码以使用 128 位 SSE 寄存器;上面的解决方案强制它进入内存,它将丢失 64 位信息。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-10-27
              • 2014-03-08
              • 2021-05-27
              • 2015-04-02
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多