使用 Cocoa 或基本 c,我如何将双精度转换为字符串表示,然后将其转换回完全相同的双精度。可读性并不重要,重要的是准确性。例如,这是否可行:
double a,b;
a=某个值;
b=[[[NSNumber numberWithDouble:a] stringValue] doubleValue];
6 回答
[编辑]实际上在 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。
正常转换为字符串几乎不可避免地会失去准确性。如果它必须是一个字符串,您可能希望将其地址放入指向 char 的指针中,然后以十六进制写出各个字节。读回它们是类似的——从十六进制转换为字节,然后以正确的顺序将字节放入内存。
要在 Cocoa 中创建和使用“正常”(即十进制)字符串表示,请使用NSString 的stringWithFormat:
方法从 a 生成字符串double
,并使用 NSScanner 的scanDouble:
方法将字符串转换为 a double
。
但是,正如 Jerry Coffin 所说,这可能会失去准确性。写出原始字节将在同一台机器上工作,但这不是一种独立于架构的方式,因此如果数据从一台机器传输到另一台机器,就会产生问题。
更好的方法是将值包装在 NSNumber 实例中。这是一个有效的属性列表对象。如果您使用 NSPropertyListSerialization 类将其编码为二进制 plist,则应保留所有精度并可供其他机器读取。
一种方法是简单地将字符串表示生成为比浮点类型可以处理的更多有效数字;例如,17 位十进制数字足以准确地表示 IEEE-754 双精度数。这将保证您得到相同的数值(如果您的运行时库的转换例程被正确实现),但缺点是经常给您提供比必要更多的数字。
Python 3.1 最近添加了一种算法,可以将任何浮点值转换为最短的字符串,保证返回到相同的值;如果您有兴趣,可以查看实现。不过,它很毛茸茸。
由于嵌入的零,您不能安全地将双精度复制到“字符串”。假设您的替身在内存中保存为0x43 0x63 0x00 0x00 0x00 0x00 0x00 0x01
... 作为C string
“抄送”,最后一个0x01
是“丢失”。
您可以将 a 转换double
为 aunsigned 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;
}
没有办法做到便携。
上面的解决方案——将双精度重新解释为原始位并打印它们——是一种不错的方法;在相关说明中,如果您需要更高的可读性,您可以将尾数与指数分开并以十进制打印。然而,即使这些也不能保证将双精度打印到完美精度:例如,在 x86 架构上,编译器会经常优化浮点代码以使用 128 位 SSE 寄存器;上面的解决方案强制它进入内存,它将丢失 64 位信息。