Усечение поплавков в Python

110

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

1.923328437452 -> 1.923

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

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

Джоан Венге
источник
4
Следует ли -1,233 усечь до -1,23 или -1,24?
Энтони Хэтчкинс

Ответы:

117

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

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 .

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

'{}'.format(f)

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

Если вы на самом деле являетесь работой с floatS, которые очень близка к округлить , но намеренно не приравненную к ним (как 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

Если это ваша ситуация, если вы действительно хотите использовать "хорошие" десятичные представления для усечения, все, что вы можете сделать (насколько мне известно), - это выбрать некоторое количество цифр, меньшее, чем полная точность, представленная a 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

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

Дэвид З
источник
Как мы можем применить эту функцию к фрейму данных?
codelord
@RohithRNair Не в порядке, точно так же, как вы применили бы любую другую функцию, которая работает с отдельными элементами (т.е. applymap()). Может быть, есть способ сделать всю операцию более эффективной, но это уже отдельный вопрос.
David Z
applymap () занимает много времени, так как мои фреймы данных очень большие. Я пытаюсь сравнить два фрейма данных на предмет различий, но точность с плавающей запятой искажает мой вывод от желаемого. Как вы сказали, я подниму отдельный вопрос к тому же. Спасибо.
codelord
@RohithRNair А, если вы пытаетесь сравнить два фрейма данных на предмет различий, спросите об этом. Усечение значений (о чем идет речь в этом вопросе) - не лучший способ сделать это.
David Z
Просто примечание, ваш код, кажется, преобразует отрицательные числа в отрицательный ноль, что может сбивать с толку ...
user541686
152
round(1.923328437452, 3)

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

Тейфьон
источник
49
Я имел в виду, что округление мне не нужно. Мне нужно усечение, которое отличается.
Джоан Венге,
1
Ааа, достаточно честно. Извини мою ошибку.
Teifion
22
Это много голосов за неправильное решение! Одна из тех странных редкостей Stackoverflow. Интересно, есть ли за это значок ...
tumultous_rooster
5
Просто ужасно, сколько существует неправильных ответов (и голосов за неправильные ответы) на этот вопрос.
nullstellensatz
6
Многие люди будут заходить на эту страницу в поисках округления;)
janjackson
33

Результатом 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'
Фердинанд Бейер
источник
8
На моем Python это округляется: '% .3f'% 1.23456 == '1.235'
Дэвид З.
Это намного элегантнее, чем бессмыслица с ручным форматированием строк, хороший пост!
rsethc
round(1.23456, 3)есть 1.235и нет1.2350000000000001
Ахмад
1
@ Ахмад не обязательно. Пример здесь из Python 2.6 (обратите внимание на дату ответа). Форматирование строк было улучшено в Python 2.7 / 3.1, вероятно, поэтому вы получаете другие результаты. Тем не менее, числа с плавающей запятой часто имеют неожиданные строковые представления, см .: docs.python.org/3.6/tutorial/floatingpoint.html
Фердинанд Бейер,
21
n = 1.923328437452
str(n)[:4]
john_dough
источник
3
Простой и питонический. 4 - это размер всего числа, а не только цифр после точки.
GaTTaCa
4
Так что, если пользователь вводит, например 2, у вас будет десятичная точка .в конце строки - я думаю, не совсем хорошее решение.
Zelphir Kaltstahl 02
Это относится к случаю этого номера. Как бы это обобщить на 11.923328437452?
polarize
Лучший ответ! вы также можете добавить float () для возврата числа: float (str (n) [: 4])
justSaid
14

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

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

Билл
источник
11

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

n = 1.923328437452
n = float(int(n * 1000))
n /=1000
markroxor
источник
3
Чистый ответ. Вы просто пропускаете один шаг, чтобы преобразовать обратно в число с плавающей запятой перед делением на 1000. В противном случае вы получите 1.
Йохан Обадиа
9
def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])

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

Мэтт
источник
9

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

from decimal import *

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

или короче:

from decimal import Decimal as D, ROUND_DOWN

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

Обновить

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

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

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

Энтони Хэтчкинс
источник
Парсон, я спрашиваю об этом, но ... почему?
markroxor
@markroxor, не знаю, о чем конкретно вы спрашиваете. Кстати, обычно проблема не в самом округлении, а в неправильном использовании чисел с плавающей запятой перед округлением. Напр int(0.7*3*100)/100 == 2.09. Куда ушел мой 1 цент?
Энтони Хэтчкинс
это имеет смысл, можете ли вы отредактировать свой ответ этим объяснением? Спасибо.
markroxor
Как ImportError: cannot import name 'D'я считаю , что вы хотели сделать с именем импорта нет?
Overdrivr
8

Многие ответы на этот вопрос совершенно неверны. Они либо округляют числа с плавающей запятой (а не усекают), либо работают не во всех случаях.

Это лучший результат 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()
nullstellensatz
источник
4

Я сделал что-то вроде этого:

from math import trunc


def truncate(number, decimals=0):
    if decimals < 0:
        raise ValueError('truncate received an invalid value of decimals ({})'.format(decimals))
    elif decimals == 0:
        return trunc(number)
    else:
        factor = float(10**decimals)
        return trunc(number*factor)/factor
Альваро
источник
4

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

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]

источник
Это усекает только положительные числа, отрицательные числа округляются в меньшую сторону (от нуля).
Aaron D
3

Если вам нравится математика, это работает для положительных чисел:

>>> v = 1.923328437452
>>> v - v % 1e-3
1.923
cs95
источник
Насколько я понимаю, 1e-3 обрежется до 3 цифр после точки. Мне понравился этот ответ, но, похоже, он не работает для 4 и 5.
egvo
2

При использовании pandas df это сработало для меня

import math
def truncate(number, digits) -> float:
    stepper = 10.0 ** digits
    return math.trunc(stepper * number) / stepper

df['trunc'] = df['float_val'].apply(lambda x: truncate(x,1))
df['trunc']=df['trunc'].map('{:.1f}'.format)
Барт Кубрич
источник
1

Просто хотел упомянуть, что старый трюк "make round () with floor ()"

round(f) = floor(f+0.5)

можно повернуть, чтобы сделать пол () из круглого ()

floor(f) = round(f-0.5)

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

def trunc(f, n):
    if f > 0:
        return "%.*f" % (n, (f - 0.5*10**-n))
    elif f == 0:
        return "%.*f" % (n, f)
    elif f < 0:
        return "%.*f" % (n, (f + 0.5*10**-n))
itsadok
источник
1

int (16,5); это даст целочисленное значение 16, т.е. trunc, не сможет указывать десятичные дроби, но думаю, вы можете сделать это с помощью

import math;

def trunc(invalue, digits):
    return int(invalue*math.pow(10,digits))/math.pow(10,digits);
Pieter
источник
1

Вот простой способ:

def truncate(num, res=3):
    return (floor(num*pow(10, res)+0.5))/pow(10, res)

для num = 1.923328437452 это выводит 1.923

Саранг
источник
1
def trunc(f,n):
  return ('%.16f' % f)[:(n-16)]
Росс Картлидж
источник
1

Общая и простая функция:

def truncate_float(number, length):
    """Truncate float numbers, up to the number specified
    in length that must be an integer"""

    number = number * pow(10, length)
    number = int(number)
    number = float(number)
    number /= pow(10, length)
    return number
Йохан Обадиа
источник
Большой! Приведение к int усекает как положительные, так и отрицательные числа.
Aaron D
1

В python 3 есть простой обходной путь. Где вырезать, я определил с помощью переменной decPlace, чтобы облегчить адаптацию.

f = 1.12345
decPlace= 4
f_cut = int(f * 10**decPlace) /10**decPlace

Вывод:

f = 1.1234

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

МБрег
источник
1
def precision(value, precision):
    """
    param: value: takes a float
    param: precision: int, number of decimal places
    returns a float
    """
    x = 10.0**precision
    num = int(value * x)/ x
    return num
precision(1.923328437452, 3)

1,923

Эндрю Олсон
источник
Хорошо, но вы не округляетесь.
Alex
1

Краткий и легкий вариант

def truncate_float(value, digits_after_point=2):
    pow_10 = 10 ** digits_after_point
    return (float(int(value * pow_10))) / pow_10

>>> truncate_float(1.14333, 2)
>>> 1.14

>>> truncate_float(1.14777, 2)
>>> 1.14


>>> truncate_float(1.14777, 4)
>>> 1.1477
мегаджо
источник
1

На мой взгляд, большинство ответов слишком сложны, как насчет этого?

digits = 2  # Specify how many digits you want

fnum = '122.485221'
truncated_float = float(fnum[:fnum.find('.') + digits + 1])

>>> 122.48

Просто ищите индекс '.' и обрезать по желанию (без округления). Конвертируйте строку в float в качестве последнего шага.

Или в вашем случае, если вы получаете число с плавающей запятой в качестве ввода и хотите получить строку в качестве вывода:

fnum = str(122.485221)  # convert float to string first
truncated_float = fnum[:fnum.find('.') + digits + 1]  # string output
Н123321
источник
Ваше предложение проблематично, если число, которое усекается, невелико, поскольку вы потратите много точности с ведущим 0 справа от десятичной точки. Но эта проблема присуща проблеме, как было сказано. Я пытаюсь сказать, что значимые цифры - это настоящий ответ.
overcoil
1
>>> floor((1.23658945) * 10**4) / 10**4
1.2365

# разделить и умножить на 10 ** количество желаемых цифр

JohnA
источник
0

используйте numpy.round

import numpy as np
precision = 3
floats = [1.123123123, 2.321321321321]
new_float = np.round(floats, precision)
rafaelvalle
источник
0

Что-то достаточно простое, чтобы поместиться в понимание списка, без библиотек или других внешних зависимостей. Для Python> = 3.6 очень просто писать f-строками.

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

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1] for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.800', '0.888', '1.125', '1.250', '1.500']

Конечно, здесь происходит округление (а именно для четвертой цифры), но округления в какой-то момент нельзя избежать. В случае, если важен переход между усечением и округлением, вот немного лучший пример:

>>> nacc = 6  # desired accuracy (maximum 15!)
>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nacc}f}'[:-(nacc-nout)] for x in [2.9999, 2.99999, 2.999999, 2.9999999]]
>>> ['2.999', '2.999', '2.999', '3.000']

Бонус: удаление нулей справа

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1].rstrip('0') for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.8', '0.888', '1.125', '1.25', '1.5']
Axel
источник
0

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

def trunc(num, digits):
    l = str(float(num)).split('.')
    digits = min(len(l[1]), digits)
    return (l[0]+'.'+l[1][:digits])

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

aak318
источник
-1

Я также новичок в питоне, и после использования некоторых кусочков здесь я предлагаю свои два цента

print str(int(time.time()))+str(datetime.now().microsecond)[:3]

str (int (time.time ())) возьмет временную эпоху как int и преобразует ее в строку и соединится с ... str (datetime.now (). microsecond) [: 3], который возвращает только микросекунды, преобразовать для строки и усечения до первых 3 символов

user1048839
источник
-1
# value  value to be truncated
# n  number of values after decimal

value = 0.999782
n = 3
float(int(value*1en))*1e-n
Правин Рамануджам
источник
-3

Если вы имеете в виду при печати, то должно работать следующее:

print '%.3f' % number
user44511
источник
2
Это округляет число, но не усекает.
Дэвид З