Коварный дабл
Вы и так это знали:
Тип 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
Переведу в кратце: необходимо вычислить количество кубиков для здания заданного объёма. Объём здания считается как сумма объёмов состовляющих его кубиков. Каждый кубик, начиная с нижнего, имеет объём , где 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 в поле поиска. Узнаем, что:
Приравняем правую часть к m, извлечём корень и упростим выражение:
Теперь можно решить квадратное уравнение:
Пишем код:
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; }