避免在printf()中尾随零
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了避免在printf()中尾随零相关的知识,希望对你有一定的参考价值。
我一直绊倒printf()函数系列的格式说明符。我想要的是能够在小数点后打印一个最大给定位数的double(或float)。如果我使用:
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 == ' ') // If there's less than desired.
break;
p++; // Next character.
}
*p-- = ' '; // Truncate string.
while (*p == '0') // Remove trailing zeros.
*p-- = ' ';
if (*p == '.') { // If all decimals were zeros, remove ".".
*p = ' ';
}
}
}
如果你对截断方面不满意(将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
", 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
结束时调用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
为什么不这样做呢?
double f = 359.01335;
printf("%g", round(f * 1000.0) / 1000.0);
上面略有变化:
- 消除案件的期限(10000.0)。
- 处理第一个句点后的中断。
代码在这里:
void EliminateTrailingFloatZeros(char *iValue)
{
char *p = 0;
for(p=iValue; *p; ++p) {
if('.' == *p) {
while(*++p);
while('0'==*--p) *p = ' ';
if(*p == '.') *p = ' ';
break;
}
}
}
它仍然有溢出的可能性,所以要小心; P
由于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
", 359.01335);
printf("%1.2f
", 359.00999);
以下程序源代码是我对此答案的测试
#include <cstdio>
int main()
{
printf("%1.3f
", 359.01335);
printf("%1.2f
", 359.00999);
while (true){}
return 0;
}
要删除尾随零,您应该使用“%g”格式:
float num = 1.33;
printf("%g", num); //output: 1.33
在问题澄清了一点之后,抑制零并不是唯一被问到的东西,但是也需要将输出限制为三位小数。我认为单独使用sprintf格式字符串无法做到这一点。正如Pax Diablo指出的那样,需要字符串操作。
我喜欢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
", (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
", (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
", (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
", (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
", (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
", (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
没有返回字符串。
我搜索字符串(从最右边开始)搜索1
范围内的第一个字符到9
(ASCII值49
-57
)然后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
", (int)f/10, f-((int)f-(int)f%10));
它有点程序化,但至少它不会让你做任何字符串操作。
一个简单的解决方案,但它可以完成工作,分配已知的长度和精度,并避免使用指数格式(当使用%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 Do以上是关于避免在printf()中尾随零的主要内容,如果未能解决你的问题,请参考以下文章