Як округлити число до значущих цифр в Python


148

Мені потрібно округлити поплавок, щоб відобразитися в інтерфейсі користувача. Наприклад, до однієї важливої ​​цифри:

1234 -> 1000

0,12 -> 0,1

0,012 -> 0,01

0,062 -> 0,06

6253 -> 6000

1999 -> 2000

Чи є приємний спосіб зробити це за допомогою бібліотеки Python, чи я повинен сам це написати?


2
Ви просто форматуєте вихід? Ви питаєте про це? docs.python.org/library/stdtypes.html#string-форматування чи це? docs.python.org/library/string.html#string-formatting
S.Lott

який вихід ви очікуєте для 0,062 та 6253?
lamirap

Тепер пакет з точністю робить це. У моїй опублікованій відповіді детально описано, як це стосується
Вільям Раснак

Відповіді:


146

Ви можете використовувати від’ємні числа для круглих цілих чисел:

>>> 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

Ймовірно, вам доведеться подбати про перетворення поплавця на ціле число, якщо воно більше 1.


3
Це правильне рішення. Використання log10- єдиний правильний спосіб визначити, як округлити його.
Вольф

73
round_to_n = lambda x, n: круглий (x, -int (пол (log10 (x))) + + (n - 1))
Roy Hyunjin Han

28
Ви повинні використовувати log10(abs(x)), інакше негативні цифри не вдасться (І лікувати x == 0окремо, звичайно)
Тобіас Кіенцлер

2
Я створив пакет, який робить це зараз, і, ймовірно, простіше і надійніше, ніж цей. Посилання , посилання Repo . Сподіваюся, це допомагає!
Вільям Раснак

2
round_to_n = lambda x, n: x if x == 0 else round(x, -int(math.floor(math.log10(abs(x)))) + (n - 1))захищає від x==0і x<0дякую @RoyHyunjinHan та @TobiasKienzler. Не захищено від невизначених, як math.inf, або сміття, як None тощо
AJP

98

% g у форматі рядків буде форматувати поплавок, округлений до деякої кількості значущих фігур. Іноді буде використано наукове позначення 'e', ​​тому перетворіть закруглену рядок назад у поплавок, а потім через формат рядків% 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'

7
Вимога ОП в 1999 році була відформатована як "2000", а не як "2000.0". Я не бачу тривіального способу змінити ваш метод, щоб досягти цього.
Тім Мартін

1
Це просто те, чого я завжди хотів! де ти це знайшов?
djhaskin987

12
Зауважте, що поведінка% g не завжди є правильною. Зокрема, він завжди обрізає кінцеві нулі, навіть якщо вони значні. Число 1.23400 має 6 значущих цифр, але "% .6g"% (1.23400) призведе до "1.234", що невірно. Більше деталей у цьому дописі в блозі: randlet.com/blog/python-significant-figures-format
randlet

3
Так само , як і метод у відповідь Євгена, це не може правильно круглі 0.075з 0.08. Він 0.07замість цього повертається .
Габріель

1
round_sig = lambda f,p: float(('%.' + str(p) + 'e') % f)дозволяє регулювати кількість значущих цифр!
денібс

49

Якщо ви хочете мати інший, ніж один значний десятковий (інакше такий же, як Євгеній):

>>> 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

8
round_sig (-0.0232) -> помилка домену математики, ви можете додати там abs ();)
dgorissen

2
Так само , як і методи у відповідях Євгенія і Пітера Грема, це не вдається правильно круглі 0.075з 0.08. Він 0.07замість цього повертається .
Габріель

3
Крім того, він не вдається для round_sig (0).
Юваль Ацмон

2
@Gabriel Це вбудована "особливість" python, що працює на вашому комп'ютері, і проявляється в його поведінці функції round. docs.python.org/2/tutorial/floatingpoint.html#tut-fp-isissue
C

1
@Gabriel Я додав відповідь, яка пояснює, чому слід очікувати повернення 0,7 від округлення "0,075"! дивіться stackoverflow.com/a/56974893/1358308
Сем Мейсон

30
f'{float(f"{i:.1g}"):g}'
# Or with Python <3.6,
'{:g}'.format(float('{:.1g}'.format(i)))

Це рішення відрізняється від усіх інших тим, що:

  1. він точно вирішує питання ОП
  2. йому не потрібен додатковий пакет
  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). Якщо вам потрібно це зробити, тоді потрібні нестандартні функції, як ті, що надаються в точному пакеті.


FYI: Я знайшов це рішення незалежно від eddygeek, коли я намагався вирішити ту саму проблему в одному зі свого коду. Тепер я розумію, що моє рішення, очевидно, майже ідентичне його (я просто помітив помилковий вихід і не потрудився читати код, моя помилка). Можливо, короткого коментаря під його відповіддю було б достатньо замість нової відповіді ... Єдина (ключова) різниця - це подвійне використання :gформатера, який зберігає цілі числа.
Фолкен

Нічого собі, вашу відповідь потрібно читати зверху вниз;) Цей подвійний трюк брудний, але акуратний. (Зауважте, що у форматі 1999 року було 2000.0 запропоновано 5 значущих цифр, тому його потрібно {:g}повторити повторно.) Взагалі цілі числа із задніми нулями неоднозначні щодо значущих цифр, якщо не використовується якась техніка (як накреслення вище останнього значущого).
Томаш Гандор

8

Я створив пакет з точністю, який робить те, що ви хочете. Це дозволяє надати вашим номерам більш-менш значні цифри.

Він також виводить стандартні, наукові та інженерні позначення із визначеною кількістю значущих цифр.

У прийнятій відповіді є рядок

>>> 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'

Зараз я шукаю те саме, але застосовано до pandas df
mhoff

@mhoff ви, ймовірно, можете використовувати карту панд з лямбда. lambda x: to_precision(x, 2)
Вільям Раснак

Додайте це до (PyPI) [ pypi.org/] . Нічого подібного не існує, наскільки я можу сказати.
Моргот

це великий пакет , але я думаю , що більшість функцій в даний час в модулі sigfig
гіперактив

1
він має помилку: std_notation (9.999999999999999e-05, 3) дає: '0,00010', що є лише 2 значущими цифрами
Борис Малдер,

5

Щоб округлити ціле число до 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)

1
Плюс один для рішення, яке працює без круглих пітонів (.., цифр) і без доданих рядків!
Стів Роджерс

5

Щоб безпосередньо відповісти на питання, ось моя версія з використанням імен з функції 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 відформатувати число у форматі "hex":

0.075.hex()

який дає нам: 0x1.3333333333333p-4. Причиною цього є те, що звичайне десяткове представлення часто включає округлення, а значить, не те, як комп'ютер насправді "бачить" число. Якщо ви не звикли до цього формату, кілька корисних посилань є документи Python і стандартний C .

Щоб показати, як ці номери трохи працюють, ми можемо повернутися до початкової точки, зробивши:

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


1
Це чудове пояснення того, чому 0,075 округлюється до 0,07 на рівні коду , але нас (у фізичних науках) вчили завжди округлювати не вниз. Таким чином, очікувана поведінка насправді має 0,08 в результаті, питання точності з плаваючою точкою незважаючи на це.
Габріель

1
Я не впевнений, де твоя плутанина: коли ти вводиш 0,075, ти фактично вводиш ~ 0,07499 (як вище), який обходить за нормальними математичними правилами. якщо ви використовували тип даних (наприклад, десяткова плаваюча точка ), який міг би представляти 0,075, він дійсно повинен
Сем Мейсон

Я не плутаюся. Коли я ввожу 0,075, я фактично ввожу 0,075. Що б не сталося з математикою з плаваючою комою всередині коду, мені все одно.
Габріель

@Gabriel: А якщо б навмисно введений 0.074999999999999999, то , що ви очікуєте отримати в цьому випадку?
Марк Дікінсон

@ МаркДікінсон, що залежить. Одна значна цифра: 0,07, дві: 0,075.
Габріель

4
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

4

Я змінив рішення 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)

Чому б не просто перевірити, чи x == 0? Якщо ви любите однолінійку, просто return 0 if x==0 else round(...).
pjvandehaar

2
@pjvandehaar, ви правильні для загального випадку, і я мав би це зробити. Крім того, для чисельних обчислень, які мені потрібно виконати, ми періодично отримуємо числа типу 1e-15. У нашій програмі ми хочемо порівняння двох малих чисел (одне з яких може бути нульовим) вважати рівним. Також деякі люди хочуть округлити невеликі числа (це може бути 1e-9, 1e-15 або навіть 1e-300) до нуля.
ryan281

1
Цікаво. Дякуємо, що пояснили це. У такому випадку мені дуже подобається таке рішення.
pjvandehaar

@Morgoth Це цікава і складна проблема. Як ви вказали, друковане значення не містить 3 значущих цифр, але значення є правильним (наприклад 0.970 == 0.97). Я думаю, ви могли б використовувати деякі інші рішення для друку, наприклад, f'{round_sig(0.9701, sig=3):0.3f}'якщо ви хочете, щоб нульове надруковане.
ryan281

3

Якщо ви хочете обійти без залучення рядків, посилання, яке я знайшов закопаним у коментарях вище:

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 може бути a, floatякщо вам потрібно змінити відповідь з якихось причин. Ви можете фактично натиснути межу на:

sys.float_info.min*sys.float_info.epsilon

не провокуючи помилку, якщо ви з якоїсь причини працюєте з мінімальними значеннями.


2

Я не можу придумати нічого, що могло б вирішити це поза коробкою. Але це досить добре справляється з числами з плаваючою комою.

>>> 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об'єкти:

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

Просто возитися з рядками не округлить цифри. 1999 рік округлюється до 1 значущої цифри - 2000, а не 1000.
Пітер Грем

Проблема добре обговорена в архіві на ActiveState code.activestate.com/lists/python-tutor/70739
Тім Макнамара

2

Опублікована відповідь була найкраще доступною при наданні, але вона має ряд обмежень і не дає технічно правильних значущих цифр.

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.

У документації (переміщено до numpy.org/doc/stable/reference/generated/… ) зазначено, що ця функція реалізує алгоритм Dragon4 (Steele & White 1990, dl.acm.org/doi/pdf/10.1145/93542.93559 ). Це дає дратівливі результати, наприклад print(*[''.join([np.format_float_positional(.01*a*n,precision=2,unique=False,fractional=False,trim='k',pad_right=5) for a in [.99, .999, 1.001]]) for n in [8,9,10,11,12,19,20,21]],sep='\n'). Я сам не перевірив Dragon4.
Rainald62

0

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

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))

0

Використання форматування нового стилю python 2.6+ (як% -style застаріле):

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

У python 2.7+ ви можете опустити провідні 0s.


З якою версією пітона? Python 3.6.3 | Anaconda, Inc. | (за замовчуванням, 13 жовтня 2017, 12:02:49) має таку саму стару проблему округлення. "{0}". Формат (float ("{0: .1g}". Формат (0.075))) дає "0,07", а не "0,08"
Дон Маклаклан

@DonMclachlan Я додав пояснення, чому цього очікується в stackoverflow.com/a/56974893/1358308
Сем Мейсон

0

Ця функція робить звичайний раунд, якщо число більше 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)

Сподіваюся, це допомагає.


0

https://stackoverflow.com/users/1391441/gabriel , чи вирішує наступне ваше питання щодо проблеми rnd (.075, 1)? Caveat: повертає значення у вигляді поплавця

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

0

Це повертає рядок, так що результати без дробових частин і малі значення, які в іншому випадку відображатимуться в позначеннях 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))

0

Давши запитання настільки ґрунтовно відповів, чому б не додати ще одне

Це трохи підходить моїй естетиці, хоча багато з перерахованого вище можна порівняти

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)

На жаль, цей останній крок не спрацює для масиву чисел - це я залишу для вас, шановний читачу, щоб розібратися, якщо вам це потрібно.


0

Sigfig пакет / бібліотека охоплює це. Після установки ви можете зробити наступне:

>>> from sigfig import round
>>> round(1234, 1)
1000
>>> round(0.12, 1)
0.1
>>> round(0.012, 1)
0.01
>>> round(0.062, 1)
0.06
>>> round(6253, 1)
6000
>>> round(1999, 1)
2000

0
import math

  def sig_dig(x, n_sig_dig):
      num_of_digits = len(str(x).replace(".", ""))
      if n_sig_dig >= num_of_digits:
          return x
      n = math.floor(math.log10(x) + 1 - n_sig_dig)
      result = round(10 ** -n * x) * 10 ** n
      return float(str(result)[: n_sig_dig + 1])


    >>> sig_dig(1234243, 3)
    >>> sig_dig(243.3576, 5)

        1230.0
        243.36
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.