Tricky Double
Tags: java, kotlin, coding, programming
Published: 2024-04-23 11:00:00

**This text is a translation of the [original article](https://markoutte.me/notes/loopy-double/) by my friend, Maksim Pelevin. If you are comfortable with reading Russian, you might find more interesting articles in [his blog](https://markoutte.me/).**

You already know this:

> The `double` data type is not very precise

Let's reinforce this understanding by solving a [problem on CodeWars](https://www.codewars.com/kata/5592e3bd57b64d00f3000047).

## Problem statement

The [task](https://www.codewars.com/kata/5592e3bd57b64d00f3000047) 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:
```java
public static long findNb(long m) {
    long n = 0, totalVolume = 0;
    while (totalVolume < m) {
        totalVolume += ++n * n * n;
    }
    return totalVolume == m ? n : -1;
}
```

## 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](https://www.wolframalpha.com) is here to help. To get the formula, let's run the following query: [n^3, n = 1 to k](https://www.wolframalpha.com/input?i=sum+n%5E3%2C+n+%3D+1+to+k).

The results tell us that:

![The formula from Wolfram Alpha for calculating series' sum](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a9xg2vlfdnr0gvv8ravl.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9w7h4k60jf5qzkivv91b.png)

![Simplified equation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x48z593wdk14z7l9x922.png)

### Write the code

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

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

### 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:

```java
System.out.println(String.format("%.30f", Math.sqrt(1646842954019151682L)));
// output 1283293791.000000000000000000000000000000
// correct 1283293791.00000000038962239473657673134031176401122912...
```

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:

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

---

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

```java
(1646842954019151681L == 1646842954019151682D)
```

```java
(1646842954019151681L == (long) 1646842954019151682D)
```