假设我想编写一个执行以下操作的函数:
给出数字N,
当N舍入为D1数字时 结果是否包含超过D2小数位,不计算尾随零?
例如,假设N是.01001,D1是4,D2是2.问题是,.0100是否包含超过2个小数位,不计算尾随零?答案是“不”。但如果N是.00101,那么答案就是“是”。
考虑到浮点数的限制,我正在寻找一种使用标准C库函数的有效方法。
我的预期用法示例:如有必要,使用四位数字显示一个数字,否则使用两位数字显示。
(这不是一个家庭作业问题 - 这是因为他是一名专业程序员,在他还是学生时没有做过这类家庭作业问题。)
答案 0 :(得分:4)
使用标准库执行此操作的唯一简便方法是使用正确格式的snprintf
(或仅sprintf
),然后自己计算零。正确计算(二进制)浮点数的十进制表示是一项非常非常困难的任务,您不想自己尝试,并且您几乎没有机会编写比标准库更快的正确版本
我希望我做对了;这是未经测试的:
double n; /* the number N */
int d1, d2; /* the parameters d1 and d2 */
char s[MAXLEN], *z;
snprintf(s, sizeof s, "%.*f", d1, n);
for (z=s+strlen(s)-1; *z==0; z--);
if (strlen(++z)<d1-d2) puts("yes");
else puts("no");
编辑如ssianky所述,snprintf
可能会对其打印的精度产生限制。实际上C标准允许几乎任何浮点运算都没有任何理由给出错误的结果,只要实现文档本身,但鼓励IEEE行为,并且POSIX还需要正确舍入结果到DECIMAL_DIG
个地方,但允许实现在打印足够多的位置后打印废话(例如全零)以唯一地确定实际浮点值。所以长话短说,如果d1
相当大,或者你的平台是病态的,基于snprintf
的方法可能无法给出正确答案。 (实际上,他们会在GNU系统上给出正确答案,并在Microsoft系统上给出错误答案。)
如果您关心这个缺点,并希望d1
的任何值都能获得正确的结果,那么您必须implement exact float-to-decimal code yourself。涉及将浮点值重复乘以10的循环不足以满足此要求。
编辑2:查看OP的“预期用途”,使用snprintf
似乎是不费吹灰之力。如果要打印值,并且只是想确定要使用多少小数位,只需将其打印为字符串,然后在显示前删除尾随零。事实上,%g
printf
格式说明符甚至可以做OP想要的......
答案 1 :(得分:1)
您可以先将数字乘以10 ^ D1并将其四舍五入到最接近的整数,然后检查它是否可被10 ^ D2整除。有一些可以选择的舍入函数可以向上/向下/远离零/等,所以一定要检查你是否正在使用你想要的那个。
下面的函数是为了使它小而且自包含,但是生产实现应该使用查找表来获得10的幂,如注释行所示。这将产生更快的性能并避免在浮点产品中累积错误。
int f (double N, unsigned int D1, unsigned int D2)
{
int i, n_mult_round, ten_d2;
/* instead of the loop below, a real implementation should use */
/* N *= dbl_powers_of_ten[D1]; */
/* where dbl_powers_of_ten is a double array containing powers of ten */
for (i = 0; i < D1; ++i) {
N *= 10.0;
}
n_mult_round = (int) round (N);
/* instead of the loop below, a real implementation should use */
/* ten_d2 = int_powers_of_ten[D2]; */
/* where int_powers_of_ten is an int array containing powers of ten */
ten_d2 = 1;
for (i = 0; i < D2; ++i) {
ten_d2 *= 10;
}
if ( n_mult_round % ten_d2 == 0 ) {
return ( 1 );
}
else {
return ( 0 );
}
}
答案 2 :(得分:0)
这是一个不安全的实现,可帮助您入门:
char buff[20]; /* make as big as necessary */
int maxPrecision = 4;
int minPrecision = 2;
sprintf(buff, "%.*f", maxPrecision, myFloat);
char *p = buff + (strlen(buff) - 1);
char *punct = strchr(buff, ".");
/* remove trailing zeros until we reach minPrecision */
while (*p == '0' && p > (punct + minPrecision))
*p-- = 0;
答案 3 :(得分:0)
这个怎么样? fmod应该在指定的小数位数后面返回小数,所以我们可以比较这两个数字,看看它们是否相等。
int needsD1Decimals(float N, int D1, int D2) {
float pow1 = pow(0.1f, D1); // 0.0001
float pow2 = pow(0.1f, D2); // 0.01
float mod1 = fmod(N, pow1); // 0.00001
float mod2 = fmod(N, pow2); // 0.00001
if (fabs(mod1 - mod2) > (pow1 / 2)) { // > 0.00005 to handle errors
return 1;
}
return 0;
}
如果你只是想打印出正确答案,那么用最多小数位数打印它并在最后截断零可能会更快:
void printNumber(float N, int D1, int D2) {
char format[256];
char result[256];
sprintf(format, "%%.%df", D1);
sprintf(result, format, N);
char *end = result + strlen(result) - 1;
int zeros = 0;
while (end > result && end[0] == '0' && zeros < (D1 - D2))
{
zeros++;
end--;
}
if (zeros >= (D1 - D2))
{
end[1] = '\0';
}
puts(result);
}
void doNumber(float N, int D1, int D2) {
printf("%f, %d, %d: ", N, D1, D2);
printNumber(N, D1, D2);
printf("\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
doNumber(0.01001f, 4, 2);
doNumber(0.010101f, 4, 2);
doNumber(0.011001f, 4, 2);
doNumber(5000.0f, 4, 2);
return 0;
}
答案 4 :(得分:-1)
int foo(float number, int d1, int d2)
{
assert((number > 0.0f) && (number < 1.0f) && (d1 <= FLT_DIG) && (d2 < d1) && (d2 > 0));
int count = d1;
int n = (int)(number * pow(10.0f, d1));
if (n == 0) return 0;
while ((n % 10) == 0)
{
n /= 10;
count--;
}
return (count > d2) ? 1 : 0;
}