Как округлить число до значительных цифр в Python

Мне нужно округлить поплавок, который будет отображаться в пользовательском интерфейсе. Например, к одной значительной цифре:

1234 → 1000

0,12 → 0,1

0,012 → 0,01

0,062 → 0,06

6253 → 6000

1999 → 2000

Есть ли хороший способ сделать это, используя библиотеку Python, или мне самому написать его?

Ответ 1

Вы можете использовать отрицательные числа для округления целых чисел:

>>> round(1234, -3)
1000.0

Таким образом, если вам нужна только самая значимая цифра:

>>> from math import log10, floor
>>> def round_to_1(x):
...   return round(x, -int(floor(log10(abs(x)))))
... 
>>> round_to_1(0.0232)
0.02
>>> round_to_1(1234243)
1000000.0
>>> round_to_1(13)
10.0
>>> round_to_1(4)
4.0
>>> round_to_1(19)
20.0

Вам, вероятно, придется позаботиться о том, чтобы повернуть float в целое число, если оно больше 1.

Ответ 2

% g в форматировании строк будет форматировать поплавок, округленный до некоторого количества значимых цифр. Он иногда использует "e" научную нотацию, поэтому конвертирует округленную строку обратно в float, а затем через форматирование строки% s.

>>> '%s' % float('%.1g' % 1234)
'1000'
>>> '%s' % float('%.1g' % 0.12)
'0.1'
>>> '%s' % float('%.1g' % 0.012)
'0.01'
>>> '%s' % float('%.1g' % 0.062)
'0.06'
>>> '%s' % float('%.1g' % 6253)
'6000.0'
>>> '%s' % float('%.1g' % 1999)
'2000.0'

Ответ 3

Если вы хотите иметь не более 1 значащего десятичного знака (в противном случае такой же, как у Евгения):

>>> from math import log10, floor
>>> def round_sig(x, sig=2):
...   return round(x, sig-int(floor(log10(abs(x))))-1)
... 
>>> round_sig(0.0232)
0.023
>>> round_sig(0.0232, 1)
0.02
>>> round_sig(1234243, 3)
1230000.0

Ответ 4

print('{:g}'.format(float('{:.1g}'.format(12.345))))

Это решение отличается от всех остальных тем, что:

  1. он точно решает вопрос ОП
  2. not не требуется дополнительный пакет
  3. не требуется никакой пользовательской вспомогательной функции или математической операции

Для произвольного числа n значащих цифр вы можете использовать:

print('{:g}'.format(float('{:.{p}g}'.format(i, p=n))))

Тест:

a = [1234, 0.12, 0.012, 0.062, 6253, 1999, -3.14, 0., -48.01, 0.75]
b = ['{:g}'.format(float('{:.1g}'.format(i))) for i in a]
# b == ['1000', '0.1', '0.01', '0.06', '6000', '2000', '-3', '0', '-50', '0.8']

Примечание: в этом решении невозможно динамически адаптировать число значащих цифр к входным данным, поскольку не существует стандартного способа различения чисел с различным числом конечных нулей (3.14 == 3.1400). Если вам нужно сделать это, то нужны нестандартные функции, подобные тем, которые представлены в пакете to-precision.

Ответ 5

Я создал пакет с высокой точностью, который делает то, что вы хотите. Это позволяет вам давать своим цифрам более или менее значимые цифры.

Также выводятся стандартные, научные и инженерные обозначения с указанным количеством значащих цифр.

В принятом ответе есть строка

>>> round_to_1(1234243)
1000000.0

Это на самом деле определяет 8 сиг фиг. Для числа 1234243 моя библиотека отображает только одну значимую цифру:

>>> from to_precision import to_precision
>>> to_precision(1234243, 1, 'std')
'1000000'
>>> to_precision(1234243, 1, 'sci')
'1e6'
>>> to_precision(1234243, 1, 'eng')
'1e6'

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

>>> to_precision(599, 2)
'600'
>>> to_precision(1164, 2)
'1.2e3'

Ответ 6

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

Для этого нам нужно знать, что наибольшая мощность на 10 меньше целого. Для этого мы можем использовать пол функции журнала 10.

from math import log10, floor
def round_int(i,places):
    if i == 0:
        return 0
    isign = i/abs(i)
    i = abs(i)
    if i < 1:
        return 0
    max10exp = floor(log10(i))
    if max10exp+1 < places:
        return i
    sig10pow = 10**(max10exp-places+1)
    floated = i*1.0/sig10pow
    defloated = round(floated)*sig10pow
    return int(defloated*isign)

Ответ 7

Я изменил решение Indgar для обработки отрицательных чисел и небольших чисел (включая ноль).

from math import log10, floor
def round_sig(x, sig=6, small_value=1.0e-9):
    return round(x, sig - int(floor(log10(max(abs(x), abs(small_value))))) - 1)

Ответ 8

def round_to_n(x, n):
    if not x: return 0
    power = -int(math.floor(math.log10(abs(x)))) + (n - 1)
    factor = (10 ** power)
    return round(x * factor) / factor

round_to_n(0.075, 1)      # 0.08
round_to_n(0, 1)          # 0
round_to_n(-1e15 - 1, 16) # 1000000000000001.0

Надеюсь, что мы возьмем все ответы выше (минус возможность поставить его как одну линию лямбда;)). Еще не изучили, не стесняйтесь редактировать этот ответ:

round_to_n(1e15 + 1, 11)  # 999999999999999.9

Ответ 9

Если вы хотите округлить без использования строк, ссылка, которую я нашел, похоронена в комментариях выше:

http://code.activestate.com/lists/python-tutor/70739/

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

Код по ссылке состоит из трех строк: def, doc и return. В нем есть ошибка: вам нужно проверить наличие логарифмов. Это просто. Сравните входные данные с sys.float_info.min. Полное решение:

import sys,math

def tidy(x, n):
"""Return 'x' rounded to 'n' significant digits."""
y=abs(x)
if y <= sys.float_info.min: return 0.0
return round( x, int( n-math.ceil(math.log10(y)) ) )

Он работает для любого скалярного числового значения, и n может быть float если вам необходимо сместить ответ по какой-либо причине. Вы можете фактически увеличить предел:

sys.float_info.min*sys.float_info.epsilon

без провокации ошибки, если по какой-то причине вы работаете с минимальными значениями.

Ответ 10

Чтобы прямо ответить на вопрос, вот моя версия с использованием имен из функции R:

import math

def signif(x, digits=6):
    if x == 0 or not math.isfinite(x):
        return x
    digits -= math.ceil(math.log10(abs(x)))
    return round(x, digits)

Моей главной причиной для публикации этого ответа являются комментарии с жалобами на то, что "0,075" округляется до 0,07, а не 0,08. Это связано с тем, что "Новичок С" указывает на комбинацию арифметики с плавающей запятой, имеющей как конечную точность, так и представление с основанием-2. Ближайшее к 0,075 число, которое фактически может быть представлено, немного меньше, поэтому округление получается не так, как вы могли наивно ожидать.

Также обратите внимание, что это относится к любому использованию недесятичной арифметики с плавающей запятой, например, C и Java имеют одинаковую проблему.

Чтобы показать более подробно, мы просим Python отформатировать число в шестнадцатеричном формате:

0.075.hex()

что дает нам: 0x1.3333333333333p-4. Причина этого заключается в том, что нормальное десятичное представление часто включает в себя округление и, следовательно, не то, как компьютер фактически "видит" число. Если вы не привыкли к этому формату, пара полезных ссылок - это документы по Python и стандарт Си.

Чтобы показать, как работают эти числа, мы можем вернуться к нашей отправной точке, выполнив:

0x13333333333333 / 16**13 * 2**-4

который должен распечатать 0.075. 16**13 - потому что после десятичной запятой есть 13 шестнадцатеричных цифр, а 2**-4 - потому что шестнадцатеричные показатели являются основанием-2.

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

from decimal import Decimal

Decimal(0x13333333333333) / 16**13 / 2**4

давая: 0.07499999999999999722444243844 и, надеюсь, объясняя, почему round(0.075, 2) оценивается в 0.07

Ответ 11

Я не могу придумать ничего, что могло бы справиться с этим из коробки. Но он довольно хорошо обрабатывается для чисел с плавающей запятой.

>>> round(1.2322, 2)
1.23

Целые числа сложнее. Они не хранятся как база 10 в памяти, поэтому значимые места не являются естественным делом. Это довольно тривиально реализовать, как только они являются строкой.

Или для целых чисел:

>>> def intround(n, sigfigs):
...   n = str(n)
...   return n[:sigfigs] + ('0' * (len(n)-(sigfigs)))

>>> intround(1234, 1)
'1000'
>>> intround(1234, 2)

Если вы хотите создать функцию, которая обрабатывает любое число, мое предпочтение будет состоять в том, чтобы преобразовать их в строки и искать десятичное место, чтобы решить, что делать:

>>> def roundall1(n, sigfigs):
...   n = str(n)
...   try:
...     sigfigs = n.index('.')
...   except ValueError:
...     pass
...   return intround(n, sigfigs)

Другой вариант - проверить тип. Это будет гораздо менее гибким и, вероятно, не будет хорошо сочетаться с другими числами, такими как Decimal objects:

>>> def roundall2(n, sigfigs):
...   if type(n) is int: return intround(n, sigfigs)
...   else: return round(n, sigfigs)

Ответ 12

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

import decimal
from math import log10, floor

def myrounding(value , roundstyle='ROUND_HALF_UP',sig = 3):
    roundstyles = [ 'ROUND_05UP','ROUND_DOWN','ROUND_HALF_DOWN','ROUND_HALF_UP','ROUND_CEILING','ROUND_FLOOR','ROUND_HALF_EVEN','ROUND_UP']

    power =  -1 * floor(log10(abs(value)))
    value = '{0:f}'.format(value) #format value to string to prevent float conversion issues
    divided = Decimal(value) * (Decimal('10.0')**power) 
    roundto = Decimal('10.0')**(-sig+1)
    if roundstyle not in roundstyles:
        print('roundstyle must be in list:', roundstyles) ## Could thrown an exception here if you want.
    return_val = decimal.Decimal(divided).quantize(roundto,rounding=roundstyle)*(decimal.Decimal(10.0)**-power)
    nozero = ('{0:f}'.format(return_val)).rstrip('0').rstrip('.') # strips out trailing 0 and .
    return decimal.Decimal(nozero)


for x in list(map(float, '-1.234 1.2345 0.03 -90.25 90.34543 9123.3 111'.split())):
    print (x, 'rounded UP: ',myrounding(x,'ROUND_UP',3))
    print (x, 'rounded normal: ',myrounding(x,sig=3))

Ответ 13

Использование python 2. 6+ новое форматирование -style (как% -style устарело):

>>> "{0}".format(float("{0:.1g}".format(1216)))
'1000.0'
>>> "{0}".format(float("{0:.1g}".format(0.00356)))
'0.004'

В питоне 2. 7+ вы можете опустить ведущие 0 с.

Ответ 14

Эта функция выполняет нормальный раунд, если число больше 10 ** (-decimal_positions), в противном случае добавляет больше десятичной дроби, пока не будет достигнуто количество значащих десятичных позиций:

def smart_round(x, decimal_positions):
    dp = - int(math.log10(abs(x))) if x != 0.0 else int(0)
    return round(float(x), decimal_positions + dp if dp > 0 else decimal_positions)

Надеюсь, поможет.

Ответ 15

https://stackoverflow.com/users/1391441/gabriel, отвечает ли следующее ваше беспокойство по поводу rnd (.075, 1)? Предупреждение: возвращает значение в виде числа с плавающей запятой

def round_to_n(x, n):
    fmt = '{:1.' + str(n) + 'e}'    # gives 1.n figures
    p = fmt.format(x).split('e')    # get mantissa and exponent
                                    # round "extra" figure off mantissa
    p[0] = str(round(float(p[0]) * 10**(n-1)) / 10**(n-1))
    return float(p[0] + 'e' + p[1]) # convert str to float

>>> round_to_n(750, 2)
750.0
>>> round_to_n(750, 1)
800.0
>>> round_to_n(.0750, 2)
0.075
>>> round_to_n(.0750, 1)
0.08
>>> math.pi
3.141592653589793
>>> round_to_n(math.pi, 7)
3.141593

Ответ 16

Это возвращает строку, так что результаты без дробных частей и небольшие значения, которые в противном случае появлялись бы в нотации E, отображаются правильно:

def sigfig(x, num_sigfig):
    num_decplace = num_sigfig - int(math.floor(math.log10(abs(x)))) - 1
    return '%.*f' % (num_decplace, round(x, num_decplace))

Ответ 17

На вопрос, на который так тщательно ответили, почему бы не добавить еще один

Это немного лучше подходит для моей эстетики, хотя многие из вышеперечисленных сравнимы

import numpy as np

number=-456.789
significantFigures=4

roundingFactor=significantFigures - int(np.floor(np.log10(np.abs(number)))) - 1
rounded=np.round(number, roundingFactor)

string=rounded.astype(str)

print(string)

Это работает для отдельных чисел и числовых массивов и отлично работает для отрицательных чисел.

Есть еще один шаг, который мы могли бы добавить - np.round() возвращает десятичное число, даже если округлено целое число (т.е. для значимых значений = 2, мы можем ожидать возврата -460, но вместо этого мы получаем -460.0). Мы можем добавить этот шаг, чтобы исправить это:

if roundingFactor<=0:
    rounded=rounded.astype(int)

К сожалению, этот последний шаг не будет работать для массива чисел - я оставлю это вам, дорогой читатель, чтобы выяснить, если вам нужно.

Ответ 18

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

numpy.format_float_positional напрямую поддерживает желаемое поведение. Следующий фрагмент возвращает число с плавающей точкой x, отформатированное до 4 значащих цифр, с подавленной научной нотацией.

import numpy as np
x=12345.6
np.format_float_positional(x, precision=4, unique=False, fractional=False, trim='k')
> 12340.