【问题标题】:Set Precision and Clip Trailing Zeros but Never Print Exponent设置精度和剪辑尾随零但从不打印指数
【发布时间】:2014-08-20 11:38:17
【问题描述】:

我需要:

  1. 设置精度,以便浮点数四舍五入到百分之一(0.111 打印为 0.11)
  2. 剪切尾随零(1.0 打印为 1)
  3. 从不打印指数(1000.1 打印为 1000.1)

printf( "%.2f\n", input ); // handles 1 and 3 but not 2
printf( "%.2g\n", input ); // handles 1 and 2 but not 3
cout << setprecision( 2 ) << input << endl; // handles 1 and 2 but not 3

是否有 printfcout 选项可以让我处理所有这些问题?

【问题讨论】:

  • 我不明白printf( "%.2f\n", 如何在第 3 点失败。当我使用它(Mac OS X 上的 Clang)打印 1E267 或 1E-267 时,它不打印指数。
  • @PascalCuoq 谢谢我更改了订单,忘记更改附加代码。我现在已经修好了。
  • @Jongware 我几乎做了和你一样的事情,然后我认为纯 C 解决方案可能是 OP 可以接受的,即使问题确实包含一些 C++。现在我已经用纯 C 编写了一个解决方案,而您的编辑让我的解决方案看起来离题了。
  • @Pascal:……好吧,同意我太仓促了。 OP 在这方面可能只是更清楚一点。 (我在要求 C++ 时看到海报抱怨,并提交了一个纯 C 解决方案,IMO 确实应该至少一样好。)

标签: c++ c floating-point printf cout


【解决方案1】:

C11 标准提到 %f%F (7.21.6.1:8):

表示浮点数的双精度参数以 [−]ddd.ddd 样式转换为十进制表示法,其中小数点字符后的位数等于精度规范。如果精度缺失,则取6;如果精度为零且未指定 # 标志,则不出现小数点字符。如果出现小数点字符,则在其前面至少出现一位数字。该值被四舍五入到适当的位数。

这是一个 C sn-p,它在 malloced bloc t 中产生你想要的东西,之后你需要释放它。如果你用 C99 写,也可以考虑变长数组。

下面的代码不会对转换引入任何额外的近似值(如果您的 printf 打印正确舍入到十进制的转换,下面的代码也会如此),并且适用于所有浮点值。

#include <stdio.h>
#include <stdlib.h>
…
int len = snprintf(0, 0, "%.2f", input);
if (len < 0) fail();
char *t = malloc((size_t)len + 1);
if (!t) fail();
if (sprintf(t, "%.2f", input) < 0) fail();
len--;
if (t[len] == '0') {
  len--;
  if (t[len] == '0') {
    len--;
    if (t[len] == '.') len--;
  }
  t[len + 1] = '\0';
}

【讨论】:

  • 所以我使用sprintf 的唯一原因是我需要打印一个没有指数的float。我现在正在编写一些 C++ 来完成你在上面使用 C 所做的事情。如果您想以 C++ 风格执行此操作,我会接受您的回答,否则我可以发布我自己的答案。
  • @JonathanMee 抱歉,我无法提供 C++ 答案,但如果您需要一个,您真的没有理由将您的问题标记为“C”。事实上,如果您避免用这两个标签标记您未来的问题,除非它们真的都与您的问题相关,否则这两个标签的追随者(至少是我和 Jongware)会很感激。
  • 看来我用于此问题的任何解决方案都必须使用printf 变体。我不想知道snprintf。我曾希望有一些其他的老派stdio.h 魔法可以解决这个问题,而我不必编写代码来修剪尾随的 0。看起来情况并非如此,但这就是我使用C 标签的理由。
  • @JonathanMee:您是否看到How to implement "char *ftoa(float num" without sprintf() library function 的非常棒的答案(至少其中一些)?您可能可以根据自己的喜好调整其中一个。
  • @Jongware 可以在 musl 库中找到优雅、简约、正确舍入的解决方案(与您链接到的问题中的所有可疑答案不同):git.musl-libc.org/cgit/musl/tree/src/internal/floatscan.c
【解决方案2】:

我不知道有任何格式说明符可以满足您的要求。

在将值传递给单独的格式说明符之前预先消化这些值可能会起作用。例如:

  • 将原始浮点数乘以 100 并四舍五入到最接近的整数
  • 分配给 nScaled (int)。
  • 将 mod(nScaled,100) 分配给另一个整数 nFractional。
  • 将 nScaled/100 分配给另一个整数 nWhole。

if( nFractional > 0 )
  printf("%d.%d", nWhole, nFractional );
else
  printf("%d", nWhole );

你可能已经知道了。

【讨论】:

  • 这个问题是乘以 100 可能不精确,并且结果不必适合 int 或任何可用的整数类型。您不妨从使用%f 打印浮点数的字符串中删除尾随零和点,至少它适用于所有数字。
  • 当 1 ≤ nFractional ≤ 9 时也会产生错误的结果。
【解决方案3】:

我认为另一种选择是使用 asprintf() 函数:它自己动态分配适当长度的字符串。一旦字符串被存储尾随零/点可以被切断:

...
float num;
...

char *digits;
int i=asprintf(&digits, "%.2f", num)-1;

for(; digits[i] !='.'; i--)
  if (digits[i] == '0') digits[i] = NULL; else break;

if (digits[i] == '.') digits[i] = NULL;

printf("%s\n", digits);
free(digits);
...

【讨论】:

  • 这可以在 Linux 平台上运行。但这确实不如@PascalCuoq 或我的解决方案,因为它缺乏可移植性。
【解决方案4】:

遗憾的是,没有办法强制流使用printf%f 行为。因此,处理此问题的唯一方法是根据需要手动修剪小数位。我添加了一个处理此问题的 C++ 代码示例:

string fullfloat(static_cast<size_t>(log10(numeric_limits<declval(input)>::max())) + 5U, '\0'); // Adding 1 for the 10s place, 1 for the '.' 2 for the decimal places, and 1 for the null
const size_t length = size(fullfloat) - sprintf(data(fullfloat), "%.2f", input );

*(--mismatch(rbegin(fullfloat) + length, next(rbegin(fullfloat), 3U + length), "00.").first) = '\0';

fullfloat 现在将包含正确的字符串,但因为它的大小将超出应用的'\0' 字符,所以让它打印属性的唯一方法是使用data()

cout << data(fullfloat);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-16
    • 1970-01-01
    • 1970-01-01
    • 2015-04-24
    • 2020-04-22
    • 2014-08-22
    • 2013-02-18
    相关资源
    最近更新 更多