这个数字是否超过D小数位?

时间:2010-09-22 00:03:11

标签: c floating-point

假设我想编写一个执行以下操作的函数:

  

给出数字N,
  当N舍入为D1数字时   结果是否包含超过D2小数位,不计算尾随零?

例如,假设N是.01001,D1是4,D2是2.问题是,.0100是否包含超过2个小数位,不计算尾随零?答案是“不”。但如果N是.00101,那么答案就是“是”。

考虑到浮点数的限制,我正在寻找一种使用标准C库函数的有效方法。

我的预期用法示例:如有必要,使用四位数字显示一个数字,否则使用两位数字显示。

(这不是一个家庭作业问题 - 这是因为他是一名专业程序员,在他还是学生时没有做过这类家庭作业问题。)

5 个答案:

答案 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)

  1. 将其格式化为最大精度的字符串。
  2. 从右侧删除零,直到达到最小精度。
  3. 这是一个不安全的实现,可帮助您入门:

    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;
}