К книге
Рассказы о математике с примерами на языках Python и CСтраница 13
76%
Страница 13
13

Введем команды в языке Python:

a = 0.8

print a

Получим 0.8 - команда print достаточно “умна” чтобы округлить результат. Но сделаем вывод с большим числом знаком после запятой:

print("{0:.20f}".format(a))

Получим 0.8000000000000004441, что как говорится, и требовалось доказать.

Что с этим делать? Ну в общем-то ничего. Реальная ошибка в точности достаточно мала, чтобы это было проблемой. Но при желании можно использовать типы данных большей точности, правда ценой увеличения объема памяти и замедления скорости вычислений. Например, в С++ можно выбрать между типами float и double, второй имеет большую точность, но занимает больше памяти.

В целом, выполняя вычисления на компьютере, следует помнить что:

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

2) Вещественные числа нельзя сравнивать оператором ==, как было показано выше, результат будет неверен. К примеру, следующий код не будет работать:

a = 0.5

b = 0.4

if a-b == 0.1:

print "0.1!"

Вместо этого достаточно убедиться, что результат мал:

if abs(a-b-0.1) < 1e-8:

print "0.1!"

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

4) Если все-таки желательно повысить точность, в Python можно использовать тип Decimal. Он хранит, по возможности, все значимые биты числа.

from decimal import Decimal

a = Decimal("0.5")

b = Decimal("0.4")

print a-b == Decimal("0.1")

Данная программа действительно вернет True. Но достигается это за счет значительного (до 10 раз или даже выше) замедления скорости расчета, да и не для всех вычислений это работает, например для уже упомянутого числа 1/3 точное значение сохранить невозможно. Но для таких случаев можно хранить числа в виде дробей, сохраняя и числитель и знаменатель. В общем, единого решения тут нет, каждый случай нужно рассматривать отдельно.

Но опять же, стоит повторить, что в большинстве случаев можно не беспокоиться - точности стандартных типов данных вполне достаточно. К примеру, если сохранить в переменной расстояние от Москвы до Владивостока, равное 9136.9км, то ошибка округления составит 10-11м, что лишь немногим больше размера одного атома. Скорее всего, такой ошибкой можно пренебречь.

26. Математика и календарь

Еще одна большая тема, где соединяются математика, астрономия и реальная жизнь - это наш обычный календарь. Например, часто можно услышать что високосный год считается “тяжелым”. Может ли такое быть?

Для начала, вспомним что Земля вращается по эллиптической орбите вокруг Солнца (что обеспечивает смену времен года), и в то же время, вокруг своей оси (что обеспечивает чередование дней). Второй период всем известен, и составляет 24 часа, а вот первый период известен уже не всем, и составляет 365.242187 суток. А в году, как известно, 365 дней.

На картинке это выглядит примерно так:

Первую неточность в календаре знали уже в древнем Риме, еще во времена Юлия Цезаря. Каждые 4 года добавлялся один день, чтобы скомпенсировать те самые недостающие 1/4 суток. В какой именно год добавлять - несложно догадаться, что принципиальной разницы тут нет, главное чтобы число лет было кратно 4. Можно даже добавлять по 2 дня каждые 8 лет, принципиально это ни на что не повлияет. Может ли високосный год быть “тяжелее” другого, соседнего? Очевидно что нет, т.к. все равно, какой именно год считать високосным - это лишь общепринятая условность, и на орбиту Земли выбор года никак не влияет.

Но если с орбитой все более-менее понятно, то с календарем не все оказалось так просто. Внимательный читатель наверное заметил, что разница в 0.242187 суток вовсе не кратна ¼. И совершенно справедливо. В юлианском календаре год оказывался длиннее астрономического на 11.2 минуты. Казалось бы немного, но за 128 лет накапливался целый день разницы. Впрочем, в древние времена ритм жизни был не столь быстр как сейчас, и ошибку заметили … только спустя 1.5 тысячи лет, когда “набежало” уже более 10 дней разницы. Это стало заметно не только астрономам - такой сдвиг календаря влиял и на даты посевов, и на празднование дней равноденствия. Тогда, в 1582 году папа Григорий XIII ввел новый календарь, названный теперь “григорианским” - в нем убрано часть високосных годов. Современное правило звучит так: год високосный, если он делится на четыре без остатка, но если он делится на 100 без остатка, это не високосный год, но если он делится без остатка на 400, это високосный год. Таким образом, 1996, 2000, 2004 - високосные годы, но 1900й год високосным не был. Правило выглядит запутанным, но зная, что день разницы накапливается за 128 лет, нетрудно понять почему именно так. Каждый 100й год 1 день “убирается” чтобы скомпенсировать разницу, набегающую за 100 лет, но каждый 400й год делается еще одно исключение, чтобы скомпенсировать оставшиеся 28 лет.

Кстати, в России увы, исчисление европейской точностью не отличалось. Юлианский календарь использовался в России аж до 1918 года, суммарная “набежавшая” разница составила 13 суток, что и составляет разницу между “старым” и “новым” стилем (лишь Турция и Египет приняли новый календарь еще позже, в 1927 году).

Так что теперь мы знаем причину, почему все отмечают “старый новый год” именно 13 января - это тот самый промежуток, который накопился за почти 2000 лет из “всего лишь” 11 минутной разницы между реальным и календарным годами.

Является ли григорианский календарь абсолютно точным? Нет, но дополнительных високосных дней никто уже не вводит, это было бы слишком запутанно. Вместо этого, по необходимости, просто чуть корректируют текущее время, например 1 июня 2015 года, по международному соглашению, день длился на 1 секунду дольше - секунда была добавлена для компенсации разницы календарного и астрономического времени. С 1972 года такие поправки вводились 25 раз.

27. Можно ли выиграть в азартные игры?

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

Игральные кости

Самый наверное, интуитивно простой и понятный вариант - есть кубик с метками от 1 до 6, и вероятность выпадения того или иного числа равна 1/6.

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

Каждый ход игры состоит из бросания двух кубиков, набранные очки суммируются. Как написано в статье http://www.vokrugsveta.ru/article/215452/ “Правила игры незамысловаты: игрок кидает две кости, и, если сумма очков на них равна 7 или 11, он выигрывает, если 2, 3 или 12 — проигрывает. Когда на кубиках выпадает другая сумма, шутер бросает их до выигрышной или проигрышной комбинаций”.

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

import random

def shoot():

return random.randint(1,6) + random.randint(1,6)

Напишем функцию симуляции одного хода по вышеописанным правилам.

def move():

while True:

val = shoot()

print("Dice roll:", val)

if val == 7 or val == 11:

return True

if val == 2 or val == 3 or val == 12:

return False

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

money_total = 100

win = 0

loose = 0

for p in range(100):

bet = 1

step = move()

if step is True:

money_total += bet

win += 1

else:

money_total -= bet

loose += 1

print("Win", win, "Loose", loose, "Money", money_total)

Запускаем симуляцию 100 игр и удивляемся результату, игрок выиграл, причем с заметным отрывом побед от поражений: Win 63, Loose 37, Money 126.

Увеличиваем число игр до 1000 и запускаем еще раз, игрок опять выиграл: Win 680, Loose 320, Money 460.

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

Интуитивно кажется, что при бросании кубика вероятность выпадения любой грани равновероятна. И это действительно так, но в случае одного кубика. Если кубиков два, то все становится гораздо сложнее. К примеру, число 7 может выпасть как 3+4, 2+5, 1+6, а вот число 12 может выпасть только в виде комбинации 6+6.

Построим в Jupyter notebook график выпадения сумм от 1 до 12 для 100 бросков:

from matplotlib import pyplot as plt

%matplotlib inline

y = [ shoot() for v in range(100) ]

plt.hist(y)

Предположение подтвердилось, и суммы из центра действительно выпадают чаще. Таким образом, числа 7 и 11 действительно выпадают чаще чем 2,3 или 12. И вероятность получить выигрышную комбинацию “7 или 11” действительно выше.

Как такое может быть? Увы, ответ прост - автор процитированной выше статьи просто не разобрался досконально в правилах игры. Текст “правила игры незамысловаты: игрок кидает две кости, и, если сумма очков на них равна 7 или 11, он выигрывает, если 2, 3 или 12 — проигрывает” весьма далек от правды, и правила совсем не так незамысловаты как кажутся.

Реальные правила для ставки на pass line оказались несколько сложнее (есть и другие виды ставок, желающие могут разобраться самостоятельно).

Ход-1: Делается бросок. Если выпадает 7 или 11, игрок выигрывает, если 2, 3 или 12, игрок проигрывает. Если выпадает другое число, оно запоминается под названием point.

Ход-2: Делается бросок. Если выпадает 7, то игрок проиграл. Если выпадает point то игрок выиграл. Если выпадают другие числа, ход повторяется (в это время игроки могут также делать ставки на другие числа).

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

def move():

point = 0

while True:

val = shoot()

if point == 0:

# First move

if val == 7 or val == 11:

return True

if val == 2 or val == 3 or val == 12:

return False

point = val

else:

# 2..N move

if val == 7:

return False

if val == point:

return True

Результат теперь более похож на правду: за 100 игр игрок выиграл 43 раза, проиграл 57 раз, баланс в конце игры составил 86у.е. от изначальных 100. Интересно и то, что число выигрышей оказалось довольно-таки велико, и составляет лишь немногим менее 50%. Это грамотная стратегия с точки зрения казино - она позволяет поддерживать интерес участника к игре (все время проигрывать было бы неинтересно), но в то же время баланс прибыли казино остается положительным, а баланс денег игрока - соответственно, отрицательным.

Посмотрим подробнее, что получается для симуляции в 100 игр.

- Шанс выиграть на первом шаге выпал примерно в 20 случаях.

- Шанс проиграть сразу на первом шаге выпал в 15 случаях

- В остальных 65 случаях игра продолжается, и тут все хитро: выбор происходит из двух чисел, 7 и point, но как было видно из графика выше, вероятность выпадения “проигрышной” цифры 7 максимальна, что в общем-то и требовалось доказать.

Интересно заметить, что шанс выигрыша в 45% - довольно-таки высок. Так можно ли выиграть? В краткосрочном периоде, да, например, в другой симуляции игроку “повезло”, и он за 100 игр увеличил свой виртуальный капитал со 100 до 112уе.

Но уже следующая симуляция показала отрицательный баланс: игрок уменьшил свое состояние со 100 до 88уе, потеряв, кстати, те же самые 12уе, “выигранные” в предыдущий раз.

Предыдущая главаГлава 13 из 17Следующая глава