sprintf 格式:固定字符数,可变小数位数
sprintf formatting: fixed number of characters, variable numbers of decimals
我 looking 想要一个类似 sprintf
的格式字符串,它可以用 可变 小数位数来格式化浮点数,但是 fixed 字符总数(比如说,3),以提供最大的信息。例如:
0 -> `.00` or `0.0` or ` 0`
0.124 -> `.12`
0.357 -> `.36`
1.788 -> `1.8`
9.442 -> `9.4`
10.25 -> `10.` or ` 10`
75.86 -> `76.` or ` 76`
99.44 -> `99.` or ` 99`
100.0 -> `100`
(是的,我的数字都是 0 到 100 的浮点数)
如何实现?
sprintf格式化字符串语言实现的就是这种定宽格式化吗?
你或许可以试试:
void mysprintf(float a) {
if (a < 10 && a >= 0) {
printf("%2f", a);
} else if (a >= 10 && a < 100) {
printf("%1f", a);
} else {
printf("%d", (int)a);
}
}
Is this kind of fixed-width formatting implemented in sprintf format string language?
没有
How to achieve this?
为了满足 OP 的目标,代码可以分析 float f
的范围以尝试打印最大信息。
这里以"%.*f"
开头,控制.
后面的位数。
一个常见的陷阱发生在值略低于 10 的幂时,它会四舍五入并导致输出中出现另一个数字。
代码可以尝试针对 f < 0.995f
而不是 f < 1.0
进行测试,但是鉴于浮点的二进制性质,这会导致失败的极端情况。
最好根据 1.0、10.0 等精确常数测试范围...
下面最多尝试两次 sprintf()
。第二次尝试使用那些讨厌的值,刚好低于 10 的幂。
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
void iago_sprint1(char *dest, int len, float f) {
if (f < 1.0) {
char buf[len + 2];
//printf("p1\n");
snprintf(buf, sizeof buf, "%.*f", len - 1, f);
if (buf[0] == '0') {
strcpy(dest, buf + 1);
return;
}
}
float limit = 10.0;
int prec = len - 2;
while (prec >= -1) {
if (f < limit) {
char buf[len + 2];
//printf("p2\n");
int cnt = snprintf(buf, sizeof buf, "%.*f", prec < 0 ? 0: prec, f);
if (cnt <= len) {
strcpy(dest, buf);
return;
}
}
prec--;
limit *= 10;
}
assert(0); // `f` was out of range
}
#define N 3
int main(void) {
float f[] = {0, 0.124f, 0.357f, 1.788f, 9.442f, 10.25f, 75.86f, 99.44f,
100.0f, 99.999f, 9.9999f, .99999f, .099999f, 1.04f, 1.06f};
char line[N + 1];
for (unsigned i = 0; i < sizeof f / sizeof *f; i++) {
//snprintf(line, sizeof line, "%#*g", N, f[i]);
//puts(line);
iago_sprint1(line, N, f[i]);
puts(line);
}
}
输出
.00
.12
.36
1.8
9.4
10
76
99
100
100
10
1.0
.10
1.0
1.1
预计算精度需要坑
代码是否应该尝试预先计算 只有 1 sprintf()
调用所需的精度,计算需要像 sprintf()
一样推断精度 -实际上,代码正在重新执行 sprint()` 工作。
考虑 len==3
和 float x = 9.95f;
。由于二进制 float
并不完全表示,它的值恰好高于或低于 9.951。如果在下方,则字符串应为 "9.9"
,如果在上方,则应为 "10."
。如果代码有 double x = 9.95;
(同样不能完全表示),输出可能会有所不同。如果代码使用 float
,但使用 FLT_EVAL_MODE > 1
,实际传递的值可能不是预期的 float 9.95
。
Precision--> .1 .0
9.94001... "9.9" "10."
9.94999... "9.9" "10." // Is this 9.95f
9.95001... "10.0" "10." // or this?
9.95999... "10.0" "10."
新的和改进的
我重新编写并简化了代码 - 接受后。
技巧 是根据 f
的 10 次方计算出的精度 "%*.f"
打印到缓冲区 比目标宽 - 假设由于四舍五入没有进位到另一个数字。
10 的幂计算可以用一个小循环精确完成。
当前导字符是 0
时,不需要像 "0.xx"
--> ".xx"
.
否则由于四舍五入而没有进位到另一个数字,字符串适合并且我们完成了。
否则是进位,那么最后一个字符是小数点后的 '.'
或 '0'
,因此不需要。当 f
刚好低于 10 的幂时会发生这种情况,但打印版本四舍五入为 10 的幂。因此只复制长度为 1 的数字。
// `len` number of characters to print
// `len+1` is the size of `dest`
void iago_sprint3(char *dest, int len, float f) {
assert(len >= 1);
int prec = len - 1;
float power10 = 1.0;
while (f >= power10 && prec > 0) {
power10 *= 10;
prec--;
}
char buf[len + 2];
int cnt = snprintf(buf, sizeof buf, "%.*f", prec, f);
assert (cnt >= 0 && cnt <= len + 1);
if (buf[0] == '0') {
strcpy(dest, buf + 1);
return;
}
strncpy(dest, buf, (unsigned) len);
dest[len] = 0;
}
1 典型的 float 9.95f
正好是
9.94999980926513671875
典型的 double 9.95
正好是
9.949999999999999289457264239899814128875732421875
我 looking 想要一个类似 sprintf
的格式字符串,它可以用 可变 小数位数来格式化浮点数,但是 fixed 字符总数(比如说,3),以提供最大的信息。例如:
0 -> `.00` or `0.0` or ` 0`
0.124 -> `.12`
0.357 -> `.36`
1.788 -> `1.8`
9.442 -> `9.4`
10.25 -> `10.` or ` 10`
75.86 -> `76.` or ` 76`
99.44 -> `99.` or ` 99`
100.0 -> `100`
(是的,我的数字都是 0 到 100 的浮点数)
如何实现?
sprintf格式化字符串语言实现的就是这种定宽格式化吗?
你或许可以试试:
void mysprintf(float a) {
if (a < 10 && a >= 0) {
printf("%2f", a);
} else if (a >= 10 && a < 100) {
printf("%1f", a);
} else {
printf("%d", (int)a);
}
}
Is this kind of fixed-width formatting implemented in sprintf format string language?
没有
How to achieve this?
为了满足 OP 的目标,代码可以分析 float f
的范围以尝试打印最大信息。
这里以"%.*f"
开头,控制.
后面的位数。
一个常见的陷阱发生在值略低于 10 的幂时,它会四舍五入并导致输出中出现另一个数字。
代码可以尝试针对 f < 0.995f
而不是 f < 1.0
进行测试,但是鉴于浮点的二进制性质,这会导致失败的极端情况。
最好根据 1.0、10.0 等精确常数测试范围...
下面最多尝试两次 sprintf()
。第二次尝试使用那些讨厌的值,刚好低于 10 的幂。
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
void iago_sprint1(char *dest, int len, float f) {
if (f < 1.0) {
char buf[len + 2];
//printf("p1\n");
snprintf(buf, sizeof buf, "%.*f", len - 1, f);
if (buf[0] == '0') {
strcpy(dest, buf + 1);
return;
}
}
float limit = 10.0;
int prec = len - 2;
while (prec >= -1) {
if (f < limit) {
char buf[len + 2];
//printf("p2\n");
int cnt = snprintf(buf, sizeof buf, "%.*f", prec < 0 ? 0: prec, f);
if (cnt <= len) {
strcpy(dest, buf);
return;
}
}
prec--;
limit *= 10;
}
assert(0); // `f` was out of range
}
#define N 3
int main(void) {
float f[] = {0, 0.124f, 0.357f, 1.788f, 9.442f, 10.25f, 75.86f, 99.44f,
100.0f, 99.999f, 9.9999f, .99999f, .099999f, 1.04f, 1.06f};
char line[N + 1];
for (unsigned i = 0; i < sizeof f / sizeof *f; i++) {
//snprintf(line, sizeof line, "%#*g", N, f[i]);
//puts(line);
iago_sprint1(line, N, f[i]);
puts(line);
}
}
输出
.00
.12
.36
1.8
9.4
10
76
99
100
100
10
1.0
.10
1.0
1.1
预计算精度需要坑
代码是否应该尝试预先计算 只有 1 sprintf()
调用所需的精度,计算需要像 sprintf()
一样推断精度 -实际上,代码正在重新执行 sprint()` 工作。
考虑 len==3
和 float x = 9.95f;
。由于二进制 float
并不完全表示,它的值恰好高于或低于 9.951。如果在下方,则字符串应为 "9.9"
,如果在上方,则应为 "10."
。如果代码有 double x = 9.95;
(同样不能完全表示),输出可能会有所不同。如果代码使用 float
,但使用 FLT_EVAL_MODE > 1
,实际传递的值可能不是预期的 float 9.95
。
Precision--> .1 .0
9.94001... "9.9" "10."
9.94999... "9.9" "10." // Is this 9.95f
9.95001... "10.0" "10." // or this?
9.95999... "10.0" "10."
新的和改进的
我重新编写并简化了代码 - 接受后。
技巧 是根据 f
的 10 次方计算出的精度 "%*.f"
打印到缓冲区 比目标宽 - 假设由于四舍五入没有进位到另一个数字。
10 的幂计算可以用一个小循环精确完成。
当前导字符是 0
时,不需要像 "0.xx"
--> ".xx"
.
否则由于四舍五入而没有进位到另一个数字,字符串适合并且我们完成了。
否则是进位,那么最后一个字符是小数点后的 '.'
或 '0'
,因此不需要。当 f
刚好低于 10 的幂时会发生这种情况,但打印版本四舍五入为 10 的幂。因此只复制长度为 1 的数字。
// `len` number of characters to print
// `len+1` is the size of `dest`
void iago_sprint3(char *dest, int len, float f) {
assert(len >= 1);
int prec = len - 1;
float power10 = 1.0;
while (f >= power10 && prec > 0) {
power10 *= 10;
prec--;
}
char buf[len + 2];
int cnt = snprintf(buf, sizeof buf, "%.*f", prec, f);
assert (cnt >= 0 && cnt <= len + 1);
if (buf[0] == '0') {
strcpy(dest, buf + 1);
return;
}
strncpy(dest, buf, (unsigned) len);
dest[len] = 0;
}
1 典型的 float 9.95f
正好是
9.94999980926513671875
典型的 double 9.95
正好是
9.949999999999999289457264239899814128875732421875