Python раунд до следующей наивысшей степени 10 - PullRequest
44 голосов
/ 03 апреля 2020

Как мне удастся выполнить math.ceil так, чтобы число присваивалось следующей наибольшей степени 10?

# 0.04  ->  0.1
# 0.7   ->  1
# 1.1   ->  10  
# 90    ->  100  
# ...

Моим текущим решением является словарь, который проверяет диапазон входного числа, но он жестко запрограммирован, и я бы предпочел однострочное решение. Может быть, я упускаю простой математический трюк или соответствующую numpy функцию здесь?

Ответы [ 5 ]

60 голосов
/ 03 апреля 2020

Вы можете использовать math.ceil с math.log10, чтобы сделать это:

>>> 10 ** math.ceil(math.log10(0.04))
0.1
>>> 10 ** math.ceil(math.log10(0.7))
1
>>> 10 ** math.ceil(math.log10(1.1))
10
>>> 10 ** math.ceil(math.log10(90))
100

log10(n) дает вам решение x, которое удовлетворяет 10 ** x == n, поэтому, если вы округлите x, это даст вам показатель степени для следующей наивысшей степени 10.

Обратите внимание , что для значения n, где x уже целое число, "следующая наивысшая степень 10" будет n:

>>> 10 ** math.ceil(math.log10(0.1))
0.1
>>> 10 ** math.ceil(math.log10(1))
1
>>> 10 ** math.ceil(math.log10(10))
10
21 голосов
/ 04 апреля 2020

Ваша проблема недостаточно указана, вам нужно отступить назад и задать несколько вопросов.

  • Какой тип (ы) ваши входные данные?
  • Какой тип (типы) делают Вы хотите для своих результатов?
  • Для результатов меньше 1, что именно вы хотите округлить? Вы хотите фактические степени 10 или приближения с плавающей точкой степеней 10? Вы знаете, что отрицательные степени 10 не могут быть выражены точно с плавающей точкой, верно? Предположим на данный момент, что вы хотите приближения с плавающей запятой степеней 10.
  • Если входное значение равно степени 10 (или ближайшее приближение с плавающей запятой степени 10), если выходное значение будет таким же как вход? Или это должна быть следующая степень 10? «10 -> 10» или «10 -> 100»? Давайте пока предположим первое.
  • Могут ли ваши входные значения быть любыми возможными значениями рассматриваемых типов? или они более ограничены.

В другом ответе было предложено взять логарифм, затем округлить (функция потолка), затем возвести в степень.

def nextpow10(n):
    return 10 ** math.ceil(math.log10(n))

К сожалению, это страдает от ошибки округления. Прежде всего, n преобразуется из любого типа данных, в который он входит, в число с плавающей запятой двойной точности, что потенциально может привести к ошибкам округления, а затем вычисляется логарифм, потенциально вносящий больше ошибок округления как во внутренние вычисления, так и в его результат.

Таким образом, мне не понадобилось много времени, чтобы найти пример, в котором он дал неверный результат.

>>> import math
>>> from numpy import nextafter
>>> n = 1
>>> while (10 ** math.ceil(math.log10(nextafter(n,math.inf)))) > n:
...     n *= 10
... 
>>> n
10
>>> nextafter(n,math.inf)
10.000000000000002
>>> 10 ** math.ceil(math.log10(10.000000000000002))
10

Теоретически возможно, что он потерпит неудачу в другом направлении, хотя, похоже, это так. провоцировать гораздо труднее.

Так что для надежного решения для чисел с плавающей запятой и целочисленных значений нам нужно предположить, что значение нашего логарифма является лишь приблизительным, и поэтому мы должны протестировать пару возможностей. Что-то вроде

def nextpow10(n):
    p = round(math.log10(n))
    r = 10 ** p
    if r < n:
        r = 10 ** (p+1) 
    return r;

Я считаю, что этот код должен давать правильные результаты для всех аргументов в разумном диапазоне величин реального мира. Он сломается для очень малого или очень большого числа нецелочисленных и не плавающих типов из-за проблем, конвертирующих их в плавающую Python особые случаи целочисленные аргументы для функции log10 в попытке предотвратить переполнение, но все же с достаточно большим целым числом может быть возможно вызвать неправильные результаты из-за ошибок округления.

Для тестирования двух реализаций I использовала следующую тестовую программу.

n = -323 # 10**-324 == 0
while n < 1000:
    v = 10 ** n
    if v != nextpow10(v): print(str(v)+" bad")
    try:
        v = min(nextafter(v,math.inf),v+1)
    except:
        v += 1
    if v > nextpow10(v): print(str(v)+" bad")
    n += 1

Это находит множество сбоев в простой реализации, но ни одной в улучшенной реализации.

3 голосов
/ 07 апреля 2020

Кажется, вы хотите получить наименьшую следующую степень 10 ... Вот способ, использующий чистую математику и не бревно, а рекурсию.

0 голосов
/ 13 апреля 2020
y = math.ceil(x)
z = y + (10 - (y % 10))

Нечто подобное может быть? Это просто у меня в голове, но сработало, когда я попробовал несколько номеров в терминале.

0 голосов
/ 05 апреля 2020

Проверьте это!

>>> i = 0.04123; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )               
0.04123 0.1
>>> i = 0.712; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                 
0.712 1
>>> i = 1.1; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                   
1.1 10
>>> i = 90; print i, 10 ** len( str( int( i ) ) ) if int( i ) > 1  else 10 if i > 1.0 else 1 if i > 0.1 else  10 ** ( 1 - min( [ ("%.100f" % i ).replace('.','').index( k ) for k in [ str( j ) for j in xrange( 1, 10 ) if str( j ) in "%.100f" % i  ] ]  ) )                    
90 100

Этот код основан на принципе силы десяти в len( str( int( float_number ) ) ).

Существует 4 случая:

    1. int( i ) > 1.

    Float число - преобразуется в int, после этого строка str() из него даст нам string с length, что мы смотрим точно. Итак, первая часть, для ввода i > 1.0 - это мощность десяти 10 этой длины.

  • & 3. Небольшое разветвление: i > 1.0 и i > 0.1 <=> это 10 и 1 соответственно.
  • И последний случай, когда i < 0.1: Здесь десять должно быть в отрицательной степени. Чтобы получить первый ненулевой элемент после запятой, я использовал такую ​​конструкцию ("%.100f" % i ).replace('.','').index( k ), где k пробегает интервал [1:10]. После этого возьмите минимум списка результатов. И уменьшить на единицу, это первый ноль, который должен быть посчитан. Кроме того, здесь стандартный python index() может создать sh, если он не найдет хотя бы один из ненулевого элемента из интервала [1:10], поэтому в конце я должен «отфильтровать» листинг по вхождение: if str( j ) in "%.100f" % i. Кроме того, чтобы получить более глубокую точность, можно взять %.100f.
...