3

I am trying to write a C code of a function that takes an integer from 0 to 125 and return the cubic root of that integer only if it's a whole number ( 1,2,3,4,5 ) and if it's not, return 0 instead. So I wrote this code:

unsigned int cubic(unsigned int n) {
    if (n > 125)
        return 0;
    double root = pow(n, (1 / 3.));
    double rem = (double)(roundf(root)) - root;
    if (rem != 0)
        return 0;
    else
       return roundf(root);
}

this function works fine with all cases except for the number 64 and 125. in these cases it returns 0 instead of the cubic roots of these numbers which are 4 and 5 respectively. Can anybody explain to me why this is happening?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 3
    If the input `n` is guaranteed to be between `0` and `125`, then you may simply compare `n` against the cubes of `1` to `5`. – kotatsuyaki May 29 '22 at 13:31
  • 1
    `pow( n, (1/3.) )` doesn't give the exact result of cube root, just print the result with more precision and you'll see. [`cbrt(n)`](https://en.cppreference.com/w/c/numeric/math/cbrt) may be better but won't guarantee the correct result either, due to the nature of floating-point operations. The only way is to round the result to int and check – phuclv May 29 '22 at 13:33
  • 1
    If you print some values, for `64` you get `root = 3.99999999999999955591 rem = 0.00000000000000044409`. Please see [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) and [Why Are Floating Point Numbers Inaccurate?](https://stackoverflow.com/questions/21895756/why-are-floating-point-numbers-inaccurate) It's not so much that cube root of 64 can't be accurately represented by binary floating point, but `1/3.` cannot be. – Weather Vane May 29 '22 at 13:35

1 Answers1

5

Because 1 / 3. cannot accurately represented as a floating point number, the floating point calculation pow(64, (1 / 3.)) might produce a number very close to 4, but a tiny bit smaller or larger, different enough from 4 for (double)(roundf(root)) - root to be different from 0.

You could work around this precision issue this way:

unsigned int cubic(unsigned int n) {
    int root = (int)round(pow(n, 1 / 3.));
    if (root * root * root == n)
        return root;
    else
        return 0;
}

Instead of pow(n, 1 / 3.), you could use cbrt(n) if available on your system, but the precision issue might still be present.

For your goal, it seems much simpler to iterate over the possible integer roots and check:

unsigned int cubic(unsigned int n) {
    for (unsigned int i = 1; i <= 5; i++) {
        unsigned int i3 = i * i * i;
        if (i3 == n)
            return i;
        if (i3 > n)
            break;
    }
    return 0;
}

Or even more explicit:

unsigned int cubic(unsigned int n) {
    switch (n) {
      case 1 * 1 * 1: return 1;
      case 2 * 2 * 2: return 2;
      case 3 * 3 * 3: return 3;
      case 4 * 4 * 4: return 4;
      case 5 * 5 * 5: return 5;
      default: return 0;
    }
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189