Обрізання плаває в Python


110

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

1.923328437452 -> 1.923

Мені потрібно виводити як рядок до іншої функції, а не друкувати.

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


4
Чи слід скоротити -1,233 до -1,23 або -1,24?
Антоні Хеткінс,

Відповіді:


116

По-перше, функція для тих, хто просто хоче скопіювати і вставити код:

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

Якщо це ваша ситуація, якщо ви дійсно хочете використовувати «добрі» десяткові подання для усічення, все , що можна зробити (наскільки я знаю) , це вибрати кілька цифр, менше повна точність представима 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
Це багато обґрунтування неправильного рішення! Один із тих дивних раритетів Стакковерф. Цікаво, чи є для нього значок ...
tumultous_rooster

5
Це просто жахливо, скільки є неправильних відповідей (і підказок за неправильні відповіді) на це питання.
nullstellensatz

6
На цю сторінку
завітає

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

Це спосіб більш елегантний, ніж дурниці в ручному форматуванні рядків, добрий пост!
rsethc

round(1.23456, 3)є 1.235і ні1.2350000000000001
Ахмад

1
@Ahmad не обов'язково. Приклад тут із Python 2.6 (відзначте дату відповіді). Форматування рядків було покращено в Python 2.7 / 3.1, тому, ймовірно, ви отримуєте різні результати. Тим не менше, числа з плаваючою комою часто матимуть несподівані представлення рядків, дивіться: docs.python.org/3.6/tutorial/floatingpoint.html
Фердинанд Беєр

21
n = 1.923328437452
str(n)[:4]

3
Простий і пітонічний. 4 - розмір цілого числа, хоча не тільки цифр після крапки.
GaTTaCa

4
Отже, якщо користувач вводить, наприклад 2, у вас буде десяткова крапка .в кінці рядка - я думаю, не дуже вдале рішення.
Зельфір Кальтшталь

Це характерно для випадку з цим номером. Як це узагальнити до 11.923328437452?
поляризуйте

Найкраща відповідь! Ви також можете додати float (), щоб повернути число: float (str (n) [: 4])
justSaid


11

Простий скрипт python -

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

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), краще використовувати центи як "внутрішнє представлення" цін: ( 70*3 == 210) і множити / ділити входи / виходи.


Пробачте мене за запитання, але ... чому?
markroxor

@markroxor, не впевнений, про що саме ви питаєте. Як сторонне позначення, як правило, проблема полягає не в самому округленні, а в неправильному використанні чисел з поплавком перед округленням. Напр int(0.7*3*100)/100 == 2.09. Куди пішов мій 1 цент?
Ентоні Хеткінс

це має сенс, ви можете редагувати свою відповідь за допомогою цього пояснення? Дякую.
markroxor

Отримуючи ImportError: cannot import name 'D', я вважаю, ви хотіли зробити імпортний імпорт ні?
Overdrivr

8

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

Це найвищий результат Google, коли я шукаю "Python усікаючий поплавок", концепція, яка справді проста, і яка заслуговує на кращі відповіді. Я погоджуюся з Хеткінсом, що використання 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

Я зробив щось подібне:

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]

Це лише усікання з додатними числами, від’ємні числа будуть округлені вниз (далеко від нуля).
Аарон Д

3

Якщо вам здається, що це математика, це працює для + ve чисел:

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

Як я розумію, 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 ()" of

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

1

int (16,5); це дасть ціле значення 16, тобто магістраль не зможе вказати десяткові знаки, але здогадайтесь, що ви можете зробити це за допомогою

import math;

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

1

Ось простий спосіб:

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

для num = 1.923328437452, це виходить 1.923



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 скорочує як позитивні, так і від’ємні числа.
Аарон Д

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


Приємно, але ви не округляєте.
Алекс

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

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

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

fnum = str(122.485221)  # convert float to string first
truncated_float = fnum[:fnum.find('.') + digits + 1]  # string output

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

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

# ділимо і множимо на 10 ** кількість бажаних цифр


0

використовувати numpy.round

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

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

0

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

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

який повинен опікуватися усіма кутовими справами, знайденими тут і тут .


-1

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

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

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



-3

Якщо ви маєте на увазі під час друку, то слід працювати так:

print '%.3f' % number

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