DEV Community

Cover image for Tricky Double
igor.kulakov
igor.kulakov

Posted on • Originally published at flounder.dev

Tricky Double

This text is a translation of the original article by my friend, Maksim Pelevin. If you are comfortable with reading Russian, you might find more interesting articles in his blog.

You already know this:

The double data type is not very precise

Let's reinforce this understanding by solving a problem on CodeWars.

Problem statement

The task as described on CodeWars is as follows:

Your task is to construct a building, which will be a pile of n cubes.
The cube at the bottom will have a volume of n^3, the cube above will have volume of (n-1)^3 and so on until the top which will have a volume of 1^3.
You are given the total volume m of the building. Being given m, can you find the number n of cubes you will have to build?
The parameter of the function findNb will be an integer m and you have to return the integer n such as n^3 + (n-1)^3 + … + 1^3 = m* if such a n exists or -1 if there is no such n.

Naive solution

The most straightforward solution using loops would be something like:

public static long findNb(long m) {
    long n = 0, totalVolume = 0;
    while (totalVolume < m) {
        totalVolume += ++n * n * n;
    }
    return totalVolume == m ? n : -1;
}
Enter fullscreen mode Exit fullscreen mode

Mathematical solution

Back in university, I learned about mathematical series and the methods to calculate their sums, so here is my take on a mathematical solution.

I no longer recall the formulas, but thankfully, Wolfram Alpha is here to help. To get the formula, let's run the following query: n^3, n = 1 to k.

The results tell us that:

The formula from Wolfram Alpha for calculating series' sum

Let's substitute m for the left side of the equation, then apply the square root property and multiply both sides of the equation by two.

After simplifying, we get:

Simplified equation

Simplified equation

Write the code

Here is a solution for the equation above, written in Java:

public static long findNb(long m) {
    double res = Math.sqrt(m);
    double d = 1 + 8 * res;
    double n = (Math.sqrt(d) - 1) / 2;
    return n - Math.floor(n) == 0 ? (long) n : -1;
}
Enter fullscreen mode Exit fullscreen mode

Results

It turns out that this code returns an incorrect result for a subset of test data. This inconcistency is visible, for example, when you pass 1646842954019151682L as the input.

While calculating a square root in Java, you may get a result that isn't wholly accurate. Let's take a look:

System.out.println(String.format("%.30f", Math.sqrt(1646842954019151682L)));
// output 1283293791.000000000000000000000000000000
// correct 1283293791.00000000038962239473657673134031176401122912...
Enter fullscreen mode Exit fullscreen mode

Obviously, the output and the correct answer are different. Fin!

Conclusion

The most technically correct and potentially fastest solution is not always the best one to choose. This may be no surprise to a skilled developer, but those new to programming might puzzle over the cause of the error for quite a while.

If you're wondering how to fix the problem with the mathematical solution, one approach is to use BigDecimal. This code passes all the tests:

public static long findNb(long m) {
    MathContext mc = new MathContext(64);
    BigDecimal res = BigDecimal.valueOf(m).sqrt(mc);
    BigDecimal d = res.multiply(BigDecimal.valueOf(8)).add(BigDecimal.ONE);
    BigDecimal n = d.sqrt(mc).subtract(BigDecimal.ONE).divide(BigDecimal.valueOf(2));
    return Objects.equals(n, n.setScale(0, RoundingMode.FLOOR)) ? n.longValue() : -1;
}
Enter fullscreen mode Exit fullscreen mode

... and a little bonus: can you guess which of the following true, and which is false?

(1646842954019151681L == 1646842954019151682D)
Enter fullscreen mode Exit fullscreen mode
(1646842954019151681L == (long) 1646842954019151682D)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)