Skip to content

Commit 913d0c8

Browse files
committed
Новая версия урока 4.2
1 parent 65f1d6a commit 913d0c8

File tree

4 files changed

+108
-22
lines changed

4 files changed

+108
-22
lines changed

lang_c/4/1__math_function/article.md

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

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

5-
Для использования математических функций нужно подключить заголовочный файл `math.h`. В этом файле объявлены различные математические функций и константы, но мы пока рассмотрим следующие:
5+
Для использования математических функций нужно подключить заголовочный файл `math.h`.
66

7-
Некоторые математические функции
7+
Некоторые математические функции:
88

99
- `fabs(x)` -- модуль числа `x`
1010
- `sqrt(x)` -- квадратный корень из числа `x`
@@ -17,14 +17,17 @@
1717

1818
Два важных момента.
1919

20-
- Все функции возвращают значение типа `double`.
21-
- В качестве аргументов эти функции принимают вещественные числа типа `double`, но можно передать и числа других типов (например, `int`, `float`). При этом произойдёт =неявное преобразование типа=. Компилятор из целого числа, например `3`, сделает вещественное `3.0`.
20+
- Все указанные функции возвращают значение типа `double`.
2221

23-
**Примеры:**
24-
Даны длины катетов прямоугольного треугольника. Вычислить длину гипотенузы. Простая задачка на знание теоремы Пифагора.
22+
- В качестве аргументов эти функции принимают вещественные числа типа `double`, но можно передать и числа других типов (например, `int`, `float`). При этом произойдёт =неявное преобразование (приведение) типа=. Компилятор из целого числа, например `3`, сделает число `3.0`, которое будет иметь тип `double`.
2523

26-
Листинг 1.
24+
### Примеры использования математический функций:
25+
26+
**Задача 1:** Даны длины катетов прямоугольного треугольника. Вычислить длину гипотенузы.
2727

28+
Простая задачка на знание теоремы Пифагора.
29+
30+
Листинг 1.
2831
```c
2932
#include <stdio.h>
3033
#include <math.h> // подключаем math.h
@@ -37,16 +40,18 @@ int main (void)
3740
scanf("%d", &b);
3841

3942
c2 = a*a + b*b;
40-
printf("c = %.2f\n", sqrt(c2));
41-
43+
printf("c = %.2f\n", sqrt(c2)); // так как sqrt возвращает double,
44+
// то используем спецификатор %f
4245
return 0;
4346
}
4447
```
4548
46-
Вычислить синус угла ввёденного с клавиатуры. Угол вводится в градусах.
49+
Из интересного в этой программе лишь неявное преобразование типа, которое происходит, когда мы вызываем функцию `sqrt`. Допустим, мы запустили программу и ввели `3` и `4`. В переменную `c2` (типа `int`) будет записано значение `3*3 + 4*4 = 25`. Когда мы пишем вызов функции `sqrt(c2)`, то вместо `c2`, как мы уже знаем, подставляется значение, которое в ней хранится, т.е. `25`, получаем: `sqrt(25)`. Но как нам уже известно, функция `sqrt` ждёт от нас значения типа `double`, а мы ей передали значение типа `int`. Поэтому компилятор сначала выполнит неявное приведение типа: преобразует целое значение `3` в вещественное значение `3.0`.
4750
48-
Листинг 2.
4951
52+
**Задача 2:** Вычислить синус угла, введённого с клавиатуры. Угол вводится в градусах.
53+
54+
Листинг 2.
5055
```c
5156
#include <stdio.h>
5257
#include <math.h> // подключаем math.h
@@ -66,24 +71,56 @@ int main (void)
6671
}
6772
```
6873

69-
В этой программе есть о чём поговорить.
70-
71-
Тригонометрические функции из файла `math.h` работают с радианной мерой угла (угол, заданный в радианах). Людям же привычнее работать с градусной мерой угла (углом, заданным в градусах). Поэтому в данной программе мы предварительно [перевели значение из градусов в радианы](https://stepik.org/lesson/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D0%BA%D0%B0-%D0%B2-%D0%A1%D0%B8-40857/step/9). Если этого не сделать, результат получится неправильным. Проверьте это самостоятельно.
74+
Тригонометрические функции математической библиотеки языка Си работают с радианной мерой угла (угол, заданный в радианах). Людям же привычнее работать с градусной мерой угла (углом, заданным в градусах). Поэтому в данной программе мы предварительно [переводим значение из градусов в радианы](https://stepik.org/lesson/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D0%BA%D0%B0-%D0%B2-%D0%A1%D0%B8-40857/step/9).
75+
Если этого не сделать, результат получится неправильным. Проверьте это самостоятельно.
7276

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

80+
%
7681
Неявное преобразование типов осуществляется в следующих случаях:
77-
78-
1. перед передачей аргументов в функцию (как в нашем примере с корнем. Листинг 1.)
82+
1. перед передачей аргументов в функцию
7983
2. выполнение арифметических операций с разными типами аргументов
8084
3. перед выполнением присваивания
8185

86+
Неявное преобразование типа при передаче аргумента в функцию мы обсудили выше (вызов функции `sqrt` в Листинге 1).
87+
88+
Пример неявного преобразования типа из пункта `2` мы встречали в прошлой заметке, когда разбирались с тем, как получить правильный результат деления `7` на `2`. Давайте посмотрим на Листинг 3 из прошлого шага:
89+
90+
Листинг 3.
91+
```c
92+
#include <stdio.h>
93+
94+
int main(void)
95+
{
96+
int a = 7, b = 2;
97+
float res;
98+
99+
res = (float) a / b;
100+
printf("%d / %d = %f\n", a, b, res);
101+
102+
return 0;
103+
}
104+
```
105+
106+
Разберём поподробнее, как будет обрабатываться строка `res = (float) a / b;`.
107+
108+
Сначала подставим значения переменных `a` и `b`: `res = (float) 7 / 2;`
109+
Далее произведём явное приведение типа для значения `7`: `res = 7.0 / 2;`
110+
111+
Т.к. процессоры умеют выполнять арифметические операции только с операндами одного и того же типа, то компилятору нужно произвести неявное приведение типа для значения `2`. Поэтому на этом этапе значение `2` преобразуется в значение `2.0`. Получаем: `res = 7.0 / 2.0;`
112+
113+
Получается, что в прошлом уроке мы изучили небольшой программистский "хак", связанный с преобразованием типов. Для правильного выполнения деления мы должны были бы сами явно привести оба числа к типу `float`, но мы схитрили и явно привели к типу `float` только одно число, а второе число преобразовал уже компилятор неявно. =)
114+
115+
82116
### Правила неявного преобразования типов
83117
84-
* если выполняются арифметические операции с разными типами аргументов. Оба аргумента приводятся к большему типу. Старшинство типов: `int` < `float` < `double`
118+
% **Важно!**
119+
* если выполняются арифметические операции с разными типами аргументов, Оба аргумента приводятся к большему типу. Старшинство типов: `int` < `float` < `double`
120+
* при вызове функций, переданные аргументы преобразуются к типам данных, которые ожидает функция.
121+
* при присваивании. Значение справа от оператора присваивания приводится к типу переменной слева от оператора присваивания.
85122
86-
* при присваивании. Значение справа от оператора присваивания приводится к типу переменной слева от оператора присваивания. При этом, если больший тип присваивается меньшему, то может произойти =потеря точности=.
123+
Снова обращаю ваше внимание на то, что если при неявном преобразовании типов может произойти =потеря точности=.
87124
88125
**Примеры:**
89126
@@ -93,3 +130,19 @@ int main (void)
93130
- `int = double` `double` будет преобразовано к `int` с потерей дробной части
94131
- `float = int` `int` будет преобразовано к `float`
95132
133+
Учитывая разобранные правила, посмотрим на Листинг 4 из прошлого урока.
134+
135+
Листинг 4.
136+
```c
137+
int a = 7, b;
138+
float g = 9.81, v;
139+
140+
b = (int) g; // приводим значение 9.81 к типу int, получим 9
141+
v = (float) a; // приводим значение 7 к типу float, получим 7.0
142+
```
143+
144+
В этом кусочке кода мы явно преобразовали тип значений справа от оператора присваивания. Но теперь, зная правила неявного преобразования типов, мы понимаем, что этого можно было бы и не делать, т.к. компилятор провёл бы эту процедуру самостоятельно. Т.е. можно было бы просто записать:
145+
```c
146+
b = g; \\ значение 9.81 будет неявно преобразовано к типу int, получим 9
147+
v = a; \\ значение 7 будет неявно преобразовано к типу float, получим 7.0
148+
```
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"title": "Математические функции в языке Си",
3-
"description": "Учимся использовать стандартные математические функции заголовочного файла math.h",
4-
"keywords": "математические функции в си, заголовочный файл math.h, неявное преобразование типов",
3+
"description": "Изучаем работу со стандартными математическими функциями заголовочного файла math.h, а также обсуждаем правила неявное преобразование типов.",
4+
"keywords": "математические функции в си, заголовочный файл math.h, неявное преобразование типов, правила неявного преобразования типов, старшинство типов данных",
55
"canonical_link": "https://youngcoder.ru/lessons/4/math_function"
66
}

lang_c/4/1__math_function/practice.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
<iframe src="https://stepik.org/lesson/41090/step/1"></iframe>
77
</div>
88

9+
- Вычислите диапазон типа `int` в вашей системе (минимально и максимальное число, которое можно сохранить в переменную типа `int`). Используйте [решение задачи](https://stepik.org/edit-lesson/41090/step/8) и следующие факты:
10+
1. Количество **байт**, выделенное под тип `int` в вашей системе можно узнать с помощью функции `sizeof()`. Если передать ей имя тип, то она вернёт целое число -- количество байт, выделенное под хранение этого типа данных. `printf("%d\n", sizeof(int));`
11+
2. `1 байт = 8 бит`
12+
3. Тип `int` по умолчанию является знаковым, а поэтому один бит в нём отводится на хранение знака числа.
13+
4. Нуль тоже число и его тоже нужно хранить в памяти.
14+
15+
Попробуйте сохранить полученные значения в переменные типа `int` и вывести их на экран. О результатах пишите в комментариях к этому уроку.
16+
917
### Исследовательские задачи для хакеров
1018

1119
1\. Разберитесь, почему следующая программа работает некорректно.
@@ -28,4 +36,7 @@ int main(void)
2836

2937
return 0;
3038
}
31-
```
39+
```
40+
41+
2\. Найдите на своём компьютере заголовочный файл `math.h` и изучите его содержимое. Думаю, что вы удивитесь тому, что в этом файле будут только заголовки функций, без тела функций (т.е. без описания того, как они работают). Выясните самостоятельно, почему это так и где найти внутреннюю реализацию математических функций.
42+

lang_c/4/1__math_function/reference.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,26 @@
22

33
1\. Изначально `math.h` создавался для различных математических функций, работающих с числами с плавающей точкой (это способ приближённого представления вещественных чисел в памяти компьютера). Поэтому в заголовочном файле `math.h` вычисление модуля реализовано только для вещественных чисел. Например, `fabs()` для чисел типа `double` или `fabsf()` для чисел типа `float`.
44

5-
Но существует также функция `abs()`, которая вычисляет модуль для целых чисел, но она объявлена в заголовочном файле `stdlib.h`.
5+
Но существует также функция `abs()`, которая вычисляет модуль для целых чисел, но она объявлена в заголовочном файле `stdlib.h`.
6+
7+
2\. Может показаться, что потеря точности происходит только при преобразовании вещественных чисел в целые, когда отбрасывается дробная часть. Но это не так. Следующий программа демонстрирует, что проблемы могут происходить и при преобразовании целых чисел в вещественные (`int` в `float`).
8+
9+
```c
10+
#include <stdio.h>
11+
12+
int main(void)
13+
{
14+
int big_num = 123456789;
15+
float float_num = big_num; // неявно преобразуем int в float ПРОБЛЕМА
16+
int back_to_int = float_num; // неявно преобразуем float в int
17+
18+
printf("source: %d\n", big_num);
19+
// printf("float: %f\n", float_num); // раскомментируйте, чтобы посмотреть на проблему
20+
printf("after conversation: %d\n", back_to_int);
21+
printf("difference: %d\n", big_num - back_to_int);
22+
23+
return 0;
24+
}
25+
```
26+
27+
Поэтому, если у вас где-то происходят преобразования типов (явные или неявные), то внимательно проверяйте получившийся результат вычислений!

0 commit comments

Comments
 (0)