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
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
Функция round() в Python может иногда округлять числа неправильно из-за специфики работы с плавающей точкой, которая может приводить к ошибкам округления.
Для вычисления дробных чисел компьютер использует двоичную систему, что приводит к некоторым неточностям в представлении некоторых десятичных дробей.
Например, при попытке округлить число 2.675 до двух десятичных знаков с помощью функции round(), мы получим:
round(2.675, 2) # выводит 2.67
Однако, ожидалось, что результат будет 2.68. Эта ошибка вызвана тем, что число 2.675 не может быть точно представлено в двоичном представлении, и компьютер сохраняет приближенное значение этого числа.
Чтобы избежать подобных ошибок округления, можно использовать другой подход, например, функцию Decimal из модуля decimal:
from decimal import Decimal
num = Decimal('2.675')
round(num, 2) # выводит 2.68
Таким образом, использование специализированных инструментов может помочь решить проблему неправильного округления в Python.
Python round Function — Perfecting Number Manipulation
Как работает округление чисел в Python
👌 Как избавиться от громоздкого if else в Python. Простой трюк
0011 Функция Round
Потерял работу из-за Python! — Модуль decimal — Ошибки округления
Точность и ошибки округления в Python — Функция round и тип данных float (дробные числа)
Функции trunc, floor, ceil. Округление вверх и вниз в python
🤦♀️Как можно вляпаться в проблемы с неправильным применением float типа и как избежать этой проб…
Округление в Python — round int floor ceil — Как определить полный квадрат?
Pocket Option ЛУЧШАЯ СТРАТЕГИЯ! ИТОГИ КОНКУРСА НА 500$! 5 СЕКУНД НЕ ПРОБЛЕМА!
BLGPG-ADEFB97EB3D5-23-09-22-23
Новые материалы:
- Np vstack python описание
- Автоматизация рутинных задач с помощью python
- Python песочница online
- Django модель user
- Двойная индексация python
- Python список перевернуть
- Python подключение к базе данных access
- Какие парадигмы и стили программирования поддерживает python
- Эмпирическая функция распределения python
- Python алгоритмическая торговля
- Python расстояние между точками
- Программист python переподготовка
- C или python востребованность
- Никита соболев python
При выполнении различных арифметических операций важно, чтобы результат округлялся правильно. Часто требуется округлять в большую, меньшую сторону, до ближайшего целого или округлить до сотых.
Для этого программист может использовать различные инструменты, такие как встроенная функция 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% точностью. Его основной принцип: компьютер должен считать так, как считает человек. Речь идёт не о скорости вычисления, а о точности и отсутствии проблем неправильного представления чисел.