Как сделать сравнение чисел в паскале

Добавил пользователь Владимир З.
Обновлено: 04.10.2024

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

Как компьютер хранит вещественные числа?

(Если вы не поймете, что написано в этом разделе, это не очень страшно, но попробуйте понять.)

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

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

Типы данных

Все современные компьютеры умеют работать со следующими тремя типами данных:

  • single — хранит 7-8 значащих цифр, экспоненту до примерно 40, занимает в памяти 4 байта, работает сравнительно быстро;
  • double — хранит 15-16 цифр, экспонента до ~300, занимает 8 байт, работает несколько медленнее;
  • extended, он же long double — хранит 19-20 цифр, экспонента до ~5000, занимает в памяти 10 байт, работает намного медленнее;

Кроме того, в паскале есть еще один тип данных, real. Это тип данных, существующий лишь для обратной совместимости. Дело в том, что когда язык паскаль только создавался, не было общепринятых стандартов хранения вещественных чисел в компьютере. Поэтому в первых версиях паскаля существовал особенный тип real . С тех пор появились стандарты хранения вещественных чисел, поэтому тип real теперь не нужен. Теперь во Free Pascal real — это или single , или double , в зависимости от настроек паскаля. Поэтому не используйте real, а явно пишите тот тип, который хотите.

В питоне нет простой возможности выбрать один из этих трех типов, по умолчанию доступен только тип double, причем в питоне он называется float (!).

С этими типами доступны все привычные уже вам операции: +-*/, abs, sqrt, ввод-вывод через read и writeln (в паскале) или через float(input()), map(float, . ) и print (в питоне). В питоне также работает деление с остатком (// и %).

При этом в ваших программах, а также при вводе вы можете задавать числа как в записи с фиксированной точкой, так и с плавающей, т.е. вы можете писать, например, a:=1.23+2.34e-1; , и при считывании чисел можете вводить значения тоже как в формате 1.23, так и в формате 2.34e-1.

Вообще, на паскале в наших задачах стоит использовать в первую очередь тип extended , поскольку он обеспечивает наилучшую точность, а время работы во многих задачах на вещественные числа не очень важно. Если же время работы важно, или если в вашем языке программирования нет extended, то можно использовать double . Тип single , как правило, не обеспечивает достаточной точности в олимпиадных задачах, поэтому его использовать на олимпиадах практически никогда не надо. Тип real , как уже было сказано, использовать не надо вообще никогда, ни в олимпиадных задачах, ни в реальной жизни.

Про вывод подробнее

Часто в наших задачах вы можете встретить фразу "выведите ответ с точностью до 5 знаков после запятой", или "с пятью верными знаками" и т.п. Такие фразы почти всегда обозначают, что ваш ответ должен содержать 5 верных цифр после запятой, но они не запрещают вам выводить больше цифр. Вы можете вывести хоть 20 цифр — если первые пять из них верные, то ответ будет зачтен. И наоборот, вы можете вывести меньше цифр — если невыведенные цифры — нули, то ответ тоже будет зачтен. Вообще, строго говоря, такая фраза в условии просто обозначает, что ваш ответ должен отличаться от верного не более чем на 1e-5.

Пример: если правильный ответ на задачу — 0.123456789, то вы можете вывести 0.12345, или 0.123459876, или даже 1.2345e-1 (т.к. это то же самое, что и 0.12345). А если правильный ответ — 0.10000023, то вы можете вывести 0.10000, 0.10000987 или даже просто 0.1 или 1e-001 (т.к. это то же самое, что и 0.10000).

В частности, это обозначает, что вы можете пользоваться стандартными функциями вывода (writeln и print) без каких-либо особых ухищрений; не надо округлять число, не надо форматировать вывод и т.д.

Вот если в задаче строго сказано "вывести ровно с 5 знаками после запятой", то это другое дело. Но на приличных олимпиадах такое бывает очень редко.

Полезные функции

Что в паскале, что в питоне есть несколько функций, которые вам будут полезны при работе с вещественными числами. Для ряда из этих функций надо: в паскале — в самом начале программы, до var , написать uses math; ; в питоне — в самом начале программы написать from math import * . Кроме того, имейте в виду, что с этими функциями также могут возникать проблему погрешностей (см. ниже).

  • floor ("пол") — округляет число вниз, т.е. определяет ближайшее целое число, которое меньше или равно данного вещественного. Например, floor(2.4)=2 , floor(2)=2 , floor(-2.4)=-3 , и floor(2.8)=2 .
  • ceil ("потолок") — округляет число вверх, т.е. определяет ближайшее целое число, которое больше или равно данного вещественного. Например, ceil(2.4)=3 , ceil(2)=2 , ceil(-2.4)=-2 , и ceil(2.8)=3 .
  • trunc — округляет число в сторону нуля. Например, trunc(2.4)=2 , trunc(2)=2 , trunc(-2.4)=-2 , и trunc(2.8)=2 .
  • round — округляет число к ближайшему целому числу ("по школьным правилам", за исключением ситуации, когда дробная часть числа строго равна 0.5 — тогда в зависимоти от числа может быть округление то в одну, то в другую сторону). Например, round(2.4)=2 , round(2)=2 , round(-2.4)=-2 , и round(2.8)=3 .
  • frac — (только в паскале; в питоне похожим образом работает взятие остатка по модулю 1: x % 1 ) возвращает дробную часть числа. Более точно, frac(x)=x-trunc(x) . Например, frac(2.4)=0.4 , frac(2)=0 , frac(-2.4)=-0.4 , и frac(2.8)=0.8 .

Пример программы, используйющей эти функции:

Погрешности

Два правила работы с вещественными числами

Сначала напишу два главных правила работы с вещественными числами:

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

Правило второе: если уж работаете, то используйте eps . При любых * сравнениях вещественных чисел надо использовать eps .

Ниже я разъясняю оба этих правила.

Необходимость использования eps

Как уже говорилось выше, компьютер не может хранить все цифры числа, он хранит только несколько первых значащих цифр. Поэтому, если, например, разделить 1 на 3, то получится не 0.33333. (бесконечно много цифр), а, например, 0.33333333 (только несколько первых цифр). Если потом умножить результат обратно на 3, то получится не ровно 1, а 0.99999999. (Аналогичный эффект есть на простых калькуляторах; на продвинутых калькуляторах он тоже есть, но проявляется сложнее.)

Поэтому если вы напишите, например, следующий код:

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

то на экран будет выведено Fail .

На самом деле все еще хуже: компьютер работает в двоичной системе счисления, поэтому даже числа, в которых в десятичной системе счисления конечное число цифр, в компьютере могут представляться неточно. Поэтому, например, сравнение if 0.3+0.6=0.9 тоже не сработает: если сложить 0.3 и 0.6, то получится не ровно 0.9, а слегка отличающее число (0.899999 или 0.900001 и т.п.)

(Вдумчивый читатель-паскалист может написать программу writeln(0.3+0.6); и обнаружить, что она выводит на экран 9.0000000000000000E-0001 , т.е. вроде точно 0.9, и никакими ухищрениями не получается заставить ее вывести что-то, отличающееся от 0.9. Причина опять в том, что компьютер работает в двоичной системе счисления: реально получается не совсем 0.9, но при переводе результата в десятичную систему для вывода на экран эта очень маленькая ошибка отбрасывается. Тем не менее, ее можно увидеть, если написать writeln(0.3+0.6-0.9); — получится вовсе не ноль, а 5.4210108624275222E-0020 , т.е. видно, что сумма 0.3+0.6 действительно не равна 0.9. Почему получился именно такой результат, и почему на самом деле цифрам этого результата нельзя верить — это тема отдельного разговора. Отмечу еще, что все это у меня получилось на Kubuntu Linux, на других системах или при других настройках результат может быть другим; может даже оказаться, что 0.3+0.6 точно равно 0.9, но ошибки будут на других парах чисел.)

(На питоне все тут проявляется еще ярче: print(0.3+0.6) выводит у меня 0.8999999999999999.)

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

Про то, как выбирать это eps , обсудим ниже, пока будем считать, что мы взяли eps=1e-6 . Тогда в начале программы (обычно принято до var ) пишем

Если нам надо написать условие if x>y , то его тоже надо переписать, ведь нам важно (подробнее см. ниже), чтобы при $x=y$ условие не выполнилось! Поэтому переписать его надо так: if x>y+eps . Аналогичные соображения действуют для любых других сравнений вещественных чисел.

Итак, именно поэтому

Правило второе: если уж работаете, то используйте eps . При любых * сравнениях вещественных чисел надо использовать eps .

(Первое правило будет дальше :) )

* за исключением случаев, когда вам не важно, что произойдет в случае точного равенства, см. ниже.

Выбор eps

Выбор eps — это весьма нетривиальная задача, и далеко не всегда она вообще имеет правильное решение. Нам надо выбрать такое eps , чтобы, если два числа должны быть равны (но отличаются из-за погрешностей), то их разность точно была меньше eps , а если они не равны, то точно была больше eps . Ясно, что в общем случае эта задача не имеет решения: может быть так, что в одной программе будут два числа, которые должны быть равны, но отличаются, например, на 0.1 из-за погрешности, и два числа, которые действительно различны, но отличаются только на 0.01.

В некоторых задачах такое eps можно вычислить строго. Например, пусть задача: даны три числа $a$, $b$ и $c$, каждое не больше 1000, и каждое имеет не более 3 цифр после десятичной запятой. Надо проверить, правда ли, что $a+b=c$. Из изложенного выше понятно, что тупое решение if a+b=c не сработает: может оказаться, что должно быть $a+b=c$, но из-за погрешностей получится, что $a+b<> c$. Поэтому надо проверять if abs(a+b-c) , но какое брать eps ?

Подумаем: пусть действительно $a+b=c$. Какой может быть разница $a+b-c$ с учетом погрешностей? Мы знаем, что $a$, $b$ и $c$ не превосходят 1000. Это значит, что если мы, например, используем тип данных double , в котором хранятся 15-16 верных цифр, то погрешности будут примерно в 15-16-й значащей цифре. Для максимальных возможных значений чисел (т.е. для 1000) погрешности будут порядка 1e-12 или меньше, т.е. можно рассчитывать, что если $a+b=c$, то в программе $|a+b-c|$ будет порядка 1e-12 или меньше.

С другой стороны, пусть $a+b<> c$. Какой тогда может быть разница $|a+b-c|$? По условию, все числа имеют не более трех цифр после запятой, поэтом понятно, что эта разница будет равна 0.001 или больше.

Поэтому можно, например, взять eps=1e-5 . С одной стороны, если на самом деле $a+b=c$, то в программе $|a+b-c|$ точно получится намного меньше eps , а с другой стороны, если на самом деле $a+b<> c$, то $|a+b-c|$ будет точно намного больше eps . Итак, в этом примере мы смогли точно вычислить подходящее eps (на самом деле, конечно, вариантов много — подошло бы любое число, которое существенно меньше 1e-3 и существенно больше 1e-12).

Но бывают задачи, где так просто вычислить подходящее eps не получается. На самом деле таких задач большинство — как только вычисления у вас становятся сложнее чем сложить два числа, за погрешностями уже становится сложно уследить. Можно, конечно, применять какие-нибудь сложные техники, но обычно принято просто брать какое-нибудь eps порядка 1e-6..1e-10.

Но в итоге вы не можете быть уверены, что вы выбрали правильное eps . Если ваша программа не работает — это может быть потому, что у вас ошибка в программе, а может быть просто потому, что вы выбрали неверный eps . Бывает так, что достаточно поменять eps — и программа пройдет все тесты. Конечно, это не очень хорошо, но ничего не поделаешь.

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

Собственно, из этого и следует

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

Пример: пусть у вас в программе есть четыре целых (integer) положительных числа $a$, $b$, $c$ и $d$, и вам надо сравнить две дроби: $a/b$ и $c/d$. Вы могли бы написать if a/b>c/d , но это плохо: в результате деления получаются вещественные числа, и вы сравниваете два вещественных числа со всеми вытекающими последствиями. (Конкретно в этом случае, возможно, ничего плохого не случится, но в чуть более сложных случаях уже может случиться, да и в этом случае возможно и случится, я не проверял.) А именно, может оказаться, например, что $a/b=c/d$ на самом деле, но из-за погрешностей в программе получится $a/b>c/d$ и if выполнится. Вы можете написать eps , думать, каким его выбрать. но можно проще. Можно просто понять, что при положительных (по условию) числах это сравнение эквивалентно условию if a*d>c*b . Здесь все вычисления идут только в целых числах, поэтому это условие работает всегда (ну, если нет переполнения, конечно), и не требует никаких eps (да еще и работает быстрее, чем предыдущий вариант). Его написать не сложнее, чем вариант с делением, поэтому всегда следует так и писать. Всегда, когда в решении вы переходите от целых к вещественным числам, задумайтесь на секунду: а нельзя ли обойтись без вещественных чисел? Если да, то постарайтесь так и поступить — и никаких проблем с точностью у вас не возникнет.

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

Рассмотрим следующие код (x, y, max -- вещественные числа):

Здесь мы сравниваем два вещественных числа, чтобы найти максимум из них. Казалось бы, в соответствии со сказанным выше, в сравнении нужен eps . но нет! Ведь если два числа на самом деле равны, то нам все равно, в какую из веток if мы попадем — обе ветки будут верными! Поэтому eps тут не нужен.

Так иногда бывает — когда вам все равно, в какую ветку if'а вы попадете, если два сравниваемых числа на самом деле равны между собой. В таком случае eps использовать не надо. Но каждый раз тщательно думайте: а правда ли все равно? Всегда лучше перестраховаться и написать eps (выше с eps тоже все работало бы), за исключением совсем уж простых случаев типа приведенного выше вычисления максимума.

Еще пример: считаем сумму положительных элементов массива

Здесь, опять-таки, если должно быть $x_i=0$, то не важно, добавим мы его в сумму или нет: сумма от добавления нуля не изменится. Поэтому eps писать не надо (но ничего страшного не будет, если и написать).

Еще пример, где уже eps необходим: определим, какое из двух чисел больше:

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

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

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

Язык Турбо Паскаль является классическим языком программирования, широко применяемым в инженерных расчётах. Его изучение позволяет сформировать у студентов особый вид мышления – алгоритмический. Студентам, успешно овладевшим этим языком, не составит особого труда в будущей своей трудовой деятельности применять свои знания и составлять программы не только на языке Паскаль, но и на других языках программирования. Особенно важным является то, что знание языка Паскаль нужно для составления программ в среде Windows при помощи прикладного пакета Delphi, всё более популярного в последнее время.

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

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

1. ОСНОВНЫЕ ПОНЯТИЯ СИСТЕМЫ ПРОГРАММИРОВАНИЯ ТУРБО ПАСКАЛЬ

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

  • Как ввести информацию в память (ввод).
  • Как хранить информацию в памяти (данные).
  • Как указать правильные команды для обработки данных (операции).
  • Как передать данные из программы пользователю (вывод).

Необходимо также уметь упорядочивать команды так, чтобы:

  • некоторые из них выполнялись только в случае, если соблюдается некоторое условие или ряд условий (условное выполнение);
  • другие выполнялись повторно некоторое число раз (циклы);
  • третьи выделялись в отдельные части, которые могут быть неоднократно выполнены в разных местах программы (подпрограммы).

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

Этот список не является полным, однако, он содержит те элементы, которые присущи обычно всем программам. Многие языки программирования имеют еще и дополнительные средства, в том числе и Паскаль.

Основные файлы пакета Турбо Паскаль:

  • Turbo.exe – интегрированная среда программирования;
  • Turbo.hlp – файл, содержащий данные для оперативной подсказки;
  • Turbo.tp – файл конфигурационной системы;
  • Turbo.tpl – библиотека стандартных модулей Турбо Паскаля.

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

В рабочем окне осуществляется набор текста программы, запуск же происходит следующим образом: выход в меню, выбор пункта Run – Run.

Для того чтобы сохранить программу, необходимо: выйти в меню, выбрать File – Save (Save as …), в появившемся окне ввести имя файла и нажать клавишу Enter.

Выход из системы программирования: выход в меню, пункт File – Exit.

1.1. Алфавит и словарь языка Паскаль

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

Алфавит Паскаля составляют:

  • прописные и строчные буквы латинского алфавита:

A, B, C…Y, Z, a, b, c…y, z;

  • десятичные цифры: 0, 1, 2…9;
  • специальные символы:

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

Зарезервированные слова (см. табл. 1.1) имеют фиксированное написание и навсегда определенный смысл. Они не могут изменяться программистом, и их нельзя использовать в качестве имен для обозначения величин.

Идентификатор – имя (identification – установление соответствия объекта некоторому набору символов). Для обозначения определенных разработчиками языка функций, констант и т. д. служат стандартные идентификаторы, например, Sqr, Sqrt и т. д. В этом примере Sqr вызывает функцию, которая возводит в квадрат данное число, а Sqrt – корень квадратный из заданного числа. Пользователь может переопределить любой стандартный идентификатор, но чаще всего это приводит к ошибкам, поэтому на практике их используют без изменения. Идентификаторы пользователя – это те имена, которые дает сам программист. При записи программ нужно соблюдать общие правила написания идентификаторов:

Читайте также: