【问题标题】:Output base-10 fixed point number in C在 C 中输出以 10 为底的定点数
【发布时间】:2020-08-31 20:18:06
【问题描述】:

我有一个整数n 代表一个数字乘以1e<exp> 得到某个常数<exp>。例如,对于 12.345 的输入数字和 4 的 <exp>n = 123450。我想尽快输出没有尾随零的原始数字。

一些潜在的(但有缺陷的)解决方案:

// Pros:
//  - Very simple
//  - Does not output trailing zeros
// Cons:
//  - Converts to float (slow, inaccurate)
printf("%g", n * 1e-<exp>);
// Pros:
//  - Quite simple
//  - Uses only integer arithmetic
// Cons:
//  - Outputs trailing zeros
printf("%d.%.0<exp>d", n / (int)1e<exp>, n % (int)1e<exp>);

【问题讨论】:

  • 我们调用 e。 e的范围是多少?会不会是 n•10^-e 小于 1?小于 0.1?会不会是负面的? n的范围是多少?会不会是负面的?会不会是零?如果 e 是一个常数,您是否希望它的每个可能值都使用不同的代码,如果这样可以缩短时间?如果数字是 100,你想要“100.”、“100”还是“1”(没有尾随零)?
  • @EricPostpischil 在这种特定情况下,n 将是正数并且 >=1,但是更通用的解决方案将是理想的。理想情况下,该解决方案可以很容易地修改为不同的 e 值,但不会以速度为代价。如果数字是 100,则输出应该是“100”或“100.0”,尽管是“100”。如果它提供显着的速度增加是可以接受的
  • 请回答所有问题。询问 n^−e 是否小于 0.1 是有原因的。如果不能,我们可以将 n 转换为字符串,打印其中的一些数字,打印小数点,并打印更多的数字,直到尾随零开始的位置。如果可以,我们需要先打印一个小数点,然后再打印一些零,这需要计算 n 的大小(这可能在转换为字符串时发生,但这可能意味着不调用库例程来执行此操作)。再说一遍,e 是否可以使 10^-e 小于 0.1? n的范围是多少?可以是负数吗?
  • @EricPostpischil 抱歉,我在之前的评论中不小心写了 n。 n*10^-e 为正且 >=1。 n 的上限是 2^64-1
  • 第三种可能是自己提取数字,并用putchar 输出。 "as fast as possible""printf" 的组合是矛盾的。

标签: c printf fixed-point


【解决方案1】:

您的第一个简单的解决方案不适用于大于 6 的 &lt;exp&gt; 值。这是一个简单的函数。您可以相应地调整整数类型和printf 格式:

#include <stdio.h>

int print_fixed(int n, int exp, FILE *fp) {
    char buf[32];
    int len = snprintf(buf + 1, sizeof(buf) - 1, "%.*d", exp + 1, n);
    int i;
    for (i = 0; i < len - exp; i++) {
        buf[i] = buf[i + 1];
    }
    buf[i] = '.';
    while (buf[len] == '0') {
        buf[len--] = '\0';
    }
    if (buf[len] == '.')
        buf[len--] = '\0';
    fputs(buf, fp);
    return len + 1;
}

int main() {
    int tests[][2] = {
        { 0, 0 }, { 1, 0 }, { 10, 1 }, { 100, 2 }, { 1000, 3 }, { 10000, 4 },
        { 1, 0 }, { 1, 1 }, { 1, 2 }, { 1, 3 }, { 1, 4 },
        { 1, 0 }, { 12, 1 }, { 123, 2 }, { 1234, 3 }, { 12345, 4 },
        { 1, 0 }, { 12, 1 }, { 120, 2 }, { 1204, 3 }, { 12040, 4 },
        { -1, 0 }, { -12, 1 }, { -120, 2 }, { -1204, 3 }, { -12040, 4 },
    };
    for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
        printf("print_fixed(%d, %d, stdout) -> ", tests[i][0], tests[i][1]);
        print_fixed(tests[i][0], tests[i][1], stdout);
        printf("\n");
    }
    return 0;
}

【讨论】:

  • 我总是有动力去否决那些全是没有文档或解释的代码的答案。 Stack Overflow 不应该是一个 write-my-code 服务。它应该回答问题并通知人们。
  • 缓冲区的操作可以通过使用fwrite 写入初始数字,fputc 写入小数点,fwrite 写入尾随数字来替换,这可能是更快。
  • @EricPostpischil:我考虑过这种替代方法,但是由于流锁定,对 C 库的 3 次单独调用实际上可能会更慢。使用除法构造字符串可能会更有效,而不是像 4386427 的答案那样依赖 snprintf,但更容易将上述代码调整为 OP 使用的整数类型。
  • 是的,流锁定是一个好点。我怀疑与部门的自我转换会表现良好;库例程应针对二进制到十进制转换进行优化。由于 e 是一个常数,因此可以通过转换 (snprintf) n/T、“.”和 n%T(其中 T 是常数 10^e)并截断尾随零来进行改进。
【解决方案2】:

我不确定这将如何执行,但您可以尝试一下并告诉我们。

#define BUF_SZ 32

void p(unsigned n, unsigned exp)
{
    char br[BUF_SZ];
    int ix = BUF_SZ - 1;
    br[ix--] = '\0';
    while(exp)
    {
        // Skip trailing zeros
        int t = n % 10;
        n = n / 10;
        exp--;
        if (t)
        {
            br[ix--] = '0' + t;
            break;
        }
    }

    while(exp)
    {
        br[ix--] = '0' + n % 10;
        n = n / 10;
        exp--;
    }

    // Only insert a '.' if something has been printed
    if (ix != (BUF_SZ - 2)) br[ix--] = '.';

    do
    {
        br[ix--] = '0' + n % 10;
        n = n / 10;
    } while(n);

    puts(&br[ix + 1]);
}

它不打印尾随零,也不打印'.'当没有小数时。

性能未知。

【讨论】:

    猜你喜欢
    • 2010-10-25
    • 1970-01-01
    • 2020-12-29
    • 2021-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-29
    相关资源
    最近更新 更多