提问人:pmor 提问时间:9/22/2023 最后编辑:pmor 更新时间:9/26/2023 访问量:86
如何计算转换规范 %.*f 的精度以保持浮点值的精度?
How to compute precision for conversion specification %.*f to maintain precision of floating-point value?
问:
注意:这个问题源于这个答案。
如何计算转换规范的精度以保持浮点值的精度?%.*f
注意:这里的“保持精度”是指在读取打印值后(例如,通过或按),结果值等于原始值(NaNs除外)(用于具有转换规范的打印)。strtod
scanf
%.*f
答:
一个简单的解决方案是通过 x 波谷,然后(产生 y),直到 x = y:sprintf
strtod
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define PREC_MAX 340
void print_f(double x, bool newline)
{
double y;
char s[PREC_MAX * 3]; // what is the precise multiplier ?
bool f_printed = false;
if (isnan(x) || isinf(x))
{
printf("%f", x);
}
else
{
for (int prec = 1; prec < PREC_MAX; ++prec)
{
if (sprintf(s, "%.*f", prec, x) > 0) // may encoding error occur ?
{
y = strtod(s, 0); // is there a real need of error handling ?
if (x == y)
{
printf("%s", s);
f_printed = true;
break;
}
}
}
if (!f_printed)
{
printf("%.*g", DBL_DECIMAL_DIG, x);
}
}
if (newline)
{
printf("\n");
}
}
int main(void)
{
print_f(0.000000000000000000000001617, true); // 0.000000000000000000000001617
print_f(1.0, true); // 1.0
print_f(0x1p-27, true); // 0.000000007450580596923828
print_f(NAN, true); // nan
print_f(INFINITY, true); // inf
}
可能有一个“无循环”的常数时间复杂度解决方案,这很有趣。额外的问题:这个解决方案可以得到正式证明吗?
额外:scanf(
) 和 strtol() / strtod()
在解析数字方面的区别。
评论
print_f(-DBL_TRUE_MIN, 0)
DBL_DECIMAL_DIG - DBL_MIN_10_EXP
(DBL_DECIMAL_DIG - lround(log10(fabs(x))))
要通过(二进制浮点)值往返于同一值的十进制文本,最多需要(通常为 17 位)有效数字。double
"%.*f"
DBL_DECIMAL_DIG
DBL_DECIMAL_DIGITS
十进制位数 n 使得任何具有 p 基数 b 位数的浮点数都可以四舍五入为具有 n 个十进制数字的浮点数,然后再次返回,而无需更改值,
C23dr § 5.2.4.2.2 24
任何值大小 >= 10DBL_DECIMAL_DIG - 1 打印将至少打印数字。只有小于此值的值才可能需要小数点后的一些数字。"%.0f"
DBL_DECIMAL_DIG
int prec = DBL_DECIMAL_DIG - log10(fabs(x));
if (prec < 0) {
prec = 0;
}
printf("%.*f\n", prec, x);
需要注意,因为非常接近 10 的幂的值可能会产生计算错误,从而导致差 1 的误差。最好是四舍五入,并且可能产生 +1 精度。
int prec = DBL_DECIMAL_DIG - log10(fabs(x))
选择值可以很容易地用更少的数字通过。可以尝试逐步降低精度。
double
无穷大、NaN 和零可能需要特殊处理。
关于的值可能需要最长的字符串。这是关于.
-DBL_TRUE_MIN
2 /* "-0." */ - DBL_MIN_10_EXP + DBL_DECIMAL_DIG + 1 /* \0 */
要找到最佳的最小格式精度,请执行以下操作:
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
// Buffer size needed for all `double`
#define BUF_N (3 /* "-0." */ - DBL_MIN_10_EXP + DBL_DECIMAL_DIG + 1 /* \0 */)
// Untested code. Grandparent duty calls.
// Needs review for off-by-1 errors.
int round_trip_precision_min(double x) {
if (!isfinite(x) || x == 0.0) {
return 0;
}
char buf[BUF_N + 10]; // 10 extra for margin
int prec = (int) (DBL_DECIMAL_DIG - lround(log10(fabs(x))));
if (prec < 0) {
prec = 0;
}
// Try with less precision
while (prec > 0) {
sprintf(buf, "%.*f", prec - 1, x);
if (atof(buf) != x) {
break;
}
prec--;
}
return prec;
}
评论
float
FLT_
下一个:带有浮点数的 std::cout
评论
sprintf
sscanf
DBL_TRUE_MIN
double
double
float
x
x
printf
%.*f
scanf
x