【问题标题】:sprintf formatting: fixed number of characters, variable numbers of decimalssprintf 格式:固定字符数,可变小数位数
【发布时间】:2019-04-17 04:37:34
【问题描述】:

我是lookingsprintf-like 格式字符串,它将用可变小数位数格式化浮点数,但固定字符总数(比如说,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格式字符串语言实现的吗?

【问题讨论】:

  • .00 , 1.5 和 200 不共享一些共同点。您希望输出为字符串吗?
  • @TsakiroglouFotis 他们分享说他们都是 3 个字符长。是的,输出是一个字符串:) 我需要自己编写方法吗?是否超出了 sprintf 的能力?
  • 研究"%3g",因为这似乎可以让你接近你的目标。
  • @chux 正如我所写,我的数字范围仅从 0 到 100,我同意在尝试仅用 3 个字符压缩 -999 时,关键信息丢失是不可避免的。
  • @chux "%3g" 确实很有趣,但不完全是我需要的。也许我需要的格式字符串不存在?

标签: c format printf number-formatting


【解决方案1】:

这种定宽格式是用sprintf格式字符串语言实现的吗?

没有。

如何做到这一点?

为了达到 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==3float x = 9.95f;。由于二进制 float 并不能准确表示,它的值正好高于或低于 9.951。如果在下方,则字符串应为"9.9",如果在上方,则应为"10."。如果代码有double x = 9.95;(同样不能完全表示),输出可能会有所不同。如果代码使用float,但使用FLT_EVAL_MODE &gt; 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" 计算出的精度,该精度基于f 的 10 次方,比目标宽一倍 -假设由于四舍五入没有进位。

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

【讨论】:

  • 太棒了,干杯 :) 我怀疑我必须自己写它,但没想到它会是这么大的工作。再次感谢你!我没听懂你关于单调的说法,这个舍入函数肯定是单调的,对吧?
  • 通过大量输入工作更简单。我真的很感谢你的努力:)
【解决方案2】:

你可以试试:

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);
    }
}

【讨论】:

  • 好吧,你的意思是没有sprintf格式的字符串来实现我需要的,我需要自己格式化字符串,对吧? :)
  • 这是针对您的情况的自定义 sprintf :)
  • 我明白了。干杯:) 不要犹豫,充实你的答案。另外,由于问题是关于 sprintf 格式化字符串而不是专用方法,您确定没有 fstring 这样 printf(fstring, a) 总是会产生所需的输出吗?
  • 如果你真的这么认为,那么我认为值得在你的回答中明确说明:)
  • printf("%d",a); 是未定义的行为,printf("%d", (int)a); 会起作用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多