浮点数和双精度值的可能最长的 %a 字符串?

Longest possible %a string for floats and doubles?

提问人:Costava 提问时间:4/24/2023 更新时间:4/24/2023 访问量:118

问:

将说明符与函数系列一起使用时,浮点数和双精度值的最长字符串是多少?%aprintf

假设 float 和 double 是 IEEE-754 的 32 位和 64 位浮点。

更具体地说,以下函数需要多大才能不被超载(请注意,这也会写一个 null 终止符):bufbufsprintf

#include <stdio.h>

void StringFromFloat(char *buf, float f) {
    sprintf(buf, "%a", f);
}

void StringFromDouble(char *buf, double d) {
    sprintf(buf, "%a", d);
}

我假设最大长度没有区别。%A

C 浮点 打印f IEEE-754

评论

1赞 Andrew Henle 4/24/2023
如果您不知道最大长度,切勿使用。您知道缓冲区的大小,因此请使用 .或者用于获取长度和适当长度的缓冲区。(在 Linux 上使用 ...sprintf()snprintf()snprintf()malloc()asprintf()
1赞 chux - Reinstate Monica 4/24/2023
@AndrewHenle 使用代替是合理的,但还不够。在不检查其返回值的情况下使用会将问题推向前进的方向。一个想法会使用或可能不那么严重的东西。IAC,分配方法当然是合理的,它不关心动态分配开销。也许是一个 VLA?对我来说,使用肯定会处理迂腐的情况。最佳答案取决于更大的未发布上下文。snprintf()sprintf()snprintf()volatile int len = snprintf(...,n,...); assert(len >= 0 && (unsigned) len < n);char buf[sizeof(double)*CHAR_BIT + 1];
0赞 Andrew Henle 4/24/2023
@chux-恢复莫妮卡 所有优点。不过,我并没有在我的评论中投入太多精力。我喜欢做的一件事是 - 在“获取必要缓冲区的大小”第一次调用时使用具有固定大小的中等大小的缓冲区。如果它确实有效,并且缓冲区足够大,则无需运行两次,这可能是一项代价高昂的操作。snprintf()snprintf()snprintf()
0赞 chux - Reinstate Monica 4/24/2023
@AndrewHenle我曾经试图找到一个最小的最大尺寸(正如 OP 显然想要的那样)。现在我用 ,称它为好,然后继续前进。char buf[sizeof floating_point_object * CHAR_BIT + 1];

答:

3赞 Mooing Duck 4/24/2023 #1
int main() {
    int c;
    printf("%a%n\n", -M_PI, &c);
    printf("%d\n", c+1);
    printf("%a%n\n", -DBL_MAX, &c);
    printf("%d\n", c+1);
    printf("%a%n\n", -DBL_MIN, &c);
    printf("%d\n", c+1);
}

发出

-0x1.921fb54442d18p+1
22
-0x1.fffffffffffffp+1023
25
-0x1p-1022
11

https://coliru.stacked-crooked.com/a/9c8e5f7a059b66a5

如果我们假设格式是标准的,并且 dobules 是 IEEE-754 64 位,那么答案似乎是长度为 25(包括尾随的 null)

IEEE-754 64 位类型使用符号位(1 个字符)、52 位尾数(13 个十六进制字符)和 11 位指数(5 个十六进制字符)。这与上面的结果(、 和 )相匹配,这意味着其他 6 个字节是 ~标准格式(、 和-fffffffffffff+10230x1.p'\0')

评论

1赞 Costava 4/24/2023
然后,对于使用符号位(1 个字符)、23 位尾数(6 个十六进制字符)和 8 位指数(4 个字符,通过 3 位十进制数加一个符号字符)的 32 位类型,最大长度为 1 + 6 + 4 + 6 = 17?
1赞 Costava 4/24/2023
将您的数字相加,64 位类型的最大长度是 1 + 13 + 5 + 6 = 25(包括 null 终止符),而不是 26?
2赞 aka.nice 4/24/2023
请注意,指数是以十进制格式编写的,而不是十六进制格式。11 位仍然是 5 个字符(4 位小数 + 1 表示指数符号)。
0赞 Mooing Duck 4/24/2023
@Costava:很好奇。你是对的,在输出中我只看到 24 个字符(并且没有看到 null)。我不确定为什么报告了 25 个。🤔%n
1赞 chux - Reinstate Monica 4/24/2023
“我不确定为什么 %n 报告了 25。” --> 写了 24 in - 正如预期的那样。代码打印 。"%n"cc + 1
2赞 chux - Reinstate Monica 4/24/2023 #2

最长的字符串
是多少,需要多大
buf

通常,对字符串的长度进行评估,所需的缓冲区大小长度多 1。最大长度将在下面讨论。为尺寸添加一个。


"%a"具有各种实现定义的详细信息。因此,代码在根据以下计算假设最大长度时应非常小心。谨慎的代码将处理一些额外的字符。

合理估计:以下各项的总和:

  • 最长的有效长度,也许是 -1.xxx 的形式......xxx,其中 x 是十六进制数字。若:。FLT_RADIX==21 /* sign */ + 2 /* 0x */ + 1 /* lead digit */ + 1 /* . */ + roundup((xxx_MANT_DIG-1)/4)

  • 最长指数,它是值或 的十进制 2 的幂。xxx_TRUE_MIN1 /* p */ + 1 /* sign */ + roundup(log2(-xxx_MIN_EXP + xxx_MANT_DIG))


对于普通双人间

significand length: `1 + 2 + 1 + 1 + ru((53-1)/4)` --> 18

exponent length: `1 + 1 + ru(log10(- -1021 + 53))` --> 6

sum: 24

对于普通浮点:

significand length: `1 + 2 + 1 + 1 + ru((24-1)/4)` --> 11

exponent length: `1 + 1 + ru(log10(- -125 + 24))` --> 5

sum: 16

考虑 NAN的有效负载可能以某种有趣的方式格式化,超过了上述总和。

示例:NAN w/payload: .C 指定有效负载和字符:[0-9A-Za-z_],但对其长度保持沉默。一种实现使用有效负载作为十进制值。double"-NAN4503599627370495"

1 /* sign */ + 3 /* NAN */ + 16 /* decimal digits = 20

合理的 52 位有效负载应不超过 52 个有效负载字符。我从未见过比上述 20 个更长的。

1 /* sign */ + 3 /* NAN */ + 52 /* binary digits = 56

C 规范 关于 , :"%a""%A"

表示浮点数的参数以 [-]0xh.hhhhp±d 样式进行转换,其中小数点前有一个十六进制数字(如果参数是规范化浮点数且未指定,则为非零) character)和等于精度后的十六进制位数;如果精度缺失且为 2 的幂,则精度足以精确表示值;如果精度缺失且不是幂 的 2,则精度足以区分 类型的值,但尾随零可以省略;如果精度为零且未指定 # 标志,则不显示小数点字符。字母 abcdef 用于转换,字母 ABCDEF 用于 A 转换。A 转换说明符生成一个带有 X 和 P 的数字,而不是 x 和 p。指数始终包含至少一个数字,并且仅包含表示 2 的十进制指数所需的更多数字。如果该值为零,则指数为零。
表示无穷大或 NaN 的参数以 f 或 F 转换说明符的样式进行转换。
C17dr § 7.21.6.1 8
doubleFLT_RADIXFLT_RADIXdoubledouble

评论

0赞 Sam Mason 4/24/2023
不确定是否需要指数计算。对我来说,非正态数字以前导零呈现,这似乎是合适的,因为它们是这样表示的。例如 呈现为 .MANT_DIGpow(2, -1074)0x0.0000000000001p-1022
0赞 chux - Reinstate Monica 4/24/2023
@SamMason 这是一个实现细节。输出可能和你看到的一样。C 的规范在这里关于第一个数字是否为零以及尾随零是否被删除是松散的。这两种变体都是允许的。由于我们正在寻找最坏的情况,因此我强调各种合理的实现可能做到的最坏情况。"0x1.0000000000000p-1074"fprintf()
0赞 Sam Mason 4/24/2023
AFAICT整个问题是一个实施细节!en.cppreference.com/w/c/io/fprintf 确实提到了非正态数的前导零位,但不确定从哪里看出这是从哪里来的
1赞 chux - Reinstate Monica 4/24/2023
@SamMason “这是从哪里来的” --> Spec 发布。
0赞 chux - Reinstate Monica 4/24/2023 #3

以下函数需要多大才能不被超支?bufbuf

请考虑不要尝试查找最小最大缓冲区大小,而是将大小传递到函数中,并防止任何缓冲区溢出。@Andrew亨勒snprintf()

void StringFromFloat(size_t sz, char *buf, float f) {
  volatile int len = snprintf(buf, sz, "%a", f);
  assert(len >= 0 && (unsigned) len < sz);
}

如果调用代码想要传入被认为是始终有效的缓冲区大小,请使用各种宏计算预期的最大大小。
有关,请参阅以获取更多想法。
LOG10_PR()

#include <float.h>
#define LOG10_PR(x) (1 + (x)>9 + (x)>99 + (x)>999 + (x)>9999 + (x)>99999) 
#define PRINTF_A_SIZE_FLT ( \
    5 /* -0x1. */ + (FLT_MANT_DIG-1+3)/4 + \
    2 /* p- */ + LOG10_PR(FLT_MANT_DIG - FLT_MIN_EXP) + \
    1 /* \0 */)

char buf[PRINTF_A_SIZE_FLT];
StringFromFloat(sizeof buf, buf, some_float);

或者,如果不需要几个额外的字节,则要慷慨大方,并使用对象的位宽来缩放缓冲区大小。

char buf[sizeof some_float * CHAR_BIT + 1];
StringFromFloat(sizeof buf, buf, some_float);