Усечение поплавков в Python - PullRequest
90 голосов
/ 24 апреля 2009

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

1.923328437452 -> 1.923

Мне нужно выводить в виде строки в другую функцию, а не печатать.

Также я хочу игнорировать потерянные цифры, а не округлять их.

Ответы [ 28 ]

139 голосов
/ 24 апреля 2009
round(1.923328437452, 3)

См. Документация Python по стандартным типам . Вам нужно немного прокрутить вниз, чтобы перейти к функции округления. По сути, второе число говорит, сколько десятичных знаков округлить до.

102 голосов
/ 24 апреля 2009

Во-первых, эта функция предназначена для тех, кто просто хочет скопировать и вставить код:

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '{}'.format(f)
    if 'e' in s or 'E' in s:
        return '{0:.{1}f}'.format(f, n)
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

Это действительно в Python 2.7 и 3.1+. Для более старых версий невозможно получить тот же эффект «интеллектуального округления» (по крайней мере, не без большого количества сложного кода), но округление до 12 десятичных знаков перед усечением будет работать большую часть времени:

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '%.12f' % f
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

Объяснение

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

i, p, d = s.partition('.')
'.'.join([i, (d+'0'*n)[:n]])

или decimal модуль

str(Decimal(s).quantize(Decimal((0, (1,), -n)), rounding=ROUND_DOWN))

Первый шаг, преобразование в строку, довольно сложный, поскольку есть несколько пар литералов с плавающей запятой (т. Е. То, что вы пишете в исходном коде), которые оба производят одно и то же двоичное представление и все же должны быть по-разному усечены. Например, рассмотрим 0.3 и 0.29999999999999998. Если вы пишете 0.3 в программе на Python, компилятор кодирует ее, используя формат с плавающей запятой IEEE, в последовательность битов (при условии 64-разрядного числа с плавающей запятой)

0011111111010011001100110011001100110011001100110011001100110011

Это самое близкое к 0,3 значение, которое можно точно представить как число с плавающей точкой IEEE. Но если вы напишите 0.29999999999999998 в программе на Python, компилятор переведет ее в точно такое же значение . В одном случае вы имели в виду, что оно должно быть усечено (до одной цифры) как 0.3, тогда как в другом случае вы имели в виду, что оно должно быть усечено до 0.2, но Python может дать только один ответ. Это фундаментальное ограничение Python или любого другого языка программирования без ленивых вычислений. Функция усечения имеет доступ только к двоичному значению, хранящемуся в памяти компьютера, а не к строке, которую вы фактически ввели в исходный код. 1

Если вы декодируете последовательность битов обратно в десятичное число, снова используя 64-битный формат IEEE с плавающей запятой, вы получите

0.2999999999999999888977697537484345957637...

так что наивная реализация придумала бы 0.2, хотя это, вероятно, не то, что вы хотите. Подробнее об ошибке представления с плавающей точкой см. В учебнике по Python .

Очень редко работать со значением с плавающей запятой, которое настолько близко к круглому числу и все же намеренно 1038 * не равно этому круглому числу. Таким образом, при усечении, вероятно, имеет смысл выбрать «наилучшее» десятичное представление из всего, что может соответствовать значению в памяти. Python 2.7 и выше (но не 3.0) включает в себя сложный алгоритм , чтобы сделать именно это , к которому мы можем получить доступ через операцию форматирования строки по умолчанию.

'{}'.format(f)

Единственное предостережение в том, что это действует как спецификация формата g, в том смысле, что оно использует экспоненциальную запись (1.23e+4), если число большое или достаточно маленькое. Таким образом, метод должен поймать этот случай и обработать его по-другому. Есть несколько случаев, когда использование спецификации формата f вместо этого вызывает проблему, такую ​​как попытка усечь 3e-10 до 28 цифр точности (это дает 0.0000000002999999999999999980), и я пока не уверен, как лучше всего справиться те.

Если вы на самом деле работаете с float s, которые очень близки к круглым числам, но намеренно не равны им (например, 0,29999999999999998 или 99,959999999999994), это приведет к некоторым ложным срабатываниям, т.е. круглые числа, которые вы не хотели округлять. В этом случае решение состоит в том, чтобы указать фиксированную точность.

'{0:.{1}f}'.format(f, sys.float_info.dig + n + 2)

Количество используемых здесь цифр точности не имеет значения, оно должно быть достаточно большим, чтобы любое округление, выполняемое при преобразовании строки, не «увеличивало» значение до его красивого десятичного представления. Я думаю, что sys.float_info.dig + n + 2 может быть достаточно во всех случаях, но если нет, то 2, возможно, придется увеличить, и это не помешает.

В более ранних версиях Python (до 2.6 или 3.0) форматирование чисел с плавающей запятой было намного более грубым и регулярно создавало такие вещи, как

>>> 1.1
1.1000000000000001

Если это ваша ситуация, если вы do хотите использовать "хорошие" десятичные представления для усечения, все, что вы можете сделать (насколько я знаю), это выбрать некоторое количество цифр, меньше, чем полная точность, представляемая float, и округлить число до такого количества цифр перед его усечением. Типичный выбор 12,

'%.12f' % f

, но вы можете настроить его в соответствии с номерами, которые вы используете.


1 Ну ... я солгал. Технически, вы можете поручить Python повторно проанализировать свой собственный исходный код и извлечь часть, соответствующую первому аргументу, который вы передаете функции усечения. Если этот аргумент является литералом с плавающей точкой, вы можете просто обрезать его на определенное количество знаков после десятичной точки и вернуть его. Однако эта стратегия не работает, если аргумент является переменной, что делает его довольно бесполезным. Следующее представлено только для развлекательной ценности:

def trunc_introspect(f, n):
    '''Truncates/pads the float f to n decimal places by looking at the caller's source code'''
    current_frame = None
    caller_frame = None
    s = inspect.stack()
    try:
        current_frame = s[0]
        caller_frame = s[1]
        gen = tokenize.tokenize(io.BytesIO(caller_frame[4][caller_frame[5]].encode('utf-8')).readline)
        for token_type, token_string, _, _, _ in gen:
            if token_type == tokenize.NAME and token_string == current_frame[3]:
                next(gen) # left parenthesis
                token_type, token_string, _, _, _ = next(gen) # float literal
                if token_type == tokenize.NUMBER:
                    try:
                        cut_point = token_string.index('.') + n + 1
                    except ValueError: # no decimal in string
                        return token_string + '.' + '0' * n
                    else:
                        if len(token_string) < cut_point:
                            token_string += '0' * (cut_point - len(token_string))
                        return token_string[:cut_point]
                else:
                    raise ValueError('Unable to find floating-point literal (this probably means you called {} with a variable)'.format(current_frame[3]))
                break
    finally:
        del s, current_frame, caller_frame

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

30 голосов
/ 24 апреля 2009

Результат round - это число с плавающей точкой, так что будьте внимательны (пример из Python 2.6):

>>> round(1.923328437452, 3)
1.923
>>> round(1.23456, 3)
1.2350000000000001

Вам будет лучше при использовании отформатированной строки:

>>> "%.3f" % 1.923328437452
'1.923'
>>> "%.3f" % 1.23456
'1.235'
18 голосов
/ 14 ноября 2010
n = 1.923328437452
str(n)[:4]
11 голосов
/ 02 февраля 2017

В моем приглашении Python 2.7:

>>> int(1.923328437452 * 1000)/1000.0 1.923

8 голосов
/ 10 января 2016

Простой скрипт на Python -

n = 1.923328437452
n = float(int(n * 1000))
n /=1000
8 голосов
/ 24 апреля 2009
def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])

Это должно работать. Это должно дать вам усечение, которое вы ищете.

8 голосов
/ 28 мая 2014

Истинно питонический способ сделать это

from decimal import *

with localcontext() as ctx:
    ctx.rounding = ROUND_DOWN
    print Decimal('1.923328437452').quantize(Decimal('0.001'))

или короче:

from decimal import D, ROUND_DOWN

D('1.923328437452').quantize(D('0.001'), rounding=ROUND_DOWN)

Обновление

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

Например: int(0.7*3*100)/100 == 2.09.

Если вы вынуждены использовать поплавки (скажем, вы ускоряете свой код с помощью numba), лучше использовать центы как "внутреннее представление" цен: (70*3 == 210) и умножить / разделить входы / выходы.

7 голосов
/ 04 февраля 2015

Так что многие ответы на этот вопрос просто неверны. Они либо округляют поплавки (а не усекают), либо не работают для всех случаев.

Это лучший результат Google, когда я ищу «Python truncate float», концепция, которая действительно проста и заслуживает лучших ответов. Я согласен с Хэтчкинсом, что использование модуля decimal является питонским способом сделать это, поэтому я даю здесь функцию, которая, я думаю, отвечает на вопрос правильно и которая работает, как и ожидалось, для всех случаев.

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

from decimal import Decimal, localcontext, ROUND_DOWN

def truncate(number, places):
    if not isinstance(places, int):
        raise ValueError("Decimal places must be an integer.")
    if places < 1:
        raise ValueError("Decimal places must be at least 1.")
    # If you want to truncate to 0 decimal places, just do int(number).

    with localcontext() as context:
        context.rounding = ROUND_DOWN
        exponent = Decimal(str(10 ** - places))
        return Decimal(str(number)).quantize(exponent).to_eng_string()
4 голосов
/ 25 марта 2015

Вы можете сделать:

def truncate(f, n):
    return math.floor(f * 10 ** n) / 10 ** n

Тестирование:

>>> f=1.923328437452
>>> [truncate(f, n) for n in range(5)]
[1.0, 1.9, 1.92, 1.923, 1.9233]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...