Ern*_*ldo 3 c string floating-point
我想知道实现这一目标的最简单、最便携且普遍认为的最佳实践,适用于任何数字。我还希望与数字关联的字符串采用十进制表示形式,如果可能的话,不使用科学记数法。
Ste*_*mit 10
有两个问题:
\n你说你想尽可能避免使用科学记数法,这很好,但是打印像 0.00000000000000000123 或 12300000000000000000 这样的数字有点不合理,所以你可能想切换到科学记数法来表示非常大或非常小的数字。
\n碰巧,有一种 printf 格式可以做到这一点:%g. 它的行为就像%f是可以的话,但%e如果必须的话就会切换到。
还有位数的问题。float您需要足够的数字来保留or值的内部精度double。长话短说,您想要的位数是预定义的常数FLT_DECIMAL_DIG或DBL_DECIMAL_DIG。
因此,将所有这些放在一起,您可以将 a 转换float为字符串,如下所示:
sprintf(str, "%.*g", FLT_DECIMAL_DIG, f);\nRun Code Online (Sandbox Code Playgroud)\na 的技术double完全类似:
sprintf(str, "%.*g", DBL_DECIMAL_DIG, d);\nRun Code Online (Sandbox Code Playgroud)\n在这两种情况下,我们都使用间接技术来告诉%g我们需要多少个有效数字。我们本来可以%g让它选择,或者我们可以使用类似的方法%.10g来请求 10 个有效数字,但这里我们使用%.*g,其中*表示使用传入的参数来指定有效数字的数量。这让我们可以插入准确的值FLT_DECIMAL_DIG或DBL_DECIMAL_DIG来自 的值<float.h>。
(还有一个问题是您可能需要多大的字符串。下面将详细介绍。)
\n然后你可以使用, , floator从字符串转换回 a或:doubleatofstrtodsscanf
f = atof(str);\nd = strtod(str, &str2);\nsscanf(str, "%g", &f);\nsscanf(str, "%lg", &d);\nRun Code Online (Sandbox Code Playgroud)\n(顺便说一句,scanf朋友们并不那么关心 \xe2\x80\x94 的格式,你可以使用%e, %f, 或%g,它们的工作方式完全相同。)
这是一个将所有这些结合在一起的演示程序:
\n#include <stdio.h>\n#include <stdlib.h>\n#include <float.h>\n\nint main()\n{\n double d1, d2;\n char str[DBL_DECIMAL_DIG + 10];\n\n while(1) {\n printf("Enter a floating-point number: ");\n fflush(stdout);\n\n if(scanf("%lf", &d1) != 1) {\n printf("okay, we\'re done\\n");\n break;\n }\n\n printf("you entered: %g\\n", d1);\n\n snprintf(str, sizeof(str), "%.*g", DBL_DECIMAL_DIG, d1);\n\n printf("converted to string: %s\\n", str);\n\n d2 = strtod(str, NULL);\n\n printf("converted back to double: %g\\n", d2);\n\n if(d1 != d2)\n printf("whoops, they don\'t match!\\n");\n\n printf("\\n");\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n该程序提示输入 double value d1,将其转换为字符串,再将其转换回 double d2,并检查以确保值匹配。有几点需要注意:
char str[DBL_DECIMAL_DIG + 10]该代码为转换后的字符串选择一个大小。对于数字、符号、指数和终止符“\\0”来说,这应该足够了。snprintf而不是sprintf,以便可以传入目标缓冲区大小,以确保它不会溢出,如果由于某种意外它不够大的话。d1不完全等于d2,则说明出现了问题。d1 == d2,但它悄悄地掩盖了这样一个事实:d1可能并不完全等于您输入的数字!大多数实数(和大多数小数)不能精确地表示为有限精度float或double值。如果您输入看似“简单”的分数(例如 0.1 或 123.456),d1则不会得到确切的值。 d1将是一个非常接近的近似值 \xe2\x80\x94 ,然后,假设其他一切正常工作,d2最终将包含完全相同的非常接近的近似值。要了解这里到底发生了什么,您可以提高“您输入”和“转换回双精度”行打印的精度。另请参阅浮点数学是否已损坏?%g当我们说%.10gor \xe2\x80\x94 时我们给出的精度是有效数字%.*g位数。它不仅仅是小数点后位数的计数。例如,数字 1234000、12.34、0.1234 和 0.00001234 都有四位有效数字。上面我说过,“长话短说,你想要的位数是预定义的常数FLT_DECIMAL_DIG或DBL_DECIMAL_DIG。” 这些常量实际上是获取内部浮点值、将其转换为十进制(字符串)表示形式、将其转换回内部浮点值并获取完全相同的值所需的最小有效位数。显然这正是我们想要的。还有另一对看似相似的常量,FLT_DIG它DBL_DIG给出了在从外部十进制(字符串)表示形式转换为内部浮点值时保证保留的最小位数,并且然后回到十进制。对于典型的 IEEE-754 实现,FLT_DIG/DBL_DIG是 6 和 15,而FLT_DECIMAL_DIG/DBL_DECIMAL_DIG是 9 和 17。有关更多信息,请参阅此 SO 答案。
FLT_DECIMAL_DIG和DBL_DECIMAL_DIG是保证往返二进制到十进制到二进制转换所需的最小位数,但它们不一定足以精确显示实际的内部二进制值是什么。对于那些,您可能需要与有效数中的二进制位一样多的十进制数字。例如,如果我们从十进制数 123.456 开始,并将其转换为float,我们会得到类似 123.45600128... 的结果。如果我们打印FLT_DECIMAL_DIG9 位有效数字,我们会得到 123.456001,然后转换回 123.45600128...,所以我们成功了。但实际的内部值以7b.74bc816 为基数,即1111011.01110100101111001二进制,有 24 个有效位。这些数字的实际全精度十进制转换为 123.45600128173828125。
附录:\n必须注意的是,以这种方式将浮点值准确地传输为十进制字符串绝对需要:
\nsprintf %g)。将 N 位转换为 M 位时,它们必须始终是 M 个适当舍入的数字。FLT_DECIMAL_DIG或DBL_DECIMAL_DIG,如上所述)。strtod())。将 N 位数字转换为 M 位时,它们必须始终是 M 个适当舍入的位。IEEE-754 标准确实需要属性 (1) 和 (3)。但不符合 IEEE-754 的实现可能效果不佳。(事实证明,属性(1)尤其难以实现,尽管现在已经很好地理解了实现这一点的技术。)
\n附录 2:\n我使用上述程序的修改进行了实证测试,循环遍历许多值,而不仅仅是从用户扫描的单个值。\n在这个“回归测试”版本中,我已经替换了测试
\nif(d1 != d2)\n printf("whoops, they don\'t match!\\n");\nRun Code Online (Sandbox Code Playgroud)\n和
\nif(d1 != d2 && (!isnan(d1) || !(isnan(d1) && isnan(d2))))\n printf("whoops, they don\'t match!\\n");\nRun Code Online (Sandbox Code Playgroud)\n(也就是说,当数字不匹配时,仅当其中一个不是NaN时才会出错。)
\n无论如何,我已经测试了 4,294,967,296 个类型的值float。\n我已经测试了 100,000,000,000 个随机选择的类型值double(公平地说,只是其中的一小部分)。\n不是一次(除了故意引起的错误之外,要测试测试)我看到它打印“哎呀,它们不匹配!”。
| 归档时间: |
|
| 查看次数: |
419 次 |
| 最近记录: |