Раунд Питона до следующей высшей степени 10

44

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

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

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

offeltoffel
источник
3
@ bold выглядит так, будто эти решения работают 10сверху, для этого нужно что-то, например log10.
Джоншарп
3
Слово, которое вы хотите, это «сила». Может быть, вы получили неправильный перевод слова вашего родного языка для этого.
user2357112 поддерживает Monica
Спасибо, Моника! @bold: я нашел этот вопрос, но это другая проблема. Jonrsharpe предоставил идеальный ответ
offeltoffel
2
Это также связано с порядком величины . 1 - 0-й порядок, 10 - 1-й порядок, 100 - 2-й порядок и т. Д.
wjandrea

Ответы:

60

Вы можете использовать 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
jonrsharpe
источник
1
Работа с функцией журнала, кажется, просто уловка, которую я не смог придумать. Я верю, что это именно то, на что я надеялся! Большое спасибо
offeltoffel
2
NB: в зависимости от вашего желаемого поведения, это не работает для степеней 10, например 10 ** math.ceil(math.log10(1)) == 1, что не является «следующей высшей силой»
Cireo
5
Примечание: этот ответ опирается на арифметику с плавающей запятой, и как таковой он может потерпеть неудачу из-за ошибок округления. Попробуйте кормить в 1000000000000001 например.
plugwash
2
@plugwash не обязательно, математические функции также будут принимать, например, decimal.Decimals.
Джонршарп
5
Да, вы можете передавать другие типы, но они будут преобразованы в число с плавающей запятой двойной точности и переданы в функцию C "log10". Существует особый случай, чтобы предотвратить переполнение журналов больших чисел, но ничего, чтобы предотвратить ошибки округления.
plugwash
21

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

  • Какой тип (ы) ваши входы?
  • Какой тип (ы) вы хотите для ваших результатов?
  • Для результатов менее 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 в попытке предотвратить переполнение, но все же с достаточно большим целым числом может быть возможно вызвать неверные результаты из-за ошибок округления.

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

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

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

plugwash
источник
Спасибо за ваше усилие углубиться в детали здесь. Хотя ответ Джонршарпа уже решил мою проблему, этот ответ может быть полезен для других с похожими, но более конкретными вопросами.
Offeltoffel
1
Почему вы используете roundвместо math.ceil? Это приведет к множеству ненужных случаев, когда r < nэто правда, и поэтому необходимо выполнить дополнительную работу.
a_guest
1
Потому что журнал может быть отключен в любом направлении.
plugwash
1
используя «улучшенный» код , но с круглой заменен результатами Math.ceil в авариях для 1e-317 на нижнем конце и 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 на высоком конце.
plugwash
1
(на практике это, вероятно, хорошо)
plugwash
3

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

def ceiling10(x):
    if (x > 10):
        return ceiling10(x / 10) * 10
    else:
        if (x <= 1):
            return ceiling10(10 * x) / 10
        else:
            return 10
for x in [1 / 1235, 0.5, 1, 3, 10, 125, 12345]:
    print(x, ceiling10(x))
Сильвен Дюпертуа
источник
Только что протестировав этот, я даю ему оценку, поскольку в большинстве практических случаев он работает хорошо, но, похоже, он страдает от ошибок округления при достаточно малых входных данных. потолок 10 (1e-6) дает 1.0000000000000002e-06
plugwash
0
y = math.ceil(x)
z = y + (10 - (y % 10))

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

Lugene
источник
0

Проверь это!

>>> 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в степени этой длины.

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