Python ошибки округления

The problem is only when last digit is 5. Eg. 0.045 is internally stored as 0.044999999999999… You could simply increment last digit to 6 and round off. This will give you the desired results.

import re


def custom_round(num, precision=0):
    # Get the type of given number
    type_num = type(num)
    # If the given type is not a valid number type, raise TypeError
    if type_num not in [int, float, Decimal]:
        raise TypeError("type {} doesn't define __round__ method".format(type_num.__name__))
    # If passed number is int, there is no rounding off.
    if type_num == int:
        return num
    # Convert number to string.
    str_num = str(num).lower()
    # We will remove negative context from the number and add it back in the end
    negative_number = False
    if num < 0:
        negative_number = True
        str_num = str_num[1:]
    # If number is in format 1e-12 or 2e+13, we have to convert it to
    # to a string in standard decimal notation.
    if 'e-' in str_num:
        # For 1.23e-7, e_power = 7
        e_power = int(re.findall('e-[0-9]+', str_num)[0][2:])
        # For 1.23e-7, number = 123
        number = ''.join(str_num.split('e-')[0].split('.'))
        zeros = ''
        # Number of zeros = e_power - 1 = 6
        for i in range(e_power - 1):
            zeros = zeros + '0'
        # Scientific notation 1.23e-7 in regular decimal = 0.000000123
        str_num = '0.' + zeros + number
    if 'e+' in str_num:
        # For 1.23e+7, e_power = 7
        e_power = int(re.findall('e+[0-9]+', str_num)[0][2:])
        # For 1.23e+7, number_characteristic = 1
        # characteristic is number left of decimal point.
        number_characteristic = str_num.split('e+')[0].split('.')[0]
        # For 1.23e+7, number_mantissa = 23
        # mantissa is number right of decimal point.
        number_mantissa = str_num.split('e+')[0].split('.')[1]
        # For 1.23e+7, number = 123
        number = number_characteristic + number_mantissa
        zeros = ''
        # Eg: for this condition = 1.23e+7
        if e_power >= len(number_mantissa):
            # Number of zeros = e_power - mantissa length = 5
            for i in range(e_power - len(number_mantissa)):
                zeros = zeros + '0'
            # Scientific notation 1.23e+7 in regular decimal = 12300000.0
            str_num = number + zeros + '.0'
        # Eg: for this condition = 1.23e+1
        if e_power < len(number_mantissa):
            # In this case, we only need to shift the decimal e_power digits to the right
            # So we just copy the digits from mantissa to characteristic and then remove
            # them from mantissa.
            for i in range(e_power):
                number_characteristic = number_characteristic + number_mantissa[i]
            number_mantissa = number_mantissa[i:]
            # Scientific notation 1.23e+1 in regular decimal = 12.3
            str_num = number_characteristic + '.' + number_mantissa
    # characteristic is number left of decimal point.
    characteristic_part = str_num.split('.')[0]
    # mantissa is number right of decimal point.
    mantissa_part = str_num.split('.')[1]
    # If number is supposed to be rounded to whole number,
    # check first decimal digit. If more than 5, return
    # characteristic + 1 else return characteristic
    if precision == 0:
        if mantissa_part and int(mantissa_part[0]) >= 5:
            return type_num(int(characteristic_part) + 1)
        return type_num(characteristic_part)
    # Get the precision of the given number.
    num_precision = len(mantissa_part)
    # Rounding off is done only if number precision is
    # greater than requested precision
    if num_precision <= precision:
        return num
    # Replace the last '5' with 6 so that rounding off returns desired results
    if str_num[-1] == '5':
        str_num = re.sub('5$', '6', str_num)
    result = round(type_num(str_num), precision)
    # If the number was negative, add negative context back
    if negative_number:
        result = result * -1
    return result

As answered well in previous posts, this is a floating point arithmetic issue common in programming languages. You should be aware never to apply exact equality to float types.

When you have such comparisons, you can employ a function that compares based on a given tolerance (threshold). If the numbers are close enough, they should be considered equal number-wise. Something like:

def isequal_float(x1,x2, tol=10**(-8)):
    """Returns the results of floating point equality, according to a tolerance."""
    return abs(x1 - x2)<tol

will do the trick. If I’m not mistaken, the exact tolerance depends on whether the float type is single- or double-precision and this depends on the language you’re using.

Using such a function allows you to easily compare the results of calculations, for instance in numpy. Let’s take the following example for instance, where the correlation matrix is calculated for a dataset with continuous variables, using two ways: the pandas method pd.DataFrame.corr() and the numpy function np.corrcoef():

import numpy as np
import seaborn as sns 

iris = sns.load_dataset('iris')
iris.drop('species', axis = 1, inplace=True)

# calculate correlation coefficient matrices using two different methods
cor1 = iris.corr().to_numpy()
cor2 = np.corrcoef(iris.transpose())

print(cor1)
print(cor2)

The results seem similar:

[[ 1.         -0.11756978  0.87175378  0.81794113]
 [-0.11756978  1.         -0.4284401  -0.36612593]
 [ 0.87175378 -0.4284401   1.          0.96286543]
 [ 0.81794113 -0.36612593  0.96286543  1.        ]]
[[ 1.         -0.11756978  0.87175378  0.81794113]
 [-0.11756978  1.         -0.4284401  -0.36612593]
 [ 0.87175378 -0.4284401   1.          0.96286543]
 [ 0.81794113 -0.36612593  0.96286543  1.        ]]

but the results of their exact equality are not. These operators:

print(cor1 == cor2)
print(np.equal(cor1, cor2))

will yield mostly False results element-wise:

[[ True False False False]
 [False False False False]
 [False False False False]
 [False False False  True]]

Likewise, np.array_equal(cor1, cor2) will also yield False. However, the custom-made function gives the comparison you want:

out = [isequal_float(i,j) for i,j in zip(cor1.reshape(16, ), cor2.reshape(16, ))]
print(out)

[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]

Note: numpy includes the .allclose() function to perform floating point element-wise comparisons in numpy arrays.

print(np.allclose(cor1, cor2))
>>>True

Использование десятичного числа с плавающей запятой устраняет ошибку десятичного представления, позволяя точно представить 0,1. Однако некоторые операции могут все еще вызывать ошибку округления, когда ненулевые цифры превышают фиксированную точность decimal.getcontext().prec.

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

>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 8
>>> D =  Decimal
>>> u, v, w = D(11111113), D(-11111111), D('7.51111111')
>>> (u + v) + w
# Decimal('9.5111111')
>>> u + (v + w)
# Decimal('10')

>>> u, v, w = D(20000), D(-6), D('6.0000003')
>>> (u*v) + (u*w)
# Decimal('0.01')
>>> u * (v+w)
# Decimal('0.0060000')

Модуль decimal позволяет восстановить тождества, достаточно увеличив точность, чтобы избежать потери значимости:

>>> getcontext().prec = 20
>>> u, v, w = Decimal(11111113), Decimal(-11111111), Decimal('7.51111111')
>>> (u + v) + w
# Decimal('9.51111111')
>>> u + (v + w)
# Decimal('9.51111111')

>>> u, v, w = Decimal(20000), Decimal(-6), Decimal('6.0000003')
>>> (u*v) + (u*w)
# Decimal('0.0060000')
>>> u * (v+w)
# Decimal('0.0060000')

Содержание:развернуть

  • Встроенные функции
  • round

  • int

  • Функции из библиотеки Math
  • math.ceil

  • math.floor

  • math.trunc

  • Различие округления в Python 2 и Python 3

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

В Python для округления доступны функции round(), int() и модуль math. Последний дополнительно импортируется.

Встроенные функции

Для операции округления в Python есть встроенные функции — round() и int()

round

round(number[, ndigits]) — округляет число (number) до ndigits знаков после запятой. Это стандартная функция, которая для выполнения не требует подключения модуля math.

По умолчанию операция проводится до нуля знаков — до ближайшего целого числа. Например:

round(3.5)
> 4

round(3.75, 1)
> 3.8

Чтобы получить целый показатель, результат преобразовывают в int.

Синтаксически функция вызывается двумя способами.

  1. round(x) — это округление числа до целого, которое расположено ближе всего. Если дробная часть равна 0,5, то округляют до ближайшего четного значения.
  2. round(x, n) — данные х округляют до n знаков после точки. Если округление проходит до сотых, то n равен «2», если до тысячных — «3» и т.д.

int

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

int(5.9)
> 5

int(-5.77)
> -5

Для положительных чисел функция int аналогична функции math.floor(), а для отрицательных — аналогично math.ceil(). Например:

import math
math.floor(3.999)
> 3

math.ceil(3.999)
> 4

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

  1. Если число положительное, добавить к нему 0,5;
  2. Если число отрицательное, добавить -0,5.

Синтаксически преобразование оформляется так:

num = 5.77
int(num + (0.5 if num > 0 else -0.5))

> 6

Функции из библиотеки Math

Модуль необходим в Python. Он предоставляет пользователю широкий функционал работы с числами. Для обработки алгоритмов сначала проводят импорт модуля.

import math

math.ceil

Функция получила название от английского слова «ceiling» — «потолок«

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

Любая дробь находится между двумя целыми числами. Например, 2.3 лежит между 2 и 3. Функция ceil() определяет большую сторону и возводит к нему результат преобразования. Например:

import math
math.ceil(3.25)

> 4

Алгоритм определяет большую границу интервала с учетом знака:

import math
math.ceil(-3.25)

> -3

math.floor

Функция получила название от английского слова «floor» — «пол«

math.floor() действует противоположно math.ceil() — округляет дробное значение до ближайшего целого, которое меньше или равно исходному. Округление происходит в меньшую сторону (вниз):

import math
math.floor(3.9)
> 3

math.floor(-2.1)
> -3

При округлении учитывается знак перед данными.

math.trunc

Функция получила название от английского слова «truncate» — «урезать«

Функция характеризуется отбрасыванием дробной части. После преобразования получается целое значение без учета дроби. Такой алгоритм не является округлением в арифметическом смысле. В Пайтон просто игнорируется дробь независимо от ее значения:

import math
math.trunc(7.11)
> 7

math.trunc(-2.1)
-2

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

Различие округления в Python 2 и Python 3

В Python 2 и Python 3 реализованы разные принципы округления.

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

Во втором Python есть только 4 цифры, которые ведут к преобразованию к меньшему значению — 1, 2, 3 и 4. Также 5 цифр, которые приводят к большему значению — 5, 6, 7, 8, 9. Такое неравное распределение ведет к тому, что погрешность постоянно нарастает.

Python 2 по правилам арифметического округления преобразует число 5,685 в 5,68 до второго знака. Такая погрешность связана с тем, что десятичные цифры float в двоичном коде невозможно корректно представить.

В Python 3 используются принципы банковского округления. Это означает, что преобразование производится к ближайшему четному. В таком случае также не удается полностью избежать возникающих ошибок, но программисты добиваются точности в подсчетах.

2,5 по правилам банковского преобразования будет равно 2, а 3,5 = 4 (значения возводятся к близкому четному). Минимизировать погрешности можно благодаря практически равной вероятности, что перед пятеркой будет четное или нечетное число.

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

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

Способы округления чисел

Для округления чисел придумано много способов, они не лишены недостатков, однако часто используются для решения задач. Разберёмся в тонкостях каждого из них.

Если используется стандартная библиотека math, то в начале кода её необходимо подключить. Сделать это можно, например, с помощью инструкции: import math.

math.ceil() — округление чисел в большую сторону

Функция получила своё имя от термина «ceiling», который используется в математике для описания числа, которое больше или равно заданному.

Любая дробь находится в целочисленном интервале, например, 1.2 лежит между 1 и 2. Функция ceil() определяет, какая из границ интервала наибольшая и записывает её в результат округления.

Пример:

math.ceil(5.15) # = 6
math.ceil(6.666) # = 7
math.ceil(5) # = 5

Важно помнить, что функция определяет наибольшее число с учётом знака. То есть результатом округления числа -0.9 будет 0, а не -1.

math.floor() — округление чисел в меньшую сторону

Функция округляет дробное число до ближайшего целого, которое меньше или равно исходному. Работает аналогично функции ceil(), но с округлением в противоположную сторону.

Пример:

math.floor(7.9) # = 7
math.floor(9.999) # = 9
math.floor(-6.1) # = -7

math.trunc() — отбрасывание дробной части

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

Примеры:

math.trunc(5.51) # = 5
math.trunc(-6.99) # = -6

Избавиться от дробной части можно с помощью обычного преобразования числа к типу int. Такой способ полностью эквивалентен использованию trunc().

Примеры:

int(5.51) # = 5
int(-6.99) # = -6

Нормальное округление

Python позволяет реализовать нормальное арифметическое округление, использовав функцию преобразования к типу int.

И хотя int() работает по другому алгоритму, результат её использования для положительных чисел полностью аналогичен выводу функции floor(), которая округляет числа «вниз». Для отрицательных аналогичен функции ceil().

Примеры:

math.floor(9.999) # = 9
int(9.999) # = 9
math.ceil(-9.999) # = -9
int(-9.999) # = -9

Чтобы с помощью функции int() округлить число по математическим правилам, необходимо добавить к нему 0.5, если оно положительное, и -0.5, если оно отрицательное.

Тогда операция принимает такой вид: int(num + (0.5 if num > 0 else -0.5)). Чтобы каждый раз не писать условие, удобно сделать отдельную функцию:

def int_r(num):
    num = int(num + (0.5 if num > 0 else -0.5))
    return num

Функция работает также, как стандартная функция округление во второй версии Python (арифметическое округление).

Примеры:

int_r(11.5) # = 12
int_r(11.4) # = 11
int_r(-0.991) # = -1
int_r(1.391) # = 1

round() — округление чисел

round() — стандартная функция округления в языке Python. Она не всегда работает так, как ожидается, а её алгоритм различается в разных версиях Python.

В Python 2

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

Увеличение погрешности вызвано неравным количеством цифр, определяющих, в какую сторону округлять. Всего 4 цифры на конце приводят к округлению «вниз», и 5 цифр к округлению «вверх».

Помимо этого, могут быть неточности, например, если округлить число 2.675 до второго знака, получится число 2.67 вместо 2.68. Это происходит из-за невозможности точно представить десятичные числа типа «float» в двоичном коде.

В Python 3

В третьей версии Python используется банковское округление. Это значит, что округление происходит до самого близкого чётного.

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

Примеры:

round(3.5) # = 4
round(9.5) # = 10
round(6.5) # = 6
round(-6.5) # = -6
round(-7.5) # = -8

Но если вам по каким то причинам нужно округление как в Python 2, то можно воспользоваться функцией написанной нами выше на основе приведения к целому числу.

Округление до сотых

У функции raund() есть ещё один аргумент. Он показывает до какого количества знаков после запятой следует округлять. Таким образом, если нам надо в Python округлить до сотых, этому параметру следует задать значение 2.

Пример округления до нужного знака:

round(3.555, 2) # = 3.56
round(9.515,1) # = 9.5
round(6.657,2) # = 6.66

Ошибки округления и модуль decimal

При округлении функцией round(), можно получить следующее:

round(2.65, 1) # = 2.6
round(2.85, 1) # = 2.9

Почему в одном случае округляется вниз, а в другом вверх? При переводе 2.85 в двоичную систему получается число, которое немного больше. Поэтому функция видит не «5», а «>5» и округляет вверх.

Проблему неточного представления чисел отлично иллюстрирует пример:

print (0.1 + 0.1 + 0.1)

0.30000000000000004

Из-за подобных ошибок числа типа «float» нельзя использовать там, где изменения значения на одну тысячную может привести к неверному результату. Решить данную проблему поможет модуль decimal.

decimal — модуль, позволяющий округлять десятичные дроби с почти 100% точностью. Его основной принцип: компьютер должен считать так, как считает человек. Речь идёт не о скорости вычисления, а о точности и отсутствии проблем неправильного представления чисел.

Понравилась статья? Поделить с друзьями:
  • Python ошибки socket
  • Python ошибка синтаксиса
  • Python tkinter окно ошибки
  • Python split ошибка
  • Python return ошибка