Числа в языке Python представлены тремя встроенными типами: целые (int
), вещественные (float
) и комплексные (comlex
), а так же двумя типами чисел, которые предоставляет его стандартная библиотека: десятичные дроби неограниченной точности (Decimal
) и обыкновенные дроби (Float
).
Начнем с того, что числовые литералы не содержат знаки "+
" и "-
", т.е. с точки зрения Python число −3.14
не является единым отрицательным числом, а является командой, которая состоит из унарного оператора (-
) и числа 3.14
. Это говорит о том, что знаки "+
" и "-
" хоть и способны обозначать положительные и отрицательные числа, но на самом деле эти знаки являются операторами:
-+-+--++1
1
Помимо этого, числа встроенных типов (int
, float
и complex
) относятся к неизменяемым объектам, т.е. объектам, чья структура не может быть изменена напрямую. Нельзя изменить какую-нибудь цифру существующего числа, нельзя расставить его цифры в обратном порядке. То, что кажется изменением числа, на самом деле таковым не является. Например:
x = 1
x = x - 1
x
0
Вроде бы мы создали объект-число со значением 1
, а затем изменили его значение на 0
. Но если это так, то id
объекта (его адрес в памяти) не должен меняться, а он меняется:
x = 1
id(x)
94004608652320
x = x - 1
x
0
id(x)
94004608652288
Как видим, изменения объекта не произошло, старый объект исчез и появился новый.
Результат для встроенных числовых типов всегда преобразуется к более общему типу, если это необходимо. Например, если частное двух целых чисел (int
) не является целым числом, то оно будет преобразовано к вещественному типу (float
):
1 / 25
0.04
А если результат не может быть выражен типами int
и float
, то он будет преобразован к типу complex
. Так что если вдруг будете вычислять корни четной степени из отрицательных чисел, не ждите ошибки:
(-3) ** 0.5
(1.0605752387249068e-16+1.7320508075688772j)
(-3.14) ** 0.5
(1.0850398284807605e-16+1.772004514666935j)
В самом общем смысле, целые числа - это самые обыкновенные целые числа со знаком или без, например: $−11$, $126$, $0$ или $401734511064747568885490523085290650630550748445698208825344$. Последнее число в примере - это $2^{198}$, в чем очень легко убедиться:
2 ** 198
401734511064747568885490523085290650630550748445698208825344
Например, вы можете легко убедиться в том что 561 - число Кармайкла, действительно, проходит тест Ферма:
2 ** (561 - 1) % 561 == 1
True
Однако, если вы попытаетесь проверить это для числа $9746347772161$, то результата придется ждать очень долго (если вообще дождемся).
Но, если воспользоваться встроенной функцией pow()
, то результат будет получен моментально:
pow(2, 9746347772160, 9746347772161) == 1
True
Все дело в том, что данная функция для трех аргументов реализована, как алгоритм быстрого возведения в степень по модулю, что на порядки быстрее, чем эквивалентная команда:
2 ** 9746347772160 % 9746347772161
Наверное, было бы правильнее называть эти числа числами с плавающей точкой нежели вещественными, но в принципе, с определенной натяжкой, можно сказать, что это, как бы одно и тоже. Давайте разберемся почему.
В качестве примера возьмем число $\sqrt2$, которое является вещественным, потому что мы никогда не сможем выразить его с помощью обыкновенной дроби. А если мы все-таки извлечем корень из двойки, то обнаружим, что это бесконечная десятичная дробь.
Но вычислив этот корень на Python:
2 ** (1 / 2) # равносильно 2**0.5
1.4142135623730951
Мы увидим, что никакой бесконечной дробью и не пахнет. Python вернул только начало этой дроби, а все остальное отбросил, т.е. он вернул число с плавающей точкой, которое как бы и соответствует вещественному числу, но с определенной погрешностью.
На самом деле, работая с числами с плавающей точкой, мы очень часто подразумеваем числа вещественные, например вот такое число $\sqrt[77]7$, его мы увидим в виде конечной десятичной дроби:
7 ** (1 / 77)
1.0255935932948266
А число $7^{-77}$ $-$ в виде мантисы $8.461569363277291$ (опять же конечной десятичной дроби) и порядка $−66$:
7 ** (-77)
8.461569363277291e-66
Кстати, можно было бы подумать, что 8.461569363277291*10**(-66)
вернет результат идентичный предыдущему, но:
8.461569363277291*10**(-66)
8.461569363277292e-66
Отличие настолько незначительное, что для нас оно становится абсолютно неважным. Возможно, поэтому числа типа float
в Python все чаще называют вещественными, а не числами с плавающей точкой.
def far_to_cels_v1(far):
return (far - 32) * 5/9
def far_to_cels_v2(far):
return 5/9 * (far - 32)
far_to_cels_v1(456)
235.55555555555554
far_to_cels_v2(456)
235.55555555555557
Числа данного типа позволяют производить вычисления над десятичными дробями с заданной точностью. Возможно, вы сразу задались вопросом: "А разве типом float
мы обойтись не можем? Ведь это как бы и десятичные дроби, а погрешности при вычислениях с ними, настолько ничтожны, что мы можем вообще не обращать на них внимания." Чтож, вполне обоснованное замечание, но давайте посмотрим вот на такой пример:
0.11 + 0.29
0.39999999999999997
Должно получиться ровно $0.4$, а получилось $0.39999999999999997$. Конечно, как вы сказали: на такую погрешность можно вообще не обращать внимания, но как минимум, такой результат сложения кажется странным сам по себе. Ну в самом деле, разве это так трудно правильно сложить? Дело в том, что компьютер использует двоичную арифметику, над числами в двоичном представлении, а конечная десятичная дробь, в двоичном представлении может оказаться бесконечной, бесконечный "хвост" которой и отбрасывается при вычислениях, что в свою очередь и приводит к таким "ничтожным" погрешностям.
Но, как говорится "Дьявол кроется в мелочах". Очень неприятным последствием таких "ничтожно-маленьких" погрешностей является то, что вы не можете точно проверить истинность равенства:
0.7 + 0.2 - 0.9 == 0
False
Потому что с точки зрения компьютера:
0.7 + 0.2 - 0.9
-1.1102230246251565e-16
А в финансовой и бухгалтерской среде подобные логические проверки выполняются постоянно.
Вторым неприятным последствием становится то, что погрешности имеют свойство накопления. Расмотрим простой пример:
s = 0
for i in range(100000000):
s += 0.1
s
9999999.98112945
Мы $100000000$ раз сложили число $0.1$ с самим собой, но вместо $10000000$ мы получили $9999999.98112945$, которое отличается от правильного результата на целых $0.018870549276471138$. В принципе не так уж и сильно отличается. Да и пример "притянут за уши". Но что-то подобное происходит при решении дифференциальных уравнений. Если с помощью таких уравнений строится траектория космического аппарата, то из-за такой мизерной погрешности он, конечно, полетит в сторону нужной планеты, но пролетит мимо. А если вы рассчитываете параметры химической реакции, то на компьютере все может выглядеть более чем безобидно, но в действительности, из-за этой мизерной погрешности вполне может произойти взрыв.
Потребность в повышенной точности, возникает очень редко, но возникает неспроста. Именно эту потребность и призваны удовлетворить числа типа Decimal
. Этот тип не является встроенным, а предоставляется модулем Decimal
из стандартной библиотеки Python:
from decimal import * # импортируем модуль
getcontext().prec = 10 # устанавливаем точность
# Вычислим частное 13/17
Decimal(13) / Decimal(17)
Decimal('0.7647058824')
Причем точность может быть настолько большой, насколько позволяет мощность компьютера. Допустим, мы хотим видеть результат с точностью $80$ знаков после запятой (хотя можем увидеть и $1000$), вот они:
getcontext().prec = 80 # меняем точность
Decimal(13) / Decimal(17)
Decimal('0.76470588235294117647058823529411764705882352941176470588235294117647058823529412')
Хотелось бы думать, что такая точность доступна абсолютно для всех математических операций и функций, например таких как всякие синусы, косинусы и прочая экзотика. Но нет, слишком хорошо – тоже не хорошо. К тому же все эти и другие функции могут быть получены с помощью базовых математических операций, которые модулем Decimal прекрасно поддерживаются, например:
Decimal(3).sqrt() # квадратный корень из 3
Decimal('1.7320508075688772935274463415058723669428052538103806280558069794519330169088000')
Decimal(3)**Decimal(1/7) # корень 7-й степени
Decimal('1.1699308127586868762703324263880195497962096309602270476311059210484631095336891')
Decimal(3).ln() # натуральный логарифм
Decimal('1.0986122886681096913952452369225257046474905578227494517346943336374942932186090')
#from decimal import * # импортируем модуль
def far_to_cels_v1(far):
return (Decimal(far) - Decimal(32)) * Decimal(5/9)
def far_to_cels_v2(far):
return Decimal(5/9) * (Decimal(far) - Decimal(32))
far_to_cels_v1(456)
Decimal('235.5555555555555660163236098')
far_to_cels_v2(456)
Decimal('235.5555555555555660163236098')
from fractions import Fraction
a = Fraction(21, 49)
a
Fraction(3, 7)
Честно говоря без чисел типа Fraction
можно легко обойтись, но из примера видно, что данный модуль выполнил сокращение числителя и знаменателя автоматически, что довольно любопытно и наводит на вопрос "А где бы мне это могло пригодиться?".
Самый очевидный ответ – числовые ряды и пределы. Для примера рассмотрим ряд Лейбница, который сходится к $\pi/4$ (правда медленно... ооочень медленно сходится):
$1 - \frac{1}{3} + \frac{1}{5} - \frac{1}{7} + \frac{1}{9} - \frac{1}{11} + \frac{1}{13} - \frac{1}{15} + \frac{1}{17} - \frac{1}{19} + \frac{1}{21} - \cdots = \sum_{n=0}^\infty \, \frac{(-1)^n}{2n+1}$
for n in range(20):
print(Fraction((-1)**n, 2*n + 1), end=', ')
1, -1/3, 1/5, -1/7, 1/9, -1/11, 1/13, -1/15, 1/17, -1/19, 1/21, -1/23, 1/25, -1/27, 1/29, -1/31, 1/33, -1/35, 1/37, -1/39,
Или посмотреть на поведение вот такого предела:
$\pi=\lim \limits_{m\rightarrow \infty }{\frac { (m!)^{4}\,{2}^{4m}}{\left[ (2m )! \right] ^{2}\,m}}$
который тоже можно выразить с помощью чисел типа fractions:
from math import factorial
for m in range(1, 20):
pi = Fraction(factorial(m)**4*2**(4*m), factorial(2*m)**2*m)
print(pi, '=', pi.numerator / pi.denominator)
4 = 4.0 32/9 = 3.5555555555555554 256/75 = 3.4133333333333336 4096/1225 = 3.3436734693877552 65536/19845 = 3.3023935500125976 524288/160083 = 3.2751010413348074 4194304/1288287 = 3.255721745232235 134217728/41409225 = 3.2412518708089806 4294967296/1329696225 = 3.2300364664117174 34359738368/10667118605 = 3.221088996975674 274877906944/85530896451 = 3.2137849402931895 4398046511104/1371086188563 = 3.2077097324665482 70368744177664/21972535073125 = 3.202577396894602 562949953421312/176021737014375 = 3.198184286610796 4503599627370496/1409850293610375 = 3.1943814515494275 288230376151711744/90324408810638025 = 3.1910574333896466 18446744073709551616/5786075364399106425 = 3.18812716944714 147573952589676412928/46326420401234675625 = 3.1855246166557545 1180591620717411303424/370882277949065911875 = 3.1831977177392785
from fractions import Fraction
def far_to_cels_v1(far):
return (far - 32) * Fraction(5/9)
def far_to_cels_v2(far):
return Fraction(5/9) * (far - 32)
far_to_cels_v1(245)
Fraction(532925955905508717, 4503599627370496)
far_to_cels_v2(245)
Fraction(532925955905508717, 4503599627370496)
float(Fraction(532925955905508717, 4503599627370496))
118.33333333333334