Коварный дабл
Вы и так это знали:
Тип 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); if (res - Math.floor(res) != 0) { return -1; } 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);