2

It seems like a no brainer but I am having trouble to get this solved:

I would like to calculate a level based on given experience points (exp). Therefore I use the cube root formula and round down to the next whole number. The next level is reached when exp exactly reaches level^3. The number of levels is unlimited, so I would avoid having a pre-calculated lookup table.

When I use the standard php math

floor( pow( 10648, 1/3))

It returns 21 instead of 22. This is wrong since 21^3 gives 92161. The reason is that due to limited floating point precision pow(10648, 1/3) returns not exactly 22, instead it returns 21.9993112732. You can check it out with the following snippet:

$lvl = pow( 10647, (float) 1 / 3);
print number_format( $lvl, 10);

This is my workaround. But I am not sure if this is bulletproof:

public static function getLevel($exp) {    
    $lvl = floor(pow($exp, (float) 1 / 3));    // calculate the level
    if (pow($lvl + 1, 3) == $exp) {            // make check
        $lvl++;                                // correct 
    }
    return $lvl;
}

Also it looks a bit fragile when it comes to the check. So the question remains: Is there a reliable, efficient and bulletproof way of calculating cube root (of positive numbers).

Thanks.

stot
  • 1,036
  • 10
  • 20

2 Answers2

2

I think this is the only modification your code needs:

public static function getLevel($exp) {    
    $lvl = floor(pow($exp, (float) 1 / 3));    
    if (pow($lvl + 1, 3) <= $exp) {   // compare with <= instead of ==         
        $lvl++;                                 
    }
    return $lvl;
}
JoriO
  • 1,050
  • 6
  • 13
1

If you need 100% reliable results, you should probably use the GMP library for arbitrary precision calculations.

The gmp_root function should do what you need. You'll need PHP version 5.6 or newer with the GMP extension enabled.

$num = gmp_init(10648);
$third_root = gmp_root($num, 3);

var_dump(gmp_strval($third_root));  // string(2) "22"

If the GMP library is inconvenient for you and you're guaranteed that your number has an integer root then you can try the following:

function getLevel($base, $root = 3.0) {
    $exact = pow($base, 1.0 / $root);
    $ceil  = ceil($exact);
    $floor = floor($exact);

    if (pow($exact, $root) == $base) { return $exact; }
    if (pow($ceil,  $root) == $base) { return $ceil;  }
    if (pow($floor, $root) == $base) { return $floor; }

    // Default: no integer root
    return FALSE;
}

It checks the exact, floor, and ceil values of the result to find which is the correct answer. If it's not one of the three then the number has no integer root and defaults to FALSE.

Here's an example of it in action:

var_dump(getLevel(10648, 3)); // 22^3 => float(22)
var_dump(getLevel(16807, 5)); //  7^5 => float(7)

var_dump(getLevel(1,  3)); // Should always return 1 => float(1)
var_dump(getLevel(1, 99)); // Should always return 1 => float(1)

var_dump(getLevel(7)); // Has no integer 3rd root => bool(false)

Of course, you can make the function return $floor; or return $ceil; as the default case, but that's up to you.

Mr. Llama
  • 20,202
  • 2
  • 62
  • 115