Это корневой алгоритм

#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