§ 8.4. Действительные типы. Программирование линейных алгоритмов. Функции библиотеки cmath

Школьный курс python
Содержание

Действительные типы

Действительные (вещественные) типы представлены типами одинарной, двойной и расширенной точности: float, double и long double соответственно:

-----------------------------------------------------------------------------
Types       | Size | MIN                         | MAX
-----------------------------------------------------------------------------
float       |    4 |               1.1754944e-38 |              3.4028235e+38  
double      |    8 |      2.225073858507201e-308 |     1.797693134862316e+308  
long double |   12 | 3.3621031431120935063e-4932 | 1.189731495357231765e+4932  
-----------------------------------------------------------------------------

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

  • Действительное число с фиксированной точкой. Например: 0.12, .5, 10.
  • Действительное число в экспоненциальном (научном) формате с разделителем мантиссы и порядка – e|E. Например: 3E8, 6.6720e-11, 2.E+20
  • Действительное число в экспоненциальном формате с разделителем мантиссы в виде шестнадцатеричного числа и порядка в виде десятичного числа – p|P. Например: 0xC.68p+2, 0x1.P-126, 0xa.bp10
Обратите внимание на то, что для этих форм нулевую дробную или целую часть числа (для экспоненциальной формы – в мантиссе) можно опустить (но не обе одновременно!). Также можно опустить и саму точку, оставив лишь целую часть числа. Но для улучшения представления кода точку всё же опускать не следует. Для представления отрицательной вещественной константы используется операция “унарный минус”.
Типом по умолчанию для вещественных типов является тип double. Для явного указания другого действительного типа в выражениях следует использовать суффиксы (для floatf или F, для long doublel или L). Например: 12.3F, .1E4f, .0005l.
Представление вещественных чисел существенно отличается от представления целых чисел. Вещественное число представлено в памяти в виде двух частей: мантиссы и порядка. Мантисса – это число >= 1, но < 2, поэтому старшая цифра мантиссы, равная 1, никогда не хранится. Например, для хранения числа типа float выделяется 32 бита, из них: 1 разряд выделяется для хранения знака мантиссы, 8 разрядов для хранения порядка числа и 23 разряда для хранения мантиссы.
С действительными типами можно выполнять только 4 арифметических операции: *, /, + и -. Если в выражении участвуют, наряду с действительными типами, целые типы, то выражение будет автоматически приведено к действительному типу (с возможной потерей точности и ширине, соответствующей наиболее широкому типу из тех действительных типов, которые присутствуют в данном выражении). Например, результатом вычисления этого выражения

300000ull * 0.17856f + 12.5

будет тип double, так как последний операнд соответствует более широкому типу.
Решим задачу с действительным типом.
Задача 1. Даны две стороны прямоугольника a и b. Определить площадь прямоугольника, длину его диагонали, а также площадь треугольника образованного сторонами прямоугольника и его диагональю.

Программа 8.4.1
#include <iostream>
#include <cmath> // для вычисления диагонали
using namespace std;
	 	 
int main() {
	double a, b;
	cout << "a = "; cin >> a;
	cout << "b = "; cin >> b;
	double S = a * b;
	double d = hypot(a, b);
	double St = S / 2;
	cout.precision(3); // определяем количество дробных знаков
	cout << "Площадь прямоугольника: " << S << "\n"
		 << "Длина диагонали: " << d << "\n"
		 << "Площадь треугольника образованного\n"
		    "a, b и d: " << St << endl;
 return 0;	 	 
}	 

Вывод

a = 2.3
b = 6.7
Площадь прямоугольника: 15.4
Длина диагонали: 7.08
Площадь треугольника образованного
a, b и d: 7.7

По умолчанию, все действительные типы выводятся в общем количестве 6 десятичных разрядов. Класс iostream содержит функцию precision(n), которая устанавливает количество выводимых дробных знаков действительных чисел (n-1). Эта функция не округляет число! Максимально возможная точность будет сохранена. Помимо этой функции вы можете использовать аналогичный манипулятор setprecision(n) (показано в программе 8.4.3).
Для определения длины диагонали в программе 8.4.1 мы воспользовались функцией hypot() математической библиотеки cmath, подробно о которой мы будем говорить ниже.

Точность вычислений

Размеры мантиссы чисел действительных типов float, double и long double, в количестве десятичных разрядов, соответственно равны: 7, 15 и 19. Это предел точности этих типов, сверх которой будет накапливаться ошибка. Отсюда следует, что наибольшей точностью вычислений обладает тип long double.
Науке известны как слишком большие величины, так и слишком маленькие: объем Метагалактики  3,5\cdot10^{30} m^3 , радиус протона  0,8768\cdot10^{-15} m , масса электрона  9.10938291\cdot10^{-31} kg , масса Солнца 1.98892\cdot10^{30} kg , а масса Галактики - 6\cdot10^{42} kg!
Производя вычисления с подобными действительными числами мы будем терять точность вычислений, так как в мантиссе действительных типов представлено лишь небольшое число десятичных разрядов по сравнению с их порядком.
Если необходимо соблюсти точность вычислений, то не нужно использовать для этих целей любой из вещественных типов! Необходимо выбирать подходящий по размеру целый тип. Если же размер числа больше любого целого типа, то прибегать к программированию больших чисел с помощью алгоритмов длинной арифметики с массивами.
Заметим, что для точных вычислений с большими числами (как с целыми, так и с действительными) для языка С++ существует свободная библиотека Boost.Multiprecision. Эта библиотека входит в набор известных библиотек написанных для языка C++ в рамках проекта Boost (а разработчики Boost, как известно, активно сотрудничают с комитетом по разработке C++), поэтому она хорошо интегрирована в систему и может работать с фундаментальными типами, подчиняясь, при этом, всем правилам языка. Для вычислений с повышенной точностью Boost.Multiprecision предоставляет абстрактный тип данных называемый cpp_dec_float. Число точных десятичных знаков фиксируется во время компиляции с помощью параметра шаблона.

Программа 8.4.2
// Включаем константы multiprecision
#include <boost/math/constants/constants.hpp>
// Включаем работу с типом cpp_dec_float
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>
// Для лаконичности
using namespace boost::multiprecision; 
using namespace boost::math::constants;      
using namespace std;
           
int main() {
    // Определяем переменную типа cpp_dec_float_50
    // которая может хранить 50 десятичных разрядов 
    // и присваиваем ей значение 1 / 7
    cpp_dec_float_50 seventh = cpp_dec_float_50(1) / 7;      
    cout << "seventh = " << seventh << endl;
    // seventh = 0.142857 <- Выводит по умолчанию 
    // только 6 десятичных разрядов
    // Устанавливаем кол-во дробных разрядов
    cout.precision(50);
    // Выводим макс. количество десятичных разрядов
    cout << "seventh = " << seventh << endl;
    // Вычислим с очень большой точностью 2 * pi:
    cpp_dec_float_50 circ = pi<cpp_dec_float_50>() * 2;
    cout << "2 * pi  = " << circ << endl;
 return 0;       
} 

Вывод

seventh = 0.142857
seventh = 0.14285714285714285714285714285714285714285714285714
2 * pi  = 6.2831853071795864769252867665590057683943387987502	 

В этой программе для вас пока много непонятных вещей. По мере "погружения" в C++ вы поймете их назначение.

Числовая библиотека std

В стандартной библиотеке C++ имеется числовой раздел с математическими библиотеками в которых реализованы различные математические функции. В таблице 1 перечислены некоторые заголовки этой библиотеки и их краткие описания.

Таблица 1. Numerics library
Заголовок Описание
cmath Общие математические функции
random Генераторы случайных чисел и распределения
numeric Числовые операции над значениями в диапазонах
ratio Рациональная арифметика во время компиляции
numbers Математические константы
Функции библиотеки сmath

Математические функции не входят в ядро языка, а вынесены в отдельную библиотеку cmath. Чтобы эти функции стали доступными в программе необходимо включить одноименный заголовочный файл (как это сделано в программе 8.4.1). Некоторые часто используемые функции библиотеки cmath перечислены в методичке (Таблица 10) и таблице ниже.

Таблица 2. Некоторые математические функции числовой библиотеки C++
Функция Описание
cmath
fabs Модуль действительного числа
fmod Остаток от делания действительных чисел
fmax/fmim Максимальное/минимальное из двух действительных чисел
exp ex
exp2 2x
log Натуральный логарифм
log10 Десятичный логарифм
log2 Логарифм по основанию 2
pow ab
sqrt Квадратный корень
hypot Гиротенуза
sin Синус
cos Косинус
tan Тангенс
asin Арксинус
acos Арккосинус
atan Арктангенс
atan2 Угол phi в полярных координатах
ceil Округление до ближайшего целого не меньше заданного значения
floor Округление до ближайшего целого не больше заданного значения
tranc Отсечение дробной части
round Округление по правилам математики
numeric
gcd Наибольший общий делитель двух целых чисел
lcm Наименьшее общее кратное двух целых чисел

Возвращаемый тип этих функций соответствует типу аргументов. Если какой-то аргумент имеет целый тип, то функции возвращают тип double.
Помимо функций, cmath содержит несколько важных математических констант:

Таблица 3. Некоторые константы cmath
Имя Описание
M_E e
M_PI pi
M_LN2 ln(2)
M_LN10 ln(10)
M_LOG2E log2(e)
M_LOG10E log10(e)

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

Программирование линейных алгоритмов

Программа, в которой нет инструкций управления (ветвления и циклы) и она составляется в одном модуле (файле), реализует линейный алгоритм. Линейным он называется потому, что все инструкции программы от начала и до конца выполняются последовательно. В большинстве своем, такие программы являются частью больших и более сложных программ. Разработка программ такого типа позволяет получить навыки работы с различными типами данных, умение создавать дружественные интерфейсы программ и составлять арифметические выражения на языке программирования C++. Условно, такую программу можно разделить на несколько частей:

  • Раздел директив, глобальных деклараций и объявлений
  • Реализация диалога для ввода исходных данных
  • Раздел вычислений
  • Форматированный вывод результатов
Строго говоря, в C++ нет каких-либо обязательных разделов (в отличие от языка программирования Pascal). Но есть определенные программные блоки, например, объявления и определения функций, структур и классов, расположение которых, в программном файле, строго регламентировано и подчиняется синтаксическим правилам. В программе 8.4.3 используется только одна функция (главная). Включения заголовочных файлов должны быть произведены до вхождения в эту функцию, поскольку реализация этой функции зависит от этих включений.
Рассмотрим пример линейной программы на примере следующей задачи.
Задача 2. Даны стороны треугольника a, b и c. По формуле Герона определить его площадь, длину высоты ha, а также радиусы вписанной и описанной окружности - r и R. Для рассчетов используйте следующие формулы:
                
Конкретно в этой задаче, даются классические формулы в математической нотации. В других задачах, прежде чем составлять алгоритм, требуется произвести анализ задачи и построить математическую модель. Создание несложных программ - это многоступенчатый процесс, который повторяет все этапы каскадной модели разработки программного обеспечения. Методологии разработки ПО, их достоинства и недостатки будут обсуждаться в следующем учебном году. Здесь же перечислим основные этапы каскадной модели:
  • Постановка задачи
  • Проектирование
  • Конструирование
  • Реализация
  • Тестирование и отладка
  • Инсталляция
Постановка задачи - это то условие, которое нами было получено. В более широком понимании - здесь выдвигаются требования заказчика, обсуждаются общие подходы к решению задачи.
На этапе проектирования создается математическая модель. Выясняется, какие требуются исходные данные, чтобы модель была работоспособной, подбирается необходимый математический аппарат, чтобы задача решалась наиболее эффективно. В задачах школьного курса на это "намекает" условие самой задачи, которое нужно очень внимательно прочитать. Часто сразу становится ясным как решать данную задачу - в уме зреет алгоритм её решения.
Формулы в классической математической нотации должны быть представлены в линейной нотации, в виде строки. Для этого применяются арифметические операции языка и круглые скобки.
На этапе конструирования создается алгоритм решения задачи на основе полученной на предыдущем этапе математической модели. Для наглядного представления могут создаваться блок-схемы всей задачи или отдельных её подзадач. Существуют и иные способы представления алгоритма, например, с помощью унифицированного языка моделирования (UML). Подбираются библиотеки, кодируются отдельные модули. Блок-схема нашей программы может выглядеть так, как показано на рис. ниже.
Реализация - это основной этап создания программы. На этом этапе составляется программа на базовом языке программирования и производится компиляция исходного кода программы в исполняемый машинный код. Наша программа на языке программирования С++ может быть составлена следующим образом:

Программа 8.4.3
//============================================//
//                     1                      //
//============================================//
#include <iostream>
#include <iomanip>
#include <cmath> 
using namespace std;
//============================================//
//                     2                      //
//============================================//
int main() {
    double a, b, c;
    cout << "a = "; cin >> a; 
    cout << "b = "; cin >> b;
    cout << "c = "; cin >> c;
//============================================//
//                     3                      //
//============================================//
    double p = (a + b + c) / 2;
	double S = sqrt(p * (p - a) * (p - b) * (p - c));
	double h_a = 2 * S / a;
	double R = a * b * c / 4 / S;
	double r = 2 * S / 2 / p;
//============================================//
//                     4                      //
//============================================//
    cout << setprecision(2) 
         << fixed // Выводить в формате с фиксированной точкой
         << "S   = "   << setw(6) << S
         << "\nh_a = " << setw(6) << h_a
         << "\nR   = " << setw(6) << R
         << "\nr   = " << setw(6) << r 
         << endl;
    return 0;
}

Вывод:

a = 10
b = 5
c = 7
S   =  16.25
h_a =   3.25
R   =   5.39
r   =   1.48

Это не единственный вариант составления программы. Именно поэтому преподаватель всегда "вычислит" недобросовестных учеников. Что может отличать одну такую простую программу от её другой реализации? Как ни странно - программирование творческий процесс и у каждого разработчика есть свой "конёк" (стиль, если хотите). Например, для идентификаторов используют не буквы, а слова, при этом, существуют традиции для написания имен переменных (о них мы рассказывали ранее). Единственное ограничение - это то, что имена переменных должны соответствовать именам в условии задачи (с другой стороны, никто не мешает использовать в программе свои имена, а на выводе использовать символы из условия). Условие нашей задачи не предоставляет какого-то большого выбора, но, тем не менее, вывод можно было бы оформить совершенно иначе! К тому же, если вы воспользовались чужой реализацией, то по крайней мере - вы должны её объяснить. Иначе ваше решение точно не будет принято!
На следующем этапе проводится тестирование и отладка. Запуск программы на выполнение должен показать насколько данные, полученные программой, соответствуют ранее составленной математической модели. И, в целом, работа программы должна подчиняться известным свойствам алгоритма о которых мы говорили ранее. Если были выявлены синтаксические или семантические ошибки, то алгоритм отправляется на доработку, пока все ошибки не будут исправлены.
Завершающим этапом в разработке программы является инсталляция. Иными словами, внедрение продукта в эксплуатацию. В более широком смысле, этот этап включает не только внедрение, но и продвижение продукта, а также его документирование и сопровождение.

Приложение

Примеры решения задач
Вопросы
Темы сообщений
Задания А
  1. Даны стороны прямоугольника a и b. Найти его площадь S = a·b и периметр P = 2·(a + b).
  2. Дана длина L окружности. Найти ее радиус R и площадь S круга, ограниченного этой окружностью, учитывая, что L = 2·π·R, S = π·R2.
  3. Дано трехзначное число. Вывести число, полученное при прочтении исходного числа справа налево.
  4. С начала суток прошло N секунд (N — целое). Найти количество секунд, прошедших с начала последнего часа.
Задания Б
Задания С
1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 5,00 из 5)
Загрузка...

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.


Обсуждение закрыто.