Как округлить число до значащих цифр в Python - PullRequest
118 голосов
/ 05 августа 2010

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

1234 -> 1000

0,12 -> 0,1

0,012 -> 0,01

0,062 -> 0,06

6253 -> 6000

1999 -> 2000

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

Ответы [ 16 ]

129 голосов
/ 05 августа 2010

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

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

90 голосов
/ 05 августа 2010

% 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'
46 голосов
/ 05 августа 2010

Если вы хотите иметь значение, отличное от 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
12 голосов
/ 15 февраля 2018
print('{:g}'.format(float('{:.1g}'.format(12.345))))

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

  1. оно точно решает вопрос OP
  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).Если вам нужно сделать это, то нужны нестандартные функции, такие как те, которые предусмотрены в пакете to-precision .

5 голосов
/ 23 мая 2017

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

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

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

>>> 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'
5 голосов
/ 04 марта 2012

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

Для этого нам нужно знать наибольшую степень на 10 меньше целого числа. Для этого мы можем использовать функцию log 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)
4 голосов
/ 26 марта 2016

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

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)
3 голосов
/ 13 апреля 2018

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

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

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

3 голосов
/ 14 июля 2016
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
2 голосов
/ 10 июля 2019

Чтобы прямо ответить на вопрос, вот моя версия с использованием имен из функции 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. Это связано с тем, что "Новичок С" указывает на комбинацию арифметики с плавающей запятой, имеющей как конечную точность, так и представление в виде base-2 . Ближайшее к 0,075 число, которое действительно может быть представлено, немного меньше, поэтому округление получается не так, как вы могли бы наивно ожидать.

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

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

0.075.hex()

что дает нам: 0x1.3333333333333p-4. Причина этого заключается в том, что нормальное десятичное представление часто включает в себя округление и, следовательно, не то, как компьютер фактически «видит» число. Если вы не привыкли к этому формату, пара полезных ссылок - это Python docs и 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

...