我需要扫描到的浮点数与原始值
完全相同,可以通过使用
%a
格式说明符来实现。
此外,打印出的值应该容易被人类读取为浮点数,即我不想打印0x42355316并将其重新解释为32位浮点数。这更加棘手和主观。
%a
生成的字符串的第一部分实际上是由十六进制数字组成的分数,因此输出如
0x1.4p+3
可能需要一些时间才能被人类读者解析为
10
。
一个选项是打印
所有表示浮点值所需的小数位数,但可能有很多位。例如,考虑值0.1,它作为64位浮点数的最接近表示可能是:
0x1.999999999999ap-4 == 0.1000000000000000055511151231257827021181583404541015625
当执行printf("%.*lf\n", DBL_DECIMAL_DIG, 01);
时(例如参考Eric的答案),会打印出
0.10000000000000001 // If DBL_DECIMAL_DIG == 17
我的建议介于两者之间。与%a
类似,我们可以将任何以基数2表示为分数乘以2的整数幂的浮点数精确表示。我们可以将该分数转换为整数(相应地增加指数),并将其作为十进制值打印出来。
0x1.999999999999ap-4 --> 1.999999999999a16 * 2-4 --> 1999999999999a16 * 2-56
--> 720575940379279410 * 2-56
这个整数有限的数字(小于2的53次方),但结果仍然是原始double
值的精确表示。
以下代码片段是概念证明,没有检查角落情况。格式说明符%a
用p
字符(如“...乘以2的Power次方...”)分隔尾数和指数,我将使用一个q
代替,没有特别的原因,只是使用了不同的符号。
尾数的值也将被减少(并相应地提高指数),删除所有尾随的零位。想法是5q+1
(解析为510* 21)应该更容易地被识别为10
,而不是2814749767106560q-48
。
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void to_my_format(double x, char *str)
{
int exponent;
double mantissa = frexp(x, &exponent);
long long m = 0;
if ( mantissa ) {
exponent -= 52;
m = (long long)scalbn(mantissa, 52);
while (m && m % 2 == 0) {
++exponent;
m /= 2;
}
}
sprintf(str, "%lldq%+d", m, exponent);
}
double from_my_format(char const *str)
{
char *end;
long long mantissa = strtoll(str, &end, 10);
long exponent = strtol(str + (end - str + 1), &end, 10);
return scalbn(mantissa, exponent);
}
int main(void)
{
double tests[] = { 1, 0.5, 2, 10, -256, acos(-1), 1000000, 0.1, 0.125 };
size_t n = (sizeof tests) / (sizeof *tests);
char num[32];
for ( size_t i = 0; i < n; ++i ) {
to_my_format(tests[i], num);
double x = from_my_format(num);
printf("%22s%22a ", num, tests[i]);
if ( tests[i] != x )
printf(" *** %22a *** Round-trip failed\n", x);
else
printf("%58.55g\n", x);
}
return 0;
}
请点击这里进行可测试性的测试。
通常来说,阅读体验的改进可以说微乎其微,这当然是个人观点。
%a
格式,它易于阅读且类似于浮点数?或者使用其C++ iostream等效形式? - Adrian Molescanf
中的%a
格式将会把值直接读入一个double
变量。IEEE数字可以被以十六进制浮点格式准确地表示(始终如此),因为没有暗示的十进制转换/从中进行转换。 - Adrian Mole