#python #python-2.7 #floating-point #precision
#python #python-2.7 #с плавающей запятой #точность
Вопрос:
Я создаю функцию, чтобы определить, было ли число корнем (не уверен, что это правильный термин) другого числа.
def isRoot(n, root):
return not math.log(n, root)%1
По большей части это работает, но я обнаружил, что у меня возникла проблема с плавающей запятой. Например, если я это сделаю isRoot(125,5)
, я получу False
. После некоторого устранения неполадок я обнаружил, что причина в том, что
>>> math.log(125,5)
3.0000000000000004
Хотя результат должен быть 3
. Итак, мой вопрос в том, должен ли я просто использовать другой алгоритм, о котором я не знаю? Или есть способ гарантировать, что это будет работать правильно, независимо от того, насколько большое число я использую?
Комментарии:
1. Может быть, у вас было бы меньше проблем
def isSquare(root, n):
.2. @Efferalgan Это не будет делать то, что мне нужно…
3. Я просто предлагал альтернативный подход, предложенный в ответе.
Ответ №1:
Лучший способ избежать проблемы — использовать другой подход, который полностью исключает математику с плавающей запятой.
def isRoot(n, root):
return n <= 1 or (False if n % root != 0 else isRoot(n // root, root))
Комментарии:
1. Конечно, я не хочу
isRoot(0.5, 3)
быть правдой.2. @AlexHall, конечно, нет. Я предполагал, что входные данные всегда будут целыми числами> 1.
Ответ №2:
Как насчет этого:
def isRoot(n, root):
power = 1
while root ** power < n:
power = 1
return root ** power == n
Если это слишком медленно, вы также можете выполнить своего рода двоичный поиск, чтобы уменьшить количество проверок.
Комментарии:
1. Я действительно ищу решение с одной проверкой. Я понял, мне просто нужно, чтобы он был более точным.
Ответ №3:
Для работы вашей функции вы полагаетесь на число с плавающей запятой, точно равное 0 (False). Вообще говоря, вам следует избегать проверки на равенство при работе с числами с плавающей запятой. Вместо этого лучше установить приемлемый уровень допуска для разницы.
Попробуйте это вместо:
def isRoot(n, root, epsilon=1e-10):
test = math.log(n, root)%1
return abs(test - int(round(test))) < epsilon
Комментарии:
1. Помните, что ошибка округления может быть как низкой, так и высокой.
2. Это должно учитывать абсолютное значение.
3. @MarkRansom просто ищет число, которое достаточно близко, действительный способ справиться с этим? Похоже, должен быть более определенный способ сделать это.
4. Нет, взятие абсолютного значения ничего не изменит — результатом
%1
всегда будет число в диапазоне[0, 1)
. @Hoopdady да, это правильный подход, просто этот ответ еще не на 100% правильный. И он терпит неудачу, когда ваши числа становятся достаточно большими, чтобыlog
быть в пределах вашего допуска, даже если это не корень.5. Ах да, вы абсолютно правы @MarkRansom. Теперь я понимаю, что абсолютное значение на самом деле здесь ничего не делает, так как
math.log()
имеет ошибку домена для отрицательных чисел. Ваш подход, описанный выше, намного лучше, и он работает для отрицательных чисел.
Ответ №4:
Если вы не возражаете против снижения производительности, вы можете посмотреть decimal
модуль в стандартной библиотеке. У него есть числа произвольной точности.
Комментарии:
1. Эта проблема не может быть решена путем повышения точности.
2. @user2357112 Да, я думаю, ты прав. Есть предложения?
3. @Hoopdady: если все ваши входные данные будут целыми числами, просто используйте integer math, что-то вроде реализации Алекса Холла. Если ваши входные данные могут быть с плавающей запятой, идея попытаться сделать это вообще с числами с плавающей запятой вызывает подозрение. Для ввода с плавающей запятой лучшим вариантом, вероятно, было бы написать
is_near_root
функцию и ввести допуск при сравнении, что-то вроде предложения Пола.
Ответ №5:
Просто округлите до целого числа и проверьте:
def isRoot(n, root):
power = int(round(math.log(n, root)))
return root ** power == n