Усечение плавающих в Python

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

1.923328437452 -> 1.923

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

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

Ответ 1

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

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), и я еще не уверен, как лучше всего обрабатывать те.

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

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

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

Ответ 2

round(1.923328437452, 3)

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

Ответ 3

Результатом 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'

Ответ 4

n = 1.923328437452
str(n)[:4]

Ответ 5

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

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

Ответ 6

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

n = 1.923328437452
n = float(int(n * 1000))
n /=1000

Ответ 7

def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])

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

Ответ 8

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

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.

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

Ответ 9

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

Это лучший результат Google, когда я ищу "Python truncate float", концепцию, которая действительно проста, и которая заслуживает лучших ответов. Я согласен с Hatchkins в том, что использование модуля 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()

Ответ 10

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

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

Ответ 11

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

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]

Ответ 12

Если вам интересна математика, это работает для чисел +ve:

>>> v = 1.923328437452
>>> v - v % 1e-3
1.923

Ответ 13

def trunc(f,n):
  return ('%.16f' % f)[:(n-16)]

Ответ 14

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

round(f) = floor(f+0.5)

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

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

Ответ 15

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

import math;

def trunc(invalue, digits):
    return int(invalue*math.pow(10,digits))/math.pow(10,digits);

Ответ 16

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

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

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

Ответ 17

использовать numpy.round

import numpy as np
precision = 3
floats = [1.123123123, 2.321321321321]
new_float = np.round(floats, precision)

Ответ 18

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

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

Ответ 19

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

Ответ 20

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

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

Ответ 21

>>> floor((1.23658945) * 10**4) / 10**4
1.2365

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

Ответ 22

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

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

Выход:

f = 1.1234

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

Ответ 23

Что-то достаточно простое, чтобы вписаться в понимание списка, без библиотек или других внешних зависимостей. Для 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']

Ответ 24

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

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

Ответ 25

Короткий и простой вариант

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

Ответ 26

f = 1.923328437452
f = f - f % 0.001

Ответ 27

При использовании панд 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)

Ответ 28

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

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

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

Ответ 29

# value  value to be truncated
# n  number of values after decimal

value = 0.999782
n = 3
float(int(value*1en))*1e-n

Ответ 30

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

print '%.3f' % number