Floating point error python это

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

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

Синтаксис обработки исключений

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

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

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

Ошибки могут быть разных видов:

  • Синтаксические
  • Недостаточно памяти
  • Ошибки рекурсии
  • Исключения

Разберем их по очереди.

Синтаксические ошибки (SyntaxError)

Синтаксические ошибки часто называют ошибками разбора. Они возникают, когда интерпретатор обнаруживает синтаксическую проблему в коде.

Рассмотрим на примере.

a = 8
b = 10
c = a b
File "", line 3
 c = a b
       ^
SyntaxError: invalid syntax

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

Недостаточно памяти (OutofMemoryError)

Ошибки памяти чаще всего связаны с оперативной памятью компьютера и относятся к структуре данных под названием “Куча” (heap). Если есть крупные объекты (или) ссылки на подобные, то с большой долей вероятности возникнет ошибка OutofMemory. Она может появиться по нескольким причинам:

  • Использование 32-битной архитектуры Python (максимальный объем выделенной памяти невысокий, между 2 и 4 ГБ);
  • Загрузка файла большого размера;
  • Запуск модели машинного обучения/глубокого обучения и много другое;

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

Но поскольку Python использует архитектуру управления памятью из языка C (функция malloc()), не факт, что все процессы восстановятся — в некоторых случаях MemoryError приведет к остановке. Следовательно, обрабатывать такие ошибки не рекомендуется, и это не считается хорошей практикой.

Ошибка рекурсии (RecursionError)

Эта ошибка связана со стеком и происходит при вызове функций. Как и предполагает название, ошибка рекурсии возникает, когда внутри друг друга исполняется много методов (один из которых — с бесконечной рекурсией), но это ограничено размером стека.

Все локальные переменные и методы размещаются в стеке. Для каждого вызова метода создается стековый кадр (фрейм), внутрь которого помещаются данные переменной или результат вызова метода. Когда исполнение метода завершается, его элемент удаляется.

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

def recursion():
    return recursion()

recursion()
---------------------------------------------------------------------------

RecursionError                            Traceback (most recent call last)

 in 
----> 1 recursion()


 in recursion()
      1 def recursion():
----> 2     return recursion()


... last 1 frames repeated, from the frame below ...


 in recursion()
      1 def recursion():
----> 2     return recursion()


RecursionError: maximum recursion depth exceeded

Ошибка отступа (IndentationError)

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

Пример:

for i in range(10):
    print('Привет Мир!')
  File "", line 2
    print('Привет Мир!')
        ^
IndentationError: expected an indented block

Исключения

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

Программы обычно не обрабатывают исключения, что приводит к подобным сообщениям об ошибке:

Ошибка типа (TypeError)

a = 2
b = 'PythonRu'
a + b
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

 in 
      1 a = 2
      2 b = 'PythonRu'
----> 3 a + b


TypeError: unsupported operand type(s) for +: 'int' and 'str'

Ошибка деления на ноль (ZeroDivisionError)

10 / 0
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

 in 
----> 1 10 / 0


ZeroDivisionError: division by zero

Есть разные типы исключений в Python и их тип выводится в сообщении: вверху примеры TypeError и ZeroDivisionError. Обе строки в сообщениях об ошибке представляют собой имена встроенных исключений Python.

Оставшаяся часть строки с ошибкой предлагает подробности о причине ошибки на основе ее типа.

Теперь рассмотрим встроенные исключения Python.

Встроенные исключения

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

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

  • Try: он запускает блок кода, в котором ожидается ошибка.
  • Except: здесь определяется тип исключения, который ожидается в блоке try (встроенный или созданный).
  • Else: если исключений нет, тогда исполняется этот блок (его можно воспринимать как средство для запуска кода в том случае, если ожидается, что часть кода приведет к исключению).
  • Finally: вне зависимости от того, будет ли исключение или нет, этот блок кода исполняется всегда.

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

Ошибка прерывания с клавиатуры (KeyboardInterrupt)

Исключение KeyboardInterrupt вызывается при попытке остановить программу с помощью сочетания Ctrl + C или Ctrl + Z в командной строке или ядре в Jupyter Notebook. Иногда это происходит неумышленно и подобная обработка поможет избежать подобных ситуаций.

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

try:
    inp = input()
    print('Нажмите Ctrl+C и прервите Kernel:')
except KeyboardInterrupt:
    print('Исключение KeyboardInterrupt')
else:
    print('Исключений не произошло')

Исключение KeyboardInterrupt

Стандартные ошибки (StandardError)

Рассмотрим некоторые базовые ошибки в программировании.

Арифметические ошибки (ArithmeticError)

  • Ошибка деления на ноль (Zero Division);
  • Ошибка переполнения (OverFlow);
  • Ошибка плавающей точки (Floating Point);

Все перечисленные выше исключения относятся к классу Arithmetic и вызываются при ошибках в арифметических операциях.

Деление на ноль (ZeroDivisionError)

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

try:  
    a = 100 / 0
    print(a)
except ZeroDivisionError:  
    print("Исключение ZeroDivisionError." )
else:  
    print("Успех, нет ошибок!")
Исключение ZeroDivisionError.

Переполнение (OverflowError)

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

try:  
    import math
    print(math.exp(1000))
except OverflowError:  
    print("Исключение OverFlow.")
else:  
    print("Успех, нет ошибок!")
Исключение OverFlow.

Ошибка утверждения (AssertionError)

Когда инструкция утверждения не верна, вызывается ошибка утверждения.

Рассмотрим пример. Предположим, есть две переменные: a и b. Их нужно сравнить. Чтобы проверить, равны ли они, необходимо использовать ключевое слово assert, что приведет к вызову исключения Assertion в том случае, если выражение будет ложным.

try:  
    a = 100
    b = "PythonRu"
    assert a == b
except AssertionError:  
    print("Исключение AssertionError.")
else:  
    print("Успех, нет ошибок!")

Исключение AssertionError.

Ошибка атрибута (AttributeError)

При попытке сослаться на несуществующий атрибут программа вернет ошибку атрибута. В следующем примере можно увидеть, что у объекта класса Attributes нет атрибута с именем attribute.

class Attributes(obj):
    a = 2
    print(a)

try:
    obj = Attributes()
    print(obj.attribute)
except AttributeError:
    print("Исключение AttributeError.")

2
Исключение AttributeError.

Ошибка импорта (ModuleNotFoundError)

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

import nibabel
---------------------------------------------------------------------------

ModuleNotFoundError                       Traceback (most recent call last)

 in 
----> 1 import nibabel


ModuleNotFoundError: No module named 'nibabel'

Ошибка поиска (LookupError)

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

Здесь есть два вида исключений:

  • Ошибка индекса (IndexError);
  • Ошибка ключа (KeyError);

Ошибка ключа

Если ключа, к которому нужно получить доступ, не оказывается в словаре, вызывается исключение KeyError.

try:  
    a = {1:'a', 2:'b', 3:'c'}  
    print(a[4])  
except LookupError:  
    print("Исключение KeyError.")
else:  
    print("Успех, нет ошибок!")

Исключение KeyError.

Ошибка индекса

Если пытаться получить доступ к индексу (последовательности) списка, которого не существует в этом списке или находится вне его диапазона, будет вызвана ошибка индекса (IndexError: list index out of range python).

try:
    a = ['a', 'b', 'c']  
    print(a[4])  
except LookupError:  
    print("Исключение IndexError, индекс списка вне диапазона.")
else:  
    print("Успех, нет ошибок!")
Исключение IndexError, индекс списка вне диапазона.

Ошибка памяти (MemoryError)

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

Ошибка имени (NameError)

Ошибка имени возникает, когда локальное или глобальное имя не находится.

В следующем примере переменная ans не определена. Результатом будет ошибка NameError.

try:
    print(ans)
except NameError:  
    print("NameError: переменная 'ans' не определена")
else:  
    print("Успех, нет ошибок!")
NameError: переменная 'ans' не определена

Ошибка выполнения (Runtime Error)

Ошибка «NotImplementedError»
Ошибка выполнения служит базовым классом для ошибки NotImplemented. Абстрактные методы определенного пользователем класса вызывают это исключение, когда производные методы перезаписывают оригинальный.

class BaseClass(object):
    """Опередляем класс"""
    def __init__(self):
        super(BaseClass, self).__init__()
    def do_something(self):
	# функция ничего не делает
        raise NotImplementedError(self.__class__.__name__ + '.do_something')

class SubClass(BaseClass):
    """Реализует функцию"""
    def do_something(self):
        # действительно что-то делает
        print(self.__class__.__name__ + ' что-то делает!')

SubClass().do_something()
BaseClass().do_something()

SubClass что-то делает!



---------------------------------------------------------------------------

NotImplementedError                       Traceback (most recent call last)

 in 
     14
     15 SubClass().do_something()
---> 16 BaseClass().do_something()


 in do_something(self)
      5     def do_something(self):
      6         # функция ничего не делает
----> 7         raise NotImplementedError(self.__class__.__name__ + '.do_something')
      8
      9 class SubClass(BaseClass):


NotImplementedError: BaseClass.do_something

Ошибка типа (TypeError)

Ошибка типа вызывается при попытке объединить два несовместимых операнда или объекта.

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

try:
    a = 5
    b = "PythonRu"
    c = a + b
except TypeError:
    print('Исключение TypeError')
else:
    print('Успех, нет ошибок!')

Исключение TypeError

Ошибка значения (ValueError)

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

В этом примере встроенная операция float получат аргумент, представляющий собой последовательность символов (значение), что является недопустимым значением для типа: число с плавающей точкой.

try:
    print(float('PythonRu'))
except ValueError:
    print('ValueError: не удалось преобразовать строку в float: 'PythonRu'')
else:
    print('Успех, нет ошибок!')
ValueError: не удалось преобразовать строку в float: 'PythonRu'

Пользовательские исключения в Python

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

Это можно сделать, создав новый класс, который будет наследовать из класса Exception в Python.

class UnAcceptedValueError(Exception):   
    def __init__(self, data):    
        self.data = data
    def __str__(self):
        return repr(self.data)

Total_Marks = int(input("Введите общее количество баллов: "))
try:
    Num_of_Sections = int(input("Введите количество разделов: "))
    if(Num_of_Sections < 1):
        raise UnAcceptedValueError("Количество секций не может быть меньше 1")
except UnAcceptedValueError as e:
    print("Полученная ошибка:", e.data)

Введите общее количество баллов: 10
Введите количество разделов: 0
Полученная ошибка: Количество секций не может быть меньше 1

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

Недостатки обработки исключений в Python

У использования исключений есть свои побочные эффекты, как, например, то, что программы с блоками try-except работают медленнее, а количество кода возрастает.

Дальше пример, где модуль Python timeit используется для проверки времени исполнения 2 разных инструкций. В stmt1 для обработки ZeroDivisionError используется try-except, а в stmt2if. Затем они выполняются 10000 раз с переменной a=0. Суть в том, чтобы показать разницу во времени исполнения инструкций. Так, stmt1 с обработкой исключений занимает больше времени чем stmt2, который просто проверяет значение и не делает ничего, если условие не выполнено.

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

import timeit
setup="a=0"
stmt1 = '''
try:
    b=10/a
except ZeroDivisionError:
    pass'''

stmt2 = '''
if a!=0:
    b=10/a'''

print("time=",timeit.timeit(stmt1,setup,number=10000))
print("time=",timeit.timeit(stmt2,setup,number=10000))

time= 0.003897680000136461
time= 0.0002797570000439009

Выводы!

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

Обработка исключений — один из основных факторов, который делает код готовым к развертыванию. Это простая концепция, построенная всего на 4 блоках: try выискивает исключения, а except их обрабатывает.

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

As it is known that 1.2 – 1.0 = 0.2 . But when you try to the same in python you will surprised by results:

>>> 1.2 - 1.0

Output:

0.199999999999999996

This can be considered as a bug in Python, but it is not. This has little to do with Python, and much more to do with how the underlying platform handles floating-point numbers. It’s a normal case encountered when handling floating-point numbers internally in a system. It’s a problem caused when the internal representation of floating-point numbers, which uses a fixed number of binary digits to represent a decimal number. It is difficult to represent some decimal number in binary, so in many cases, it leads to small roundoff errors. We know similar cases in decimal math, there are many results that can’t be represented with a fixed number of decimal digits, Example

10 / 3 = 3.33333333.......

In this case, taking 1.2 as an example, the representation of 0.2 in binary is 0.00110011001100110011001100…… and so on. It is difficult to store this infinite decimal number internally. Normally a float object’s value is stored in binary floating-point with a fixed precision (typically 53 bits). So we represent 1.2 internally as,

1.0011001100110011001100110011001100110011001100110011  

Which is exactly equal to :

1.1999999999999999555910790149937383830547332763671875

Still, you thinking why python is not solving this issue, actually it has nothing to do with python. It happens because it is the way the underlying c platform handles floating-point numbers and ultimately with the inaccuracy, we’ll always have been writing down numbers as a string of fixed number of digits. Note that this is in the very nature of binary floating-point: this is not a bug either in Python or C, and it is not a bug in your code either. You’ll see the same kind of behaviors in all languages that support our hardware’s floating-point arithmetic although some languages may not display the difference by default, or in all output modes). We have to consider this behavior when we do care about math problems with needs exact precisions or using it inside conditional statements. Check floating point section in python documentation for more such behaviours.

Python documentation says that FloatingPointError is raised when a float calculation fails. But what is exactly meant here by «a float calculation»?
I tried adding, multiplying and dividing with floats but never managed to raise this specific error. Instead, i got a TypeError:

10/'a'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'int' and 'str'

Can someone help me understand when a FloatingPointError is raised in python?

Ma0's user avatar

Ma0

14.9k3 gold badges33 silver badges64 bronze badges

asked Dec 19, 2016 at 13:39

DhKo's user avatar

6

It is part of the fpectl module. The FloatingPointError shouldn’t be raised if you don’t explicitly turn it on (fpectl.turnon_sigfpe()).

However mind the note:

The fpectl module is not built by default, and its usage is discouraged and may be dangerous except in the hands of experts. See also the section fpectl-limitations on limitations for more details.

Update: The fpectl module has been removed as of Python 3.7.


Even with FloatingPointErrors turned on, 10/'a' will never raise one. It will always raise a TypeError. A FloatingPointError will only be raised for operations that reach the point of actually performing floating-point math, like 1.0/0.0. 10/'a' doesn’t get that far.

Ben's user avatar

Ben

7847 silver badges17 bronze badges

answered Dec 19, 2016 at 13:43

MSeifert's user avatar

MSeifertMSeifert

141k35 gold badges328 silver badges345 bronze badges

4

You can also trigger a FloatingPointError within numpy, by setting the appropriate numpy.seterr (or numpy.errstate context manager) flag. For an example taken from the documentation:

>>> np.sqrt(-1)
nan
>>> with np.errstate(invalid='raise'):
...     np.sqrt(-1)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
FloatingPointError: invalid value encountered in sqrt

Interestingly, it also raises FloatingPointError when all operands are integers:

>>> old_settings = np.seterr(all='warn', over='raise')
>>> np.int16(32000) * np.int16(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FloatingPointError: overflow encountered in short_scalars

The documentation notes the conditions under which the FloatingPointError will be raised:

The floating-point exceptions are defined in the IEEE 754 standard [1]:

  • Division by zero: infinite result obtained from finite numbers.
  • Overflow: result too large to be expressed.
  • Underflow: result so close to zero that some precision was lost.
  • Invalid operation: result is not an expressible number, typically indicates that a NaN was produced.

Community's user avatar

answered Jun 15, 2018 at 10:46

gerrit's user avatar

gerritgerrit

22.7k17 gold badges90 silver badges166 bronze badges

Airbrake.io

Today we get started with our in-depth Python Exception Handling series by looking at the FloatingPointError. As with most programming languages, the FloatingPointError in Python indicates that something has gone wrong with a floating point calculation. However, unlike most other languages, Python will not raise a FloatingPointError by default. The ability to do so must be implemented by including the fpectl module when building your local Python environment.

In this article we’ll explore the FloatingPointError by first looking at where it resides in the overall Python Exception Class Hierarchy. We’ll also go over how the fpectl module can be enabled, and how doing so can allow the raising of FloatingPointErrors in your own code. Let’s get to it!

The Technical Rundown

All Python exceptions inherit from the BaseException class, or extend from an inherited class therein. The full exception hierarchy of this error is:

  • BaseException

    • Exception
    • ArithmeticError

      • FloatingPointError

Full Code Sample

Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.

import fpectl
from gw_utility.logging import Logging

def main():
    Logging.line_separator("FLOATING POINT")
    test_floating_point()

    Logging.line_separator("DIVISION BY ZERO")
    test_division_by_zero()

    Logging.line_separator("FLOATING POINT DIVISION BY ZERO", 60)
    test_floating_point_division_by_zero()

def test_floating_point():
    try:
        Logging.log(round(24.601 / 3.5, 4))
    except FloatingPointError as exception:
        # Output expected FloatingPointErrors.
        Logging.log_exception(exception)
    except Exception as exception:
        # Output expected Exceptions.
        Logging.log_exception(exception, False)

def test_division_by_zero():
    try:
        # Divide by zero.
        Logging.log(24 / 0)
    except FloatingPointError as exception:
        # Output expected FloatingPointErrors.
        Logging.log_exception(exception)
    except Exception as exception:
        # Output expected Exceptions.
        Logging.log_exception(exception, False)

def test_floating_point_division_by_zero():
    try:
        # Divide by floating point zero and round.
        Logging.log(round(24.601 / 0.0, 4))
    except FloatingPointError as exception:
        # Output expected FloatingPointErrors.
        Logging.log_exception(exception)
    except Exception as exception:
        # Output expected Exceptions.
        Logging.log_exception(exception, False)

if __name__ == " __main__":
    main()

import math
import sys
import traceback

class Logging:

    separator_character_default = '-'
    separator_length_default = 40

    @classmethod
    def __output(cls, *args, sep: str=' ', end: str='n', file=None):
        """Prints the passed value(s) to the console.

        :param args: Values to output.
        :param sep: String inserted between values, default a space.
        :param end: String appended after the last value, default a newline.
        :param file: A file-like object (stream); defaults to the current sys.stdout.
        :return: None
        """
        print(*args, sep=sep, end=end, file=file)

    @classmethod
    def line_separator(cls, value: str, length: int=separator_length_default, char: str=separator_character_default):
        """Print a line separator with inserted text centered in the middle.

        :param value: Inserted text to be centered.
        :param length: Total separator length.
        :param char: Separator character.
        """
        output = value

        if len(value) < length:
            # Update length based on insert length, less a space for margin.
            length -= len(value) + 2
            # Halve the length and floor left side.
            left = math.floor(length / 2)
            right = left
            # If odd number, add dropped remainder to right side.
            if length % 2 != 0:
                right += 1

            # Surround insert with separators.
            output = f'{char * left} {value} {char * right}'

        cls.__output(output)

    @classmethod
    def log(cls, *args, sep: str=' ', end: str='n', file=None):
        """Prints the passed value(s) to the console.

        :param args: Values to output.
        :param sep: String inserted between values, default a space.
        :param end: String appended after the last value, default a newline.
        :param file: A file-like object (stream); defaults to the current sys.stdout.
        """
        cls.__output(*args, sep=sep, end=end, file=file)

    @classmethod
    def log_exception(cls, exception: BaseException, expected: bool=True):
        """Prints the passed BaseException to the console, including traceback.

        :param exception: The BaseException to output.
        :param expected: Determines if BaseException was expected.
        """
        output = "[{}] {}: {}".format('EXPECTED' if expected else 'UNEXPECTED', type(exception). __name__ , exception)
        cls.__output(output)
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_tb(exc_traceback)

Enter fullscreen mode

Exit fullscreen mode

When Should You Use It?

As discussed in the introduction, before a FloatingPointError can even appear you’ll need to make sure your local Python build includes the fpectl module. Since this module is not included with most Python builds by default, you’d likely have had to explicitly build your Python with it if desired. Adding the fpectl module to can be accomplished by using the --with-fpectl flag when compiling Python. Going through the compilation process of Python is well beyond the scope of this article, but once fpectl is an included module, you can start testing the FloatingPointError.

For our example code we’re not doing anything spectacular. In fact, the FloatingPointError is effectively raised in situations where other ArithmeticErrors would normally appear, except that you’re using floating point numbers and the fpectl module is enabled. For example, you might raise a FloatingPointError where you’d normally get a ZeroDivisionError by attempting to divide by zero using a floating point value.

We’ve created a few simple testing methods starting with test_floating_point():

def test_floating_point():
    try:
        Logging.log(round(24.601 / 3.5, 4))
    except FloatingPointError as exception:
        # Output expected FloatingPointErrors.
        Logging.log_exception(exception)
    except Exception as exception:
        # Output expected Exceptions.
        Logging.log_exception(exception, False)

Enter fullscreen mode

Exit fullscreen mode

Executing this code works as expected, performing the floating point calculation and rounding the result to four decimal places before outputting the result to our log:

------------ FLOATING POINT ------------
7.0289

Enter fullscreen mode

Exit fullscreen mode

Now, let’s step away from using a floating point value and use regular integers while attempting to divide by zero:

def test_division_by_zero():
    try:
        # Divide by zero.
        Logging.log(24 / 0)
    except FloatingPointError as exception:
        # Output expected FloatingPointErrors.
        Logging.log_exception(exception)
    except Exception as exception:
        # Output expected Exceptions.
        Logging.log_exception(exception, False)

Enter fullscreen mode

Exit fullscreen mode

This raises an unexpected ZeroDivisionException since, even though fpectl is enabled, we aren’t using a floating point value in our calculation:

----------- DIVISION BY ZERO -----------
[UNEXPECTED] ZeroDivisionError: division by zero
  File "D:/work/Airbrake.io/Exceptions/Python/BaseException/Exception/ArithmeticError/FloatingPointError/main.py", line 30, in test_division_by_zero
    Logging.log(24 / 0)

Enter fullscreen mode

Exit fullscreen mode

Finally, let’s try the same division by zero while using floating point values:

def test_floating_point_division_by_zero():
    try:
        # Divide by floating point zero and round.
        Logging.log(round(24.601 / 0.0, 4))
    except FloatingPointError as exception:
        # Output expected FloatingPointErrors.
        Logging.log_exception(exception)
    except Exception as exception:
        # Output expected Exceptions.
        Logging.log_exception(exception, False)

Enter fullscreen mode

Exit fullscreen mode

As you might suspect, this raises a FloatingPointError for us:

------------- FLOATING POINT DIVISION BY ZERO --------------
[EXPECTED] FloatingPointError: invalid value encountered in divide
  File "D:/work/Airbrake.io/Exceptions/Python/BaseException/Exception/ArithmeticError/FloatingPointError/main.py", line 42, in test_floating_point_division_by_zero
    Logging.log(round(24.601 / 0.0, 4))

Enter fullscreen mode

Exit fullscreen mode

There we have the basics of using FloatingPointErrors. However, before you jump into adding the fpectl module to your Python to distinguish between FloatingPointErrors and normal ArithmeticErrors, there are a number of caveats and cautions to be aware of. The IEEE 754 standard for floating point arithmetic defines a number of universal standards for the formatting, rounding, allowed operations, and exception handling practices of floating point numbers. However, your code must be explicitly told to capture IEEE 754 exceptions in the form of SIGFPE signals generated by the local processor. Consequently, while Python is configured to do so via the fpectl module, many other custom scripts/applications are not.

The other major consideration is that use of the fpectl module is generally discouraged, in large part because it is not thread safe. Thread safe applications (that is, most properly developed Python applications) allow data structures to be safely shared between multiple threads without fear of one thread manipulating or altering some data that another thread is using (or where another thread sees different data). However, using the fpectl module means your floating point data is no longer thread safe, which could cause major issues in multithreaded applications. To be on the safe side, it’s generally recommended that you avoid fpectl and use another form of application logic to check for arithmetic errors.

Airbrake’s robust error monitoring software provides real-time error monitoring and automatic exception reporting for all your development projects. Airbrake’s state of the art web dashboard ensures you receive round-the-clock status updates on your application’s health and error rates. No matter what you’re working on, Airbrake easily integrates with all the most popular languages and frameworks. Plus, Airbrake makes it easy to customize exception parameters, while giving you complete control of the active error filter system, so you only gather the errors that matter most.

Check out Airbrake’s error monitoring software today and see for yourself why so many of the world’s best engineering teams use Airbrake to revolutionize their exception handling practices!

This article contains affiliate links. See my affiliate disclosure for more information.

Floating-point numbers are a fast and efficient way to store and work with numbers, but they come with a range of pitfalls that have surely stumped many fledgling programmers — perhaps some experienced programmers, too! The classic example demonstrating the pitfalls of floats goes like this:

>>> 0.1 + 0.2 == 0.3
False

Seeing this for the first time can be disorienting. But don’t throw your computer in the trash bin. This behavior is correct!

This article will show you why floating-point errors like the one above are common, why they make sense, and what you can do to deal with them in Python.


Your Computer is a Liar… Sort Of

You’ve seen that 0.1 + 0.2 is not equal to 0.3 but the madness doesn’t stop there. Here are some more confounding examples:

>>> 0.2 + 0.2 + 0.2 == 0.6
False

>>> 1.3 + 2.0 == 3.3
False

>>> 1.2 + 2.4 + 3.6 == 7.2
False

The issue isn’t restricted to equality comparisons, either:

>>> 0.1 + 0.2 <= 0.3
False

>>> 10.4 + 20.8 > 31.2
True

>>> 0.8 - 0.1 > 0.7
True

So what’s going on? Is your computer lying to you? It sure looks like it, but there’s more going on beneath the surface.

When you type the number 0.1 into the Python interpreter, it gets stored in memory as a floating-point number. There’s a conversion that takes place when this happens. 0.1 is a decimal in base 10, but floating-point numbers are stored in binary. In other words, 0.1 gets converted from base 10 to base 2.

The resulting binary number may not accurately represent the original base 10 number. 0.1 is one example. The binary representation is (0.0overline{0011}). That is, 0.1 is an infinitely repeating decimal when written in base 2. The same thing happens when you write the fraction ⅓ as a decimal in base 10. You end up with the infinitely repeating decimal (0.overline{33}).

Computer memory is finite, so the infinitely repeating binary fraction representation of 0.1 gets rounded to a finite fraction. The value of this number depends on your computer’s architecture (32-bit vs. 64-bit). One way to see the floating-point value that gets stored for 0.1 is to use the .as_integer_ratio() method for floats to get the numerator and denominator of the floating-point representation:

>>> numerator, denominator = (0.1).as_integer_ratio()
>>> f"0.1 ≈ {numerator} / {denominator}"
'0.1 ≈ 3602879701896397 / 36028797018963968'

Now use format() to show the fraction accurate to 55 decimal places:

>>> format(numerator / denominator, ".55f")
'0.1000000000000000055511151231257827021181583404541015625'

So 0.1 gets rounded to a number slightly larger than its true value.

This error, known as floating-point representation error, happens way more often than you might realize.


Representation Error is Really Common

There are three reasons that a number gets rounded when represented as a floating-point number:

  1. The number has more significant digits than floating points allow.
  2. The number is irrational.
  3. The number is rational but has a non-terminating binary representation.

64-bit floating-point numbers are good for about 16 or 17 significant digits. Any number with more significant digits gets rounded. Irrational numbers, like π and e, can’t be represented by any terminating fraction in any integer base. So again, no matter what, irrational numbers will get rounded when stored as floats.

These two situations create an infinite set of numbers that can’t be exactly represented as a floating-point number. But unless you’re a chemist dealing with tiny numbers, or a physicist dealing with astronomically large numbers, you’re unlikely to run into these problems.

What about non-terminating rational numbers, like 0.1 in base 2? This is where you’ll encounter most of your floating-point woes, and thanks to the math that determines whether or not a fraction terminates, you’ll brush up against representation error more often than you think.

In base 10, a fraction terminates if its denominator is a product of powers of prime factors of 10. The two prime factors of 10 are 2 and 5, so fractions like ½, ¼, ⅕, ⅛, and ⅒ all terminate, but ⅓, ⅐, and ⅑ do not. In base 2, however, there is only one prime factor: 2. So only fractions whose denominator is a power of 2 terminate. As a result, fractions like ⅓, ⅕, ⅙, ⅐, ⅑, and ⅒ are all non-terminating when expressed in binary.

You can now understand the original example in this article. 0.1, 0.2, and 0.3 all get rounded when converted to floating-point numbers:

>>> # -----------vvvv  Display with 17 significant digits
>>> format(0.1, ".17g")
'0.10000000000000001'

>>> format(0.2, ".17g")
'0.20000000000000001'

>>> format(0.3, ".17g")
'0.29999999999999999'

When 0.1 and 0.2 are added, the result is a number slightly larger than 0.3:

>>> 0.1 + 0.2
0.30000000000000004

Since 0.1 + 0.2 is slightly larger than0.3 and 0.3 gets represented by a number slightly smaller than itself, the expression 0.1 + 0.2 == 0.3 evaluates to False.

Floating-point representation error is something every programmer in every language needs to be aware of and know how to handle. It’s not specific to Python. You can see the result of printing 0.1 + 0.2 in many different languages over at Erik Wiffin’s aptly named website 0.30000000000000004.com.


So, how do you deal with floating-point representation errors when comparing floats in Python? The trick is to avoid checking for equality. Never use ==, >=, or <= with floats. Use the math.isclose() function instead:

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True

math.isclose() checks if the first argument is acceptably close to the second argument. But what exactly does that mean? The key idea is to examine the distance between the first argument and the second argument, which is equivalent to the absolute value of the difference of the values:

>>> a = 0.1 + 0.2
>>> b = 0.3
>>> abs(a - b)
5.551115123125783e-17

If abs(a - b) is smaller than some percentage of the larger of a or b, then a is considered sufficiently close to b to be «equal» to b. This percentage is called the relative tolerance. You can specify the relative tolerance with the rel_tol keyword argument of math.isclose() which defaults to 1e-9. In other words, if abs(a - b) is less than 1e-9 * max(abs(a), abs(b)), then a and b are considered «close» to each other. This guarantees that a and b are equal to about nine decimal places.

You can change the relative tolerance if you need to:

>>> math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-20)
False

Of course, the relative tolerance depends on constraints set by the problem you’re solving. For most everyday applications, however, the default relative tolerance should suffice.

There’s a problem if one of a or b is zero and rel_tol is less than one, however. In that case, no matter how close the nonzero value is to zero, the relative tolerance guarantees that the check for closeness will always fail. In this case, using an absolute tolerance works as a fallback:

>>> # Relative check fails!
>>> # ---------------vvvv  Relative tolerance
>>> # ----------------------vvvvv  max(0, 1e-10)
>>> abs(0 - 1e-10) < 1e-9 * 1e-10
False

>>> # Absolute check works!
>>> # ---------------vvvv  Absolute tolerance
>>> abs(0 - 1e-10) < 1e-9
True

math.isclose() will do this check for you automatically. The abs_tol keyword argument determines the absolute tolerance. However, abs_tol defaults to 0.0So you’ll need to set this manually if you need to check how close a value is to zero.

All in all, math.isclose() returns the result of the following comparison, which combines the relative and absolute tests into a single expression:

abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

math.isclose() was introduced in PEP 485 and has been available since Python 3.5.


When Should You Use math.isclose()?

In general, you should use math.isclose() whenever you need to compare floating-point values. Replace == with math.isclose():

>>> # Don't do this:
>>> 0.1 + 0.2 == 0.3
False

>>> # Do this instead:
>>> math.isclose(0.1 + 0.2, 0.3)
True

You also need to be careful with >= and <= comparisons. Handle the equality separately using math.isclose() and then check the strict comparison:

>>> a, b, c = 0.1, 0.2, 0.3

>>> # Don't do this:
>>> a + b <= c
False

>>> # Do this instead:
>>> math.isclose(a + b, c) or (a + b < c)
True

Various alternatives to math.isclose() exist. If you use NumPy, you can leverage numpy.allclose() and numpy.isclose():

>>> import numpy as np

>>> # Use numpy.allclose() to check if two arrays are equal
>>> # to each other within a tolerance.
>>> np.allclose([1e10, 1e-7], [1.00001e10, 1e-8])
False

>>> np.allclose([1e10, 1e-8], [1.00001e10, 1e-9])
True

>>> # Use numpy.isclose() to check if the elements of two arrays
>>> # are equal to each other within a tolerance
>>> np.isclose([1e10, 1e-7], [1.00001e10, 1e-8])
array([ True, False])

>>> np.isclose([1e10, 1e-8], [1.00001e10, 1e-9])
array([ True, True])

Keep in mind that the default relative and absolute tolerances are not the same as math.isclose(). The default relative tolerance for both numpy.allclose() and numpy.isclose() is 1e-05 and the default absolute tolerance for both is 1e-08.

math.isclose() is especially useful for unit tests, although there are some alternatives. Python’s built-in unittest module has a unittest.TestCase.assertAlmostEqual() method. However, that method only uses an absolute difference test. It’s also an assertion, meaning that failures raise an AssertionError, making it unsuitable for comparisons in your business logic.

A great alternative to math.isclose() for unit testing is the pytest.approx() function from the pytest package. Unlike math.isclose(), pytest.approx() only takes one argument — namely, the value you expect:

>>> import pytest
>>> 0.1 + 0.2 == pytest.approx(0.3)
True

pytest.approx() has rel_tol and abs_tol keyword arguments for setting the relative and absolute tolerances. The default values are different from math.isclose(), however. rel_tol has a default value of 1e-6 and abs_tol has a default value of 1e-12.

If the argument passed to pytest.approx() is array-like, meaning it’s a Python iterable like a list or a tuple, or even a NumPy array, then pytest.approx() behaves similar to numpy.allclose() and returns whether or not the two arrays are equal within the tolerances:

>>> import numpy as np                                                          
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == pytest.approx(np.array([0.3, 0.6])) 
True

pytest.approx() will even work with dictionary values:

>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == pytest.approx({'a': 0.3, 'b': 0.6})
True

Floating-point numbers are great for working with numbers whenever absolute precision isn’t needed. They are fast and memory efficient. But if you do need precision, then there are some alternatives to floats that you should consider.


Floating-Point Alternatives That Are Precise

Two built-in numeric types in Python offer full precision for situations where floats are inadequate: Decimal and Fraction.

The Decimal Type

The Decimal type can store decimal values exactly with as much precision as you need. By default, Decimal preserves 28 significant figures, but you can change this to whatever you need to suit the specific problem you’re solving:

>>> # Import the Decimal type from the decimal module
>>> from decimal import Decimal

>>> # Values are represented exactly so no rounding error occurs
>>> Decimal("0.1") + Decimal("0.2") == Decimal("0.3")
True

>>> # By default 28 significant figures are preserved
>>> Decimal(1) / Decimal(7)
Decimal('0.1428571428571428571428571429')

>>> # You can change the significant figures if needed
>>> from decimal import getcontext
>>> getcontext().prec = 6  # Use 6 significant figures
>>> Decimal(1) / Decimal(7)
Decimal('0.142857')

You can read more about the Decimal type in the Python docs.

The Fraction Type

Another alternative to floating-point numbers is the Fraction type. Fraction can store rational numbers exactly and overcomes representation error issues encountered by floating-point numbers:

>>> # import the Fraction type from the fractions module
>>> from fractions import Fraction

>>> # Instantiate a Fraction with a numerator and denominator
>>> Fraction(1, 10)
Fraction(1, 10)

>>> # Values are represented exactly so no rounding error occurs
>>> Fraction(1, 10) + Fraction(2, 10) == Fraction(3, 10)
True

Both Fraction and Decimal offer numerous benefits over standard floating-point values. However, these benefits come at a price: reduced speed and higher memory consumption. If you don’t need absolute precision, you’re better off sticking with floats. But for things like financial and mission-critical applications, the tradeoffs incurred by Fraction and Decimal may be worthwhile.


Conclusion

Floating-point values are both a blessing and a curse. They offer fast arithmetic operations and efficient memory use at the cost of inaccurate representation. In this article, you learned:

  • Why floating-point numbers are imprecise
  • Why floating-point representation error is common
  • How to correctly compare floating-point values in Python
  • How to  represent numbers precisely using Python’s Fraction and Decimal types

If you learned something new, then there might be even more that you don’t know about numbers in Python. For example, did you know the int type isn’t the only integer type in Python? Find out what the other integer type is and other little-known facts about numbers in my article 3 Things You Might Not Know About Numbers in Python.

3 Things You Might Not Know About Numbers in Python

If you’ve written anything in Python, you’ve probably used a number in one of your programs. But there’s more to numbers than just their raw values.

David Amos


Additional Resources

  • Floating-Point Arithmetic: Issues and Limitations
  • The Floating-Point Guide
  • The Perils of Floating Point
  • Floating-Point Math
  • What Every Computer Scientist Should Know About Floating-Point Arithmetic
  • How to Round Numbers in Python

Thanks to Brian Okken for helping catch an issue with one of the pytest.approx() examples.


Want more like this?

One email, every Saturday, with one actionable tip.
Always less than 5 minutes of your time.

28.12.2019Python, Дизайн компилятора

Как известно, 1.2 - 1.0 = 0.2 . Но когда вы попробуете то же самое в python, вы будете удивлены результатами:

>>> 1.2 - 1.0

Выход:

0.199999999999999996

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

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

10 / 3 = 3.33333333.......

В этом случае, взяв 1.2 в качестве примера, представление 0.2 в двоичном виде составляет 0.00110011001100110011001100...... и так далее.
Трудно хранить это бесконечное десятичное число внутри страны. Обычно значение объекта с плавающей запятой сохраняется в двоичной форме с плавающей точкой с фиксированной точностью ( обычно 53 бита ).

Таким образом, мы представляем 1,2 внутри, как,

1.0011001100110011001100110011001100110011001100110011  

Что в точности равно:

1.1999999999999999555910790149937383830547332763671875

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

Обратите внимание, что это по своей природе двоичная с плавающей точкой: это не ошибка ни в Python, ни в C , и это не ошибка в вашем коде. Вы увидите одинаковое поведение на всех языках, которые поддерживают арифметику с плавающей точкой нашего оборудования, хотя некоторые языки могут не отображать разницу по умолчанию или во всех режимах вывода). Мы должны учитывать это поведение, когда заботимся о математических задачах с точной точностью или используем их в условных выражениях.
Проверьте раздел с плавающей точкой в документации по Python для большего количества таких поведений.

Рекомендуемые посты:

  • Регулярное выражение Python | Проверьте, является ли ввод числом с плавающей точкой или нет
  • Python программа для преобразования плавающего в двоичный
  • Python | Ошибка подтверждения
  • Python | Средняя квадратическая ошибка
  • Ошибка NZEC в Python
  • Программа Python для представления плавающего числа в шестнадцатеричном формате по стандарту IEEE 754
  • Python | 404 Обработка ошибок во Flask
  • Python | Запрос пароля во время выполнения и завершение с сообщением об ошибке
  • Python PIL | Метод Image.point ()
  • Python | Самая дальняя точка на горизонтальных линиях в 2D плоскости
  • Python | Кроссовер с одной точкой в генетическом алгоритме
  • ML | Потеря журнала и средняя квадратическая ошибка
  • Обработка ошибок в дизайне компилятора
  • Обнаружение ошибок и восстановление в компиляторе
  • ML | Математическое объяснение СКО и R-квадрата ошибки

Ошибка с плавающей точкой в Python

0.00 (0%) 0 votes

Понравилась статья? Поделить с друзьями:
  • Floating point error overflow encountered in exp
  • Floating point division by zero как исправить delphi
  • Float object is not subscriptable как исправить
  • Float object is not iterable python ошибка
  • Float object is not callable ошибка