【问题标题】:How to hide leading zero in printf如何在printf中隐藏前导零
【发布时间】:2010-04-07 20:36:33
【问题描述】:

以下输出0.23。如何让它简单地输出.23

printf( "%8.2f" , .23 );

【问题讨论】:

  • 使用 "%.2f" 会得到什么?我已经很多年没有用 C 编码了。
  • 从中减去 0 ;)

标签: c double printf


【解决方案1】:

C 标准规定 fF 浮点格式说明符:

如果出现小数点字符,则在其前面至少出现一位数字。

我认为,如果您不希望小数点前出现零,您可能必须执行类似使用 snprintf() 将数字格式化为字符串的操作,并删除 0 如果格式化字符串以“0”开头。 (对于“-0”也是如此)。然后将该格式化字符串传递给我们的真实输出。或者类似的东西。

【讨论】:

    【解决方案2】:

    仅使用printf 是不可能的。 printf 的文档说:

    f  - "double" argument is output in conventional form, i.e.
         [-]mmmm.nnnnnn
         The default number of digits after the decimal point is six,
         but this can be changed with a precision field. If a decimal point
         appears, at least one digit appears before it. The "double" value is
         rounded to the correct number of decimal places.
    

    注意如果出现小数点,则在其前面至少出现一位数字

    因此,您似乎必须手动编写自己的格式化程序。

    【讨论】:

      【解决方案3】:
      double f = 0.23;
      
      assert(f < 0.995 && f >= 0);  
      printf(".%02u\n" , (unsigned)((f + 0.005) * 100));
      

      【讨论】:

      • 这就是答案。简单、高效。如果转换为 (int),“%2d”也可以工作。像 %.2f 一样将 .005 添加到四舍五入的感觉很好。
      • @Brent Foust 对于 0.995 &lt; f &lt; 1.00.0 &lt; f &lt; 0.095 失败
      • @chux: .%02u 应该修复 0 &lt; f &lt; 0.095 并且如果 1 &gt; f &gt; 0.995 则没有前导零。严格来说,+0.005 代码与 %.2f 不同的值有很多(除了前导零),例如,0.625 -> .630.62
      • 同意这些更改可以解决comments 的问题。使用(f + 0.005) * 100)(f *100 + 0.5) 不会最小化中途大小写差异吗?
      【解决方案4】:

      只需将其转换为所需精度的整数

      double value = .12345678901; // input
      int accuracy = 1000; // 3 digit after dot
      printf(".%03d\n", (int)(value * accuracy) );
      

      输出:

      .123
      

      example source on pastebin

      【讨论】:

      • 对于 0.01、0.001、负数、value &gt; INT_MAX/1000 等值失败,截断而不是舍入。
      • 未正确四舍五入。如果value = .1239,那么你的方法产生.123,而printf("%.3f\n", value);产生0.124
      • 取整会有所不同,具体取决于您所居住的世界。所以,.1239 变为 .124?好的,.1235 呢?
      【解决方案5】:
      #include <stdio.h>
      
      static void printNoLeadingZeros(double theValue)
      {
         char buffer[255] = { '\0' };
      
         sprintf(buffer, "%.2f", theValue);
      
         printf("%s\n", buffer + (buffer[0] == '0'));
      }
      
      int main()
      {
         double values[] = { 0.23, .23, 1.23, 01.23, 001.23, 101.23 };
         int n           = sizeof(values) / sizeof(values[0]);
         int i           = 0;
      
         while(i < n)
            printNoLeadingZeros(values[i++]);
      
         return(0);
      }
      

      【讨论】:

      • sizeof(values) 不会返回指针的大小,而不是实际的数组吗?
      • @ChristianMann:我认为取决于您是处于编译器的 C 模式还是 C++ 模式。
      • 绝对是数组的大小,与语言无关。数组有点不愿意衰减为指针,并且在您“强制”它们这样做之前不会这样做,例如通过将它们作为参数传递给接受指针的函数。
      • 好的答案,只要theValue 是肯定的。
      【解决方案6】:

      标准 C 库不提供此功能,因此您必须自己编写。这不是一个罕见的一次性要求。您迟早需要编写类似的函数来修剪尾随零并添加千位分隔符。因此,不仅要获得您正在寻找的输出字节,还要更一般地说明如何编写一个强大的库。这样做时请记住:

      1. 弄清楚你想怎么称呼它。像这样的东西你写一次但打电话 一百万次,所以让调用尽可能简单。

      2. 然后制作测试套件 行使你能想到的所有替代方案

      3. 当你在它的时候, 永远解决问题,这样你就不必再回来了 再一次(例如,不要硬编码宽度,精度,继续做 领先加号、电子格式等的版本)

      4. 成功 即使您不使用线程也是线程安全的(特定情况下 第 3 点,实际上)

      所以向后工作:线程安全需要在堆栈上分配存储,这必须由调用者完成。这既不漂亮也不好玩,只是习惯了。这是C路。格式可以有宽度、精度、一些标志和转换类型(f、e、g)。因此,让我们制作宽度和精度参数。与其完全参数化公共 API,我只会有多个入口点,在函数名称中说明它们使用的标志和转换类型。

      一个令人讨厌的地方是,当将缓冲区传递给函数时,函数需要知道大小。但是如果你把它作为一个单独的参数,这会很痛苦,但是因为 1)调用者必须编写它,并且 2)调用者可能会弄错。所以我个人的风格是制作一个掩码宏,它假设缓冲区是一个字符数组,而不是一个指针,并使用 sizeof() 将大小传递给获取大小的函数的更详细版本。

      这是我能想到的最简单的调用方式的模型,带有测试用例。

      (注意 COUNT() 是几十年来我每周使用的一个宏,用于获取数组中元素的数量。标准 C 应该有这样的东西。)

      (注意我在这里使用“匈牙利表示法”的方言。“d”是一个双精度数。“a”是“数组”。“sz”是一个以 NUL 结尾的字符串缓冲区,而“psz”是一个指针为一。这两者的区别在于“sz”可以与COUNT()或sizeof()一起使用来获取数组大小,而“psz”不能。“i”是整数,具体变量“i”是用于循环。

      double ad[] = { 0.0, 1.0, 2.2, 0.3, 0.45, 0.666, 888.99,
                      -1.0, -2.2, -0.3, -0.45, -0.666, -888.99 };
      char   szBuf[20];
      
      for ( int i = 0; i < COUNT( ad ); i++ )
          printf( "%s\n", NoLeadingZeroF( 4, 2, ad[i], szBuf ) );
      
      for ( int i = 0; i < COUNT( ad ); i++ )
          printf( "%s\n", NoLeadingZeroPlusF( 4, 2, ad[i], szBuf ) );
      

      现在,“f”和“+f”版本看起来非常相似,所以让它们都调用一个内部函数。以下是获取缓冲区大小的函数和自行计算的宏。 (也为 e 和 g 格式编写了并行函数。)

      char* NoLeadingZeroFN( int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) {
        return NoLeadingZeroFmtN( "%*.*f", iWidth, iPrecision, d, szBuf, iBufLen );
      }
      
      char* NoLeadingZeroPlusFN( int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) {
        return NoLeadingZeroFmtN( "%+*.*f", iWidth, iPrecision, d, szBuf, iBufLen );
      }
      
      
      #define NoLeadingZeroF( width, precision, number, buf ) \
              NoLeadingZeroFN( ( width ), (precision ), ( number ), ( buf ), sizeof( buf ) ) 
      
      #define NoLeadingZeroPlusF( width, precision, number, buf ) \
              NoLeadingZeroPlusFN( ( width ), (precision ), ( number ), ( buf ), sizeof( buf ) ) 
      

      最后是完成工作的(内部)函数。请注意,在 Windows 上 snprintf() 需要前置下划线,但在 Unix 上不需要。

      char* NoLeadingZeroFmtN( char* szFmt, int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) {
      
      #ifdef WIN32
        _snprintf( szBuf, iBufLen - 1, szFmt, iWidth, iPrecision, d );
      #else
        snprintf( szBuf, iBufLen - 1, szFmt, iWidth, iPrecision, d );
      #endif
      
        // Some snprintf()'s do not promise to NUL-terminate the string, so do it ourselves.
        szBuf[ iBufLen - 1 ] = '\0';
      
        // _snprintf() returns the length actually produced, IF the buffer is big enough.
        // But we don't know it was, so measure what we actually got.
        char* pcTerminator = strchr( szBuf, '\0' );
      
        for ( char* pcBuf = szBuf; *pcBuf && *pcBuf != '.'; pcBuf++ )
            if ( *pcBuf == '0' ) {
                memmove( pcBuf, pcBuf + 1, pcTerminator - pcBuf );
                break;
            }
      
        return szBuf;
      }
      

      输出是:

      .00
      1.00
      2.20
      .30
      .45
      .67
      888.99
      -1.00
      -2.20
      -.30
      -.45
      -.67
      -888.99
      +.00
      +1.00
      +2.20
      +.30
      +.45
      +.67
      +888.99
      -1.00
      -2.20
      -.30
      -.45
      -.67
      -888.99
      

      额外的测试应该验证函数是否适用于太小的缓冲区。

      【讨论】:

        【解决方案7】:

        看起来没有简单的解决方案。我可能会使用下面的代码。它不是最快的方法,但它应该适用于许多不同的格式。它也保留了字符的数量和点的位置。

        #include <stdio.h>
        
        void fixprint(char *s)
        {
                size_t i;
                i = 1;
                while (s[i]=='0' || s[i]==' ' || s[i]=='+' || s[i]=='-') {
                        if (s[i]=='0') s[i]=' ';
                        i++;
                }
        }
        
        int main()
        {
                float x = .23;
                char s[14];
                sprintf(s,"% 8.2f",x);
                fixprint(s);
                printf("%s\n",s);
        }
        

        【讨论】:

        • 这不适用于应该是最简单的情况sprintf(s,"%.5f",x) - 它似乎希望在某处有空间?
        【解决方案8】:

        你不能用printf()来做到这一点那么你怎么能完美地做到这一点呢?

        这是我的解决方案。

        sprintf() => 将float 转换为string

            #include <stdio.h>
            #include <string.h>
            int main()
            {
            char result[50];
            float num = 0.23;
            sprintf(result, "%.2f", num);
        
            char *str = result;
            int n = strspn(str, "0" );
            printf("Trimmed string is %s ", &str[n]);
        
            return 0;
        }
        

        输出

        Trimmed string is .23
        

        【讨论】:

          猜你喜欢
          • 2011-06-27
          • 2016-01-15
          • 2011-03-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-01-24
          相关资源
          最近更新 更多