Коварный дабл

Вы и так это знали:

Тип double не очень точный тип данных

Решите, например, сегодняшнее задание на codewars:

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 (find_nb, find-nb, 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.

Examples:

findNb(1071225) --> 45
findNb(91716553919377) --> -1

https://www.codewars.com/kata/5592e3bd57b64d00f3000047

Переведу в кратце: необходимо вычислить количество кубиков для здания заданного объёма. Объём здания считается как сумма объёмов состовляющих его кубиков. Каждый кубик, начиная с нижнего, имеет объём (n - i + 1)^3, где i — порядковый номер куба (от 1 до n) снизу.

Решение в лоб с использованием циклов:

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

Но в университете мне что-то рассказывали про ряды и их суммы, поэтому захотелось решить задачу математически. Как считать ряды я давно забыл, но ответ вы найдёте, перейдя на Wolfram Alpha и введя sum n^3, n = 1 to k в поле поиска. Узнаем, что:

\sum_{n=1}^{k} n^3 = \frac{1}{4}k^2(k + 1)^2

Приравняем правую часть к m, извлечём корень и упростим выражение:

k^2 + k - 2\sqrt{m} = 0

Теперь можно решить квадратное уравнение:

x = \frac{\sqrt{1 + 8\sqrt{m}} - 1}{2}

Пишем код:

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

Оказывается, что для некоторого числа тестовых данных, код даёт неверный ответ. Например, для значения 1646842954019151682L. В джаве корень из этого числа даёт:

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

Тогда как правильный ответ:

1283293791.00000000038962239473657673134031176401122912...

Очевидно, что ответы не совпадают. Fin!

Вывод

Более правильное (и, наверняка, быстрое) решение не всегда даёт правильные результаты. Этим не удивишь опытного разработчика, но начинающие программисты долго могут гадать о природе ошибки.

И напоследок. Следующее выражение истинно:

System.out.println(1646842954019151681L == 1646842954019151682D);

а это ложно:

System.out.println(1646842954019151681L == (long) 1646842954019151682D);

Ещё бонус

Посчитать правильно предыдущий пример всё-таки можно, если использовать более подходищие типы для таких задач. Речь про BigDecimal:

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

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: