避免在printf()中追踪零

我一直在printf()系列函数的格式说明符上磕磕绊绊。 我想要的是能够打印一个双精度(或浮点数)小数点后的最大数字位数。 如果我使用:

printf("%1.3f", 359.01335); printf("%1.3f", 359.00999); 

我明白了

 359.013 359.010 

而不是所需的

 359.013 359.01 

有谁能够帮助我?

这不能用普通的printf格式说明符来完成。 你可以得到的最接近的是:

 printf("%.6g", 359.013); // 359.013 printf("%.6g", 359.01); // 359.01 

但是“.6”是所有的数字宽度

 printf("%.6g", 3.01357); // 3.01357 

打破它。

可以做的是sprintf("%.20g")的数字字符串缓冲区,然后操纵字符串只有N个字符超过小数点。

假设你的数字在变量num中,下面的函数将删除除前N十进制数以外的所有数字,然后去掉尾随零(如果它们全部为零,则取小数点)。

 char str[50]; sprintf (str,"%.20g",num); // Make the number. morphNumericString (str, 3); : : void morphNumericString (char *s, int n) { char *p; int count; p = strchr (s,'.'); // Find decimal point, if any. if (p != NULL) { count = n; // Adjust for more or less decimals. while (count >= 0) { // Maximum decimals allowed. count--; if (*p == '\0') // If there's less than desired. break; p++; // Next character. } *p-- = '\0'; // Truncate string. while (*p == '0') // Remove trailing zeros. *p-- = '\0'; if (*p == '.') { // If all decimals were zeros, remove ".". *p = '\0'; } } } 

如果对截断方面不满意(将0.12399转换为0.123而不是将其舍入到0.124 ),则可以使用printf已经提供的舍入功能。 您只需要先分析数字来动态创建宽度,然后使用它们将数字转换为字符串:

 #include <stdio.h> void nDecimals (char *s, double d, int n) { int sz; double d2; // Allow for negative. d2 = (d >= 0) ? d : -d; sz = (d >= 0) ? 0 : 1; // Add one for each whole digit (0.xx special case). if (d2 < 1) sz++; while (d2 >= 1) { d2 /= 10.0; sz++; } // Adjust for decimal point and fractionals. sz += 1 + n; // Create format string then use it. sprintf (s, "%*.*f", sz, n, d); } int main (void) { char str[50]; double num[] = { 40, 359.01335, -359.00999, 359.01, 3.01357, 0.111111111, 1.1223344 }; for (int i = 0; i < sizeof(num)/sizeof(*num); i++) { nDecimals (str, num[i], 3); printf ("%30.20f -> %s\n", num[i], str); } return 0; } 

在这种情况下, nDecimals()的全部要点是正确计算字段宽度,然后使用基于该字符串的格式字符串格式化数字。 测试工具main()显示了这个动作:

  40.00000000000000000000 -> 40.000 359.01335000000000263753 -> 359.013 -359.00999000000001615263 -> -359.010 359.00999999999999090505 -> 359.010 3.01357000000000008200 -> 3.014 0.11111111099999999852 -> 0.111 1.12233439999999995429 -> 1.122 

一旦你有正确的四舍五入的值,你可以再次通过这个morphNumericString()来删除尾随零,只需改变:

 nDecimals (str, num[i], 3); 

成:

 nDecimals (str, num[i], 3); morphNumericString (str, 3); 

(或在morphNumericString的末尾调用morphNumericString ,但在这种情况下,我可能只是nDecimals两个函数合并为一个函数),最后得到:

  40.00000000000000000000 -> 40 359.01335000000000263753 -> 359.013 -359.00999000000001615263 -> -359.01 359.00999999999999090505 -> 359.01 3.01357000000000008200 -> 3.014 0.11111111099999999852 -> 0.111 1.12233439999999995429 -> 1.122 

为了摆脱尾随零,你应该使用“%g”格式:

 float num = 1.33; printf("%g", num); //output: 1.33 

在问题澄清了一点之后,抑制零点并不是唯一被问到的问题,但是将输出限制在小数点后三位也是必须的。 我认为不能单独使用sprintf格式的字符串。 正如Pax暗黑破坏神指出的那样,字符串操作是必需的。

我喜欢R的回答略微调整:

 float f = 1234.56789; printf("%d.%.0f", f, 1000*(f-(int)f)); 

'1000'决定了精度。

力量为0.5的四舍五入。

编辑

好的,这个答案被编辑了几次,我错过了几年前的想法(原来并没有满足所有的标准)。 所以这是一个新的版本(填写所有的标准和正确处理负数):

 double f = 1234.05678900; char s[100]; int decimals = 10; sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals)); printf("10 decimals: %d%s\n", (int)f, s+1); 

而测试案例:

 #import <stdio.h> #import <stdlib.h> #import <math.h> int main(void){ double f = 1234.05678900; char s[100]; int decimals; decimals = 10; sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals)); printf("10 decimals: %d%s\n", (int)f, s+1); decimals = 3; sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals)); printf(" 3 decimals: %d%s\n", (int)f, s+1); f = -f; decimals = 10; sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals)); printf(" negative 10: %d%s\n", (int)f, s+1); decimals = 3; sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals)); printf(" negative 3: %d%s\n", (int)f, s+1); decimals = 2; f = 1.012; sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals)); printf(" additional : %d%s\n", (int)f, s+1); return 0; } 

和测试的输出:

  10 decimals: 1234.056789 3 decimals: 1234.057 negative 10: -1234.056789 negative 3: -1234.057 additional : 1.01 

现在,所有标准都得到满足:

  • 固定零后的最大小数位数
  • 尾随零被删除
  • 它在数学上是正确的(对吧?)
  • 作品(现在)也是当第一个小数是零

不幸的是,这个答案是sprintf不返回字符串的双sprintf

一个简单的解决方案,但它完成了工作,分配一个已知的长度和精度,并避免了指数格式的机会(这是使用%g时的风险):

 // Since we are only interested in 3 decimal places, this function // can avoid any potential miniscule floating point differences // which can return false when using "==" int DoubleEquals(double i, double j) { return (fabs(i - j) < 0.000001); } void PrintMaxThreeDecimal(double d) { if (DoubleEquals(d, floor(d))) printf("%.0f", d); else if (DoubleEquals(d * 10, floor(d * 10))) printf("%.1f", d); else if (DoubleEquals(d * 100, floor(d* 100))) printf("%.2f", d); else printf("%.3f", d); } 

如果您想要最多2位小数,请添加或删除“elses”; 4位小数; 等等

例如,如果你想要2位小数:

 void PrintMaxTwoDecimal(double d) { if (DoubleEquals(d, floor(d))) printf("%.0f", d); else if (DoubleEquals(d * 10, floor(d * 10))) printf("%.1f", d); else printf("%.2f", d); } 

如果要指定保持字段对齐的最小宽度,则根据需要进行增加,例如:

 void PrintAlignedMaxThreeDecimal(double d) { if (DoubleEquals(d, floor(d))) printf("%7.0f", d); else if (DoubleEquals(d * 10, floor(d * 10))) printf("%9.1f", d); else if (DoubleEquals(d * 100, floor(d* 100))) printf("%10.2f", d); else printf("%11.3f", d); } 

您也可以将其转换为您传递所需字段宽度的函数:

 void PrintAlignedWidthMaxThreeDecimal(int w, double d) { if (DoubleEquals(d, floor(d))) printf("%*.0f", w-4, d); else if (DoubleEquals(d * 10, floor(d * 10))) printf("%*.1f", w-2, d); else if (DoubleEquals(d * 100, floor(d* 100))) printf("%*.2f", w-1, d); else printf("%*.3f", w, d); } 

我搜索字符串(从最右边开始),范围为19 (ASCII值为4957 )的第一个字符,然后为null (设置为0 )每个字符的右边 – 见下面:

 void stripTrailingZeros(void) { //This finds the index of the rightmost ASCII char[1-9] in array //All elements to the left of this are nulled (=0) int i = 20; unsigned char char1 = 0; //initialised to ensure entry to condition below while ((char1 > 57) || (char1 < 49)) { i--; char1 = sprintfBuffer[i]; } //null chars left of i for (int j = i; j < 20; j++) { sprintfBuffer[i] = 0; } } 

这样的事情(可能有舍入错误和需要调试的负值问题,留给读者练习):

 printf("%.0d%.4g\n", (int)f/10, f-((int)f-(int)f%10)); 

它有点程序化,但至少它不会让你做任何字符串操作。

这是我的第一个答案:

空虚
 xprintfloat(char *格式,float f)
 {
   char s [50];
   char * p;

   sprintf(s,format,f);
   for(p = s; * p; ++ p)
     if('。'== * p){
      而(* ++ p)的;
       while('0'== *  -  p)* p ='\ 0';
     }
   printf(“%s”,s);
 }

已知的错误:可能的缓冲区溢出取决于格式。 如果“。” 是出于其他原因,%f错误的结果可能会发生。

以上略有差异: –

  1. 消除案件的期限(10000.0)。
  2. 处理第一个时间段后中断。

代码在这里: –

 void EliminateTrailingFloatZeros(char *iValue) { char *p = 0; for(p=iValue; *p; ++p) { if('.' == *p) { while(*++p); while('0'==*--p) *p = '\0'; if(*p == '.') *p = '\0'; break; } } } 

它仍然有可能溢出,所以要小心; P

为什么不这样做呢?

 double f = 359.01335; printf("%g", round(f * 1000.0) / 1000.0); 

我在发布的一些解决方案中发现问题。 我根据上面的答案把它放在一起。 这似乎为我工作。

 int doubleEquals(double i, double j) { return (fabs(i - j) < 0.000001); } void printTruncatedDouble(double dd, int max_len) { char str[50]; int match = 0; for ( int ii = 0; ii < max_len; ii++ ) { if (doubleEquals(dd * pow(10,ii), floor(dd * pow(10,ii)))) { sprintf (str,"%f", round(dd*pow(10,ii))/pow(10,ii)); match = 1; break; } } if ( match != 1 ) { sprintf (str,"%f", round(dd*pow(10,max_len))/pow(10,max_len)); } char *pp; int count; pp = strchr (str,'.'); if (pp != NULL) { count = max_len; while (count >= 0) { count--; if (*pp == '\0') break; pp++; } *pp-- = '\0'; while (*pp == '0') *pp-- = '\0'; if (*pp == '.') { *pp = '\0'; } } printf ("%s\n", str); } int main(int argc, char **argv) { printTruncatedDouble( -1.999, 2 ); // prints -2 printTruncatedDouble( -1.006, 2 ); // prints -1.01 printTruncatedDouble( -1.005, 2 ); // prints -1 printf("\n"); printTruncatedDouble( 1.005, 2 ); // prints 1 (should be 1.01?) printTruncatedDouble( 1.006, 2 ); // prints 1.01 printTruncatedDouble( 1.999, 2 ); // prints 2 printf("\n"); printTruncatedDouble( -1.999, 3 ); // prints -1.999 printTruncatedDouble( -1.001, 3 ); // prints -1.001 printTruncatedDouble( -1.0005, 3 ); // prints -1.001 (shound be -1?) printTruncatedDouble( -1.0004, 3 ); // prints -1 printf("\n"); printTruncatedDouble( 1.0004, 3 ); // prints 1 printTruncatedDouble( 1.0005, 3 ); // prints 1.001 printTruncatedDouble( 1.001, 3 ); // prints 1.001 printTruncatedDouble( 1.999, 3 ); // prints 1.999 printf("\n"); exit(0); } 

一些高度投票的解决方案建议printf%g转换说明符。 这是错误的,因为有%g会产生科学记数的情况。 其他解决方案使用数学打印所需的小数位数。

我认为最简单的解决方案是使用sprintf%f转换说明符,并手动删除结尾的零和可能的小数点。 这是一个C99解决方案:

 #include <stdio.h> #include <stdlib.h> char* format_double(double d) { int size = snprintf(NULL, 0, "%.3f", d); char *str = malloc(size + 1); snprintf(str, size + 1, "%.3f", d); for (int i = size - 1, end = size; i >= 0; i--) { if (str[i] == '0') { if (end == i + 1) { end = i; } } else if (str[i] == '.') { if (end == i + 1) { end = i; } str[end] = '\0'; break; } } return str; } 

请注意,用于数字和小数点分隔符的字符取决于当前的语言环境。 上面的代码假设C或美国英语区域设置。

由于f之前的“.3”,您的代码会舍入到小数点后三位

 printf("%1.3f", 359.01335); printf("%1.3f", 359.00999); 

因此,如果第二行四舍五入到小数点后两位,您应该将其更改为:

 printf("%1.3f", 359.01335); printf("%1.2f", 359.00999); 

该代码将输出您想要的结果:

 359.013 359.01 

*注意这是假设您已经在单独的行上打印,如果不是,则以下操作将阻止它在同一行上打印:

 printf("%1.3f\n", 359.01335); printf("%1.2f\n", 359.00999); 

下面的程序源代码是我对这个答案的测试

 #include <cstdio> int main() { printf("%1.3f\n", 359.01335); printf("%1.2f\n", 359.00999); while (true){} return 0; }