Программирование и научные вычисления на языке Python/§2

Здесь мы рассмотрим новый пример — перевод значения температуры из шкалы Фаренгейта в шкалу Цельсия. Мы узнаем о старшинстве арифметических операций, об использовании встроенных математических функции, например e(x) или sin(x), о том как можно использовать интерактивную оболочку Python в качестве калькулятора. Кроме того, мы поговорим об ошибках — ошибке целочисленного деления и погрешности вычислений. Для тех, кто знаком с комплексной переменной, я расскажу о том как с ней работать в Python. После этого урока в своих вычислениях вы сможете обходиться без инженерного калькулятора.

Другая формула: Цельсий/Фаренгейт

править

Следующим нашим примером послужит формула для перехода от градусов Цельсия в соответствующее значение по Фаренгейту:

  (2.1)

В этой формуле символу С соответствует температура в градусах шкалы Цельсия, а символу F — температура в градусах шкалы Фаренгейта. Наша цель состоит в том, чтобы написать программу, которая расчитывает F, когда мы знаем С.


Ошибка целочисленного деления

править

Если написать решение, основываясь только на тех знаниях, что мы получили из предыдущего урока, то получим для температуры, например, 21 градус по Цельсию, следующий текст:

C = 21
F = (9 / 5) * C + 32
print (F)


Когда вы запустите программу, программа напишет результат: 53. Вроде бы все работает. Но не стоит забывать о проверке результатов. В этом случае легко проверить результат, в столбик или на калькуляторе:

 

Но что мы сделали не так? Ведь формула в программе выглядит правильно. Ошибка в нашей программе одна из самых часто распространенных ошибок, которые по незнанию делают новички. В многих языках программирования существует два типа деления (division): "float division" и "integer division". Float division именно то, чего мы ждем математически: 9/5 равно 1.8, то есть число с плавающей (float) запятой.

Integer division для деления целых чисел в результате дает целое число, отрезая часть после точки, то есть "округляя вниз", до ближайшего меньшего целого, в нашем случае до единицы. То есть целочисленное деление рассматривает сколько целых делителей может поместиться в делимом. Это означает что для целочисленного деления 9/5 = 1. Другие примеры: 16/6 = 2, 1/5 = 0, но заметьте: 18/3 = 6. Многие языки программирования, включая Fortran, C, C++, Java и в том числе Python интерпретируют операцию деления a/b как integer division (целочисленное деление) когда оба числа a и b целые. Если хотя бы один из них является числом с плавающей точкой, то применяется обычное математическое деление.

Проблема нашей программы заключалась именно в целочисленном делении, поскольку часть формулы содержит выражение 9/5. Несложно проследить как получилось число 53. 9/5 в целочисленном делении дает 1, 1 умножается на 21 и в итоге 21 + 32 = 53. Очень скоро мы узнаем как двумя символами можно решить эту проблему и написать корректно работающую программу, но сначала будет полезно познакомиться с часто встречающемся в программировании на Python слове объект (object).

Замечание: В версии Python 3.3 ситуация с делением обстоит так: Integer division отвечает символ "//", float division - "/"

Объекты в Python

править

Когда мы пишем:


С = 21


Python создает int (integer, целочисленный) объект и присваивает ему значение 21. Переменная C действует, как имя этого int object. Также, если бы мы написали C = 21.0, Python бы определил это число как float, создал бы соответствующий float объект и присвоил ему значение 21. Таким образом, рассматривая любое выражение присваивания (assignment statement), с левой стороны от знака равенства мы видим имя переменной, а справа от знака равенства видим объект. Можно сказать, что программирование на Python заключается в определении и изменении объектов.

Самое важное, что стоит сейчас понять, что для математики безразлично, напишите вы 21 или 21.0, но в программе Python это две разные вещи: 21 — это объект целого (int) типа, 21.0 — объект типа float. Эти объекты расположены где-то в разных частях компьютерной памяти, и, когда программа встречает переменную, например С, она вызывает нужный объект из нужной области памяти. Причина наличия двух таких типов в том, что числа типа int занимают в памяти меньше места, значит, если в решении задачи не требуется дробных чисел, то работать с целыми числами (числами типа int) программа будет быстрее, выгоднее.

Существует множество типов объектов в Python, и позже мы научимся создавать собственные объекты. Некоторые объекты могут содержать множество данных, не только целые и дробные числа. Например, уже в первом уроке, мы встретились с str (string) объектами, которые выводили строки текста.


Как избежать целочисленного деления

править

Существует несколько способов сделать это. Наиболее широко используемый метод уже упоминался. Нам просто нужно, чтобы хотя бы один операнд (или делимое, или делитель, или оба вместе) был объектом не типа int, а типа float. Тогда float division просто гарантировано. Как это сделать:


F = (9.0 / 5) * C + 32       # так
F = (9 / 5.0) * C + 32       # или так


Теперь мы знаем почему неправильно работала первая версия нашей программы и как избежать ошибки. Кстати, мы могли получить ту же ошибку еще на первом уроке, если бы вместо 0.5*g*t**2 написали (1/2)*g*t**2 и долго бы думали почему у нас мячик не хочет подпрыгивать и всегда остается в руке (в y = 0). Корректно работающая версия нашей программы теперь выглядит так:


C = 21
F = (9.0 / 5) * C + 32
print (F)

Старшинство арифметических операций

править

Формулы в Python обычно записываются в том же виде, как это делается в математике и последовательно рассчитываются слева направо. И точно также, чтобы придать какой-то операции большее старшинство, мы заключаем эту операцию в скобки.

Пусть имеется некоторая переменная a. Проиллюстрируем ее карьерный рост в двух примерах

1. 5/9+2*a**4/2: сначала считается 5/9, целочисленное деление дает 0, затем a возводится в четвертую степень и умножается на 2, а затем делится на два. В итоге получаем a4.

2. 5/(9+2)*a**(4/2): сначала считается первая скобка получается 11, 5/11 дает 0. Потом считается вторая скобка, получается 2, и a возводится в квадрат. Потом квадрат a2 умножается на 0 и получается 0.

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

Стандартные математические функции

править

В математических формулах встречаются такие функции как sin, cos, tan, sinh, cosh, exp, log и так далее. В инженерном калькуляторе есть специальные кнопки для таких функций. Также и в написании программы на Python вы можете использовать такие встроенные функции.


Пример: извлечение квадратного корня

править

Вспомним наш первый урок и пример о вертикальном движении мяча. Мы определяли координату y в определенный момент времени. Но представим, нам теперь интересно узнать обратное - через сколько времени окажется мячик в определенной координате yc.

 

 

 

Два значения времени физически объясняются тем, что мяч оказывается в одной и той же точке два раза: пока он летит наверх (t = t1), и обратно — вниз (t = t2 > t1). Чтобы записать полученные выражения для t1 и t2 в программе, у нас должна быть некая функция, извлекающая квадратный корень, также как на калькуляторе для этого есть специальная кнопка. В Python, функция, извлекающая квадратный корень и множество других функции доступны в модуле, который называется math. Чтобы ими пользоваться мы просто должны импортировать этот модуль в программу, написав перед тем местом, где мы будем использовать функции, import math. После этого, чтобы взять квадратный корень от переменной a мы пишем math.sqrt(a). Вот как, например, будет выглядеть наша программа:


# импортируем модуль для работы с математическими функциями
# для быстрых вычислений следует использовать модуль cmath
# в отличие от math - cmath является не скриптом, а исполняемым файлом
import math

v0 = 5
g = 9.81
yc = 0.2

# используем функцию извлечения корня квадратного - math.sqrt
t1 = (v0 - math.sqrt(v0**2 - 2*g*yc))/g
t2 = (v0 + math.sqrt(v0**2 - 2*g*yc))/g
print('At t=%g s and %g s, the height is %g m.' % (t1, t2, yc))

Результат:

At t=0.0417064 s and 0.977662 s, the height is 0.2 m.

import и from ... import

править

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

import math

Чтобы вызвать функцию нужно записать имя_модуля.имя_функции(), например для присваивания переменной 'x' квадратного корня из переменной 'y' записывают:

x = math.sqrt(y)

Люди, часто работающие с математическими функциями, найдут запись math.sqrt(y) менее удобной, чем sqrt(y). К счастью, есть альтернативный способ, позволяющий использовать функцию без префикса имени модуля. Этот альтернативный способ имеет вид "from module import function". Например:

from math import sqrt

Если в вычислениях вам требуется несколько функций, вы можете перечислить их через запятую:

from math import sqrt, exp, log, sin

Иногда даже пишут:

from math import *

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

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


import math as m
# m теперь имя math в программе
v = m.sin(m.pi)

from math import log as ln
v = ln(5)

from math import sin as s, cos as c, log as ln
v = s(x)*c(x) + ln(x)


Как мы помним, в Python все объекты и переменные могут ссылаться на объекты модулей и функций также как на объекты строк и чисел, например:


m = math
ln = m.log
s = m.sin
c = m.cos

Пример: другие математические функции

править

Посмотрим, как можно представить гиперболический синус:

  (2.2)

Для того, чтобы вызвать гиперболический синус, мы можем использовать три способа: 1) самый простой - вызвать встроенную функцию math.sinh( ), 2)использовать два раза функцию math.exp( ), 3) использовать возведение в степень числа e. Сравним в небольшой программе, как это будет выглядеть и получаемые результаты:


from math import sinh, exp, e, pi
x = 2*pi
r1 = sinh(x)
r2 = 0.5*(exp(x) - exp(-x))
r3 = 0.5*(e**x - e**(-x))
print (r1, r2, r3)


На выходе имеем идентичные результаты:

267.744894041 267.744894041 267.744894041 Чисто технически расчёт для r1 по времени самый короткий, и именно его стоит использовать в программах. Кроме всего прочего, не стоит забывать, что обращение к локально объявленным функциям происходит быстрее, чем к функции из другого модуля(s=math.sinh вместо math.sinh).

dir( ) и help( )

править

Для того, чтобы посмотреть все, что вы можете извлечь из какого-либо модуля, достаточно после того как вы его импортировали в интерактивном режиме, написать dir(имя_модуля). Например, это может выглядеть так:


>>> import math
>>> dir(math)
['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh',
'ceil','copysign', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'factorial', 'floor', 'fmod',
'frexp', 'fsum', 'hypot', 'isinf', 'isnan', 'ldexp', 'log', 'log10', 'log1p', 'modf', 'pi', 'pow',
'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']


Имена с двумя подчеркиваниями относятся к системным, и о них мы поговорим несколькими уроками позже. Все остальные — это функции и константы, все, что мы непосредственно используем. Для того, чтобы узнать о назначении каждой функции, достаточно написать help(имя_модуля.имя_функции). Аналогично можно просмотреть и всю информацию о модуле, например help(math).


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

править

Как было показано в предыдущем примере с гиперболическим синусом, мы получили три одинаковых результата, несмотря на форму представления. Попробуем вывести тот же результат с 16 знаками после точки:


print ('%.16f %.16f %.16f' % (r1, r2, r3))


И тогда на выходе:

267.7448940410164369 267.7448940410164369 267.7448940410163232

Теперь r1 и r2 равны, но r3 отличается в последних четырех знаках.

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


print ('%.16f %.16f' % (1 / 49.0 * 49, 1 / 51.0 * 51))


дает нам:

0.9999999999999999 1.0000000000000000

Причина, по которой мы не получаем 1.0 в первом случае заключена в том, что 1/49 не может быть представлена в памяти компьютера корректно. На самом деле и 1/51 неточно записывается в памяти, но эта неточность не видна в конечном результате.

В итоге, ошибки в числах с плавающей точкой могут накапливаться в наших математических вычислениях и результаты будут являться аппроксимацией к абсолютно точным, "природным" ответам. Эти ошибки называются round-off errors, ошибки округления, ошибки точности. В Python есть специальный модуль decimal, позволяющий представлять действительные числа с регулируемой точностью, то есть снижать погрешность до любых требуемых значений. Однако, мы практически не будем пользоваться этим модулем, поскольку большинство используемых математических методов и так приближены, и вносят гораздо большую неточность, чем round-off errors. Более того, в расчётах из области радиоэлектроники, например, три значащих цифры в параметрах какого-либо элемента являются вполне достаточными.

Числа с фиксированной точностью

править

Начиная с версии Python 2.4 появился новый числовой тип: числа с фиксированной точностью представления. Такие числа создаются с помощью вызова из специального модуля decimal. Числа эти похожи на числа с плавающей запятой, но число знаков после запятой здесь фиксировано, отсюда и такое название. То есть так можно хранить число, которое всегда будет иметь, например, два знака после запятой. Кроме того, можно указать, как должны обрабатываться лишние десятичные знаки — просто отсекаться или округляться. Скорость работы с такими числами, конечно, меньше, но они могут быть востребованы в областях, где сохранение точности может быть существенно важным, например, в вопросах финансов.

Разговор о точности работы с обычными числами дополняет и следующий простой пример:


>>> 0.1 + 0.1 + 0.1 - 0.3
5.5511151231257827e-17
>>> print (0.1 + 0.1 + 0.1 - 0.3)
5.55111512313e-17


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


>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')
Decimal('0.0')


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


>>> Decimal('0.1') + Decimal('0.10') + Decimal('0.10') - Decimal('0.3')
Decimal('0.00')


Другие инструменты модуля decimal позволяют задать точность представления и многое другое. Например, объект getcontext из этого модуля позволяет задать точность (число знаков после запятой) и режим округления (вверх, вниз и т. д.):


>>> Decimal(1)/Decimal(7)
Decimal('0.1428571428571428571428571429')
>>> from decimal import getcontext
>>> getcontext().prec = 4
>>> Decimal(1)/Decimal(7)
Decimal('0.1429')


Этот тип чисел используется крайне редко, поэтому в случае, если он вам нужен (что маловероятно для научных расчетов), обращайтесь к документации.

Интерактивное программирование

править

Особенно удобная возможность, предоставляемая Python - это возможность быстро проверить как работают инструкции или выражения в интерактивном режиме. Такие интерактивные оболочки называются shell. Мы уже пользовались ей как частью IDLE. Остановимся сейчас на ней поподробнее.


Interactive Shell

править

Программа, с которой мы работали в первом уроке, может быть напечатана строчка за строчкой с тем же конечным результатом:


>>> v0 = 5
>>> g = 9.81
>>> t = 0.6
>>> y = v0 * t - 0.5 * g * t ** 2
>>> print (y)
1.2342


Мы можем легко задать новое значение переменной, например для v0, и всегда способны проконтролировать, что новое значение присвоено, введя имя переменной напрямую или через инструкцию print::


>>> v0 = 6
>>> v0
6
>>> print (v0)
6


Следующий шаг - обновить выражения, в которых еще содержится старое значение, то есть нашу формулу для y. В таком редакторе как IPython можно найти предыдущее выражение, с помощью кнопок со стрелками. О возможностях этого редактора мы еще поговорим. А пока:


>>> y = v0 * t - 0.5 * g * t ** 2
>>> y
1.8341999999999996
>>> print (y)
1.8342


Причина, по которой мы получили сейчас два разных результата, в том, что, если вы просто пишите y, то вы увидите все знаки после точки, находящиеся в памяти компьютера (16), в то время как инструкция print ограничивает число знаков и, таким образом, дает ответ с достаточной точностью. Впрочем, в некоторых версиях Python автоматически округляет значение переменной.

Преобразование типа

править

Если вы ранее работали с другими языками программирования, то могли заметить, что мы пишем программы не заботясь о задании типов переменных (вернее даже типов объектов, на которые ссылаются переменные). Однако в этом уроке мы встретились с вопросом типов объектов, когда изучали ошибку целочисленного деления, которая научила нас более внимательно относиться к типам объектов. Следующий пример иллюстрирует функцию type( ) и то, как мы можем изменять тип объекта. Для начала, создадим объект типа int, на который будет ссылаться имя С:


>>> C = 21
>>> type(C)
<type 'int'>


Теперь мы преобразуем наш объект С типа int к соответствующему объекту типа float:


>>> C = float(C)     #  type  conversion
>>> type(C)
<type 'float'>
>>> C
21.0


В инструкции C = float(C) мы создали новый объект из оригинального объекта с тем же именем. Теперь имя С ссылается на новый объект. Изначальный объект типа int со значением 21 теперь недоступен, поскольку у него больше нет имени и автоматически удаляется из памяти.

Мы также можем сделать и обратное, то есть конвертировать объект типа float к объекту типа int:


>>> C = 20.9
>>> type(C)
<type 'float'>
>>> D = int(C) #  type  conversion
>>> type(D)
<type 'int'>
>>> D
20  # decimals  are  truncated  :-/


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


>>> round(20.9)
21.0
>>> int(round(20.9))
21


Комплексные числа

править

Пусть x2 = 2. Большинство из нас будут способны найти первый корень  . Более продвинутые в математике заметят, что имеется и второй корень  . Но встретившись с выражением x2 = -2 решение смогут найти лишь немногие люди, знакомые с комплексными числами. Если вы только собираетесь стать ученым, настоятельно советуем познакомиться сейчас с этими замечательными числами перед тем, как изучить дальнейшие примеры, на которых вы сможете проверить свои новые знания.


Комплексная арифметика в Python

править

Python поддерживает расчеты с комплексными числами. Мнимая часть записывается через j, вместо i в математике. Комплексное число 2-3i соответственно будет записано как 2-3j. Ниже показан простой сеанс работы с комплексными числами и примеры простой комплексной арифметики:


>>> u = 2.5 + 3j       # создаем комплексное
>>> v = 2              # а это целое
>>> w = u + v          # комплексное + целое
>>> w
(4.5+3j)

>>> a = -2
>>> b = 0.5
>>> s = a + b*1j       # complex из двух float
>>> s = complex(a, b)  # другой способ
>>> s
(-2+0.5j)
>>> s*w                # complex*complex
(-10.5-3.75j)
>>> s/w                # complex/complex
(-0.25641025641025639+ 0.28205128205128205j)


Кроме того объект типа complex легко может быть разложен на реальную и мнимую части и для него может быть найдено сопряженное (conjugate) число:


>>> s.real
-2.0
>>> s.imag
0.5
>>> s.conjugate()
(-2-0.5j)


Комплексные функции Python

править

Для работы с функциями комплексных переменных следует использовать специальную библиотеку - cmath:


>>> from cmath import sin, sinh
>>> r1 = sin(8j)
>>> r1
1490.4788257895502j
>>> r2 = 1j*sinh(8)
>>> r2
1490.4788257895502j


Чему мы научились

править

Теперь мы знаем об ошибке целочисленного деления и как ее избежать, как можно импортировать (import) модули, узнать что в них имеется (dir) и как использовать (help) их функции. Мы познакомились с концепцией объектов в Python и умеем изменять тип объекта (int, float, string). Кроме привычной математики действительных чисел библиотеки math, мы можем пользоваться и комплексной переменной с помощью модуля cmath. Наконец, теперь в случае, когда мы хотим быстро себя проверить, мы можем воспользоваться интерактивной оболочкой IDLE.


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