Урок 2.4 — Работа с числами
Числа — основа вычислений. Разберём арифметику, класс Math, Random и подводные камни целочисленного деления.
Числа — основа программирования. Счёт в играх, цены в интернет-магазинах, координаты на карте, процент заряда батареи — всё это числа. В этом уроке мы научимся выполнять арифметические операции, округлять, брать корни и степени, а также генерировать случайные числа. Мы поднимемся на новый уровень — ты перестанешь просто складывать 2 + 2 и начнёшь писать настоящие вычислительные программы.
Арифметические операторы
В C# есть пять базовых арифметических операторов. Они работают точно так же, как на уроках математики в школе. Таблица операторов:
| Оператор | Название | Пример | Результат |
|---|---|---|---|
+ | Сложение | 10 + 3 | 13 |
- | Вычитание | 10 - 3 | 7 |
* | Умножение | 10 * 3 | 30 |
/ | Деление | 10 / 3 | 3 (int) или 3.333 (double) |
% | Остаток от деления | 10 % 3 | 1 |
Арифметика в C#:
Во всех примерах ниже показаны операции с разными типами и их результаты.
int a = 15;
int b = 4;
Console.WriteLine(a + b); // 19 — сложение
Console.WriteLine(a - b); // 11 — вычитание
Console.WriteLine(a * b); // 60 — умножение
Console.WriteLine(a / b); // 3 — целочисленное деление! (15 / 4 = 3, остаток 3)
Console.WriteLine(a % b); // 3 — остаток от деления (15 = 4*3 + 3)
double x = 15.0;
double y = 4.0;
Console.WriteLine(x / y); // 3.75 — дробное деление (один операнд double)
Операторы подчиняются приоритетам математики: умножение и деление выполняются раньше сложения и вычитания. Скобки () меняют порядок:
int result1 = 2 + 3 * 4; // 14 (сначала 3*4=12, потом 2+12=14)
int result2 = (2 + 3) * 4; // 20 (сначала 2+3=5, потом 5*4=20)
int result3 = 10 - 4 + 2; // 8 (слева направо: 10-4=6, 6+2=8)
int result4 = 10 - (4 + 2); // 4 (сначала 4+2=6, потом 10-6=4)
Console.WriteLine(result1); // 14
Console.WriteLine(result2); // 20
Console.WriteLine(result3); // 8
Console.WriteLine(result4); // 4
* / %, потом + -. Если сомневаешься — ставь скобки (). Код будет понятнее, а порядок — явным.Целочисленное деление и остаток %
Одна из главных ловушек для новичков в C# — целочисленное деление. Когда ты делишь int на int, результат всегда будет int. Дробная часть просто отбрасывается. Чтобы получить дробный результат, хотя бы один из операндов должен быть double.
⚠️ Классическая ошибка новичка
int a = 5;
int b = 2;
double result = a / b; // 2.0, а не 2.5!
Console.WriteLine(result); // 2
// Почему? Потому что a / b сначала вычисляется как 5 / 2 = 2 (int),
// а потом 2 (int) преобразуется в 2.0 (double).
// Исправление:
double result2 = (double)a / b; // 2.5 — явно преобразуем один операнд
Оператор % (остаток от деления) — невероятно полезная штука. Он возвращает остаток от деления одного числа на другое. В школе это называли «деление с остатком»:
Console.WriteLine(10 % 3); // 1 (10 = 3*3 + 1)
Console.WriteLine(17 % 5); // 2 (17 = 5*3 + 2)
Console.WriteLine(8 % 2); // 0 (8 чётное — делится нацело)
Console.WriteLine(9 % 2); // 1 (9 нечётное)
Console.WriteLine(100 % 7); // 2 (100 = 7*14 + 2)
Console.WriteLine(0 % 5); // 0 (0 делить на что угодно = 0)
Где применяется % в реальных программах:
- Проверка чётности:
число % 2 == 0— чётное - Ограничение диапазона:
index % max— никогда не выйдет за пределы max - Разбиение на группы: каждый N-ый элемент (например, каждый 3-ий)
- Время: сколько минут осталось после перевода секунд в часы
- Криптография: многие алгоритмы шифрования используют модульную арифметику
💡 Золотое правило: если тебе нужно поделить целые числа и получить дробный результат — преобразуй один из операндов в double. Если нужен только целый результат — дели спокойно, C# сделает это сам. Оператор % даст тебе остаток.
Инкремент и декремент: ++ и --
В программировании постоянно нужно увеличить число на 1 или уменьшить на 1: счётчик очков, номер шага в цикле, индекс элемента. Вместо того чтобы писать score = score + 1, C# предлагает краткую запись:
| Оператор | Название | Эквивалент |
|---|---|---|
x++ | Постфиксный инкремент | x = x + 1 |
++x | Префиксный инкремент | x = x + 1 |
x-- | Постфиксный декремент | x = x - 1 |
--x | Префиксный декремент | x = x - 1 |
Разница между x++ и ++x проявляется, когда оператор используется внутри другого выражения. В обоих случаях переменная увеличивается. Но x++ возвращает старое значение (до увеличения), а ++x — новое (после увеличения).
int a = 5;
int b = 5;
Console.WriteLine(a++); // 5 (сначала вернули a=5, потом увеличили до 6)
Console.WriteLine(a); // 6 (теперь a=6)
Console.WriteLine(++b); // 6 (сначала увеличили b до 6, потом вернули 6)
Console.WriteLine(b); // 6 (b=6)
// Демонстрация разницы:
int x = 3;
int y = 3;
int result1 = x++ * 2; // result1 = 6 (x=3, потом x стал 4)
int result2 = ++y * 2; // result2 = 8 (y стал 4, потом 4*2=8)
Console.WriteLine($"result1={result1}, x={x}"); // result1=6, x=4
Console.WriteLine($"result2={result2}, y={y}"); // result2=8, y=4
x++ и --x отдельно, не в составе других выражений. Так ты избежишь путаницы. Разницу между префиксом и постфиксом мы подробно разберём в теме «Циклы».Составные операторы присваивания
Очень часто нужно изменить переменную, применив к ней операцию: x = x + 5, price = price * 0.9. C# предоставляет составные операторы для таких случаев — это сокращения, которые совмещают операцию и присваивание:
| Оператор | Эквивалент | Пример |
|---|---|---|
+= | x = x + y | score += 10; |
-= | x = x - y | fuel -= 5; |
*= | x = x * y | price *= 0.9; |
/= | x = x / y | count /= 2; |
%= | x = x % y | index %= 3; |
int score = 0;
score += 10; // score = 0 + 10 = 10
score += 5; // score = 10 + 5 = 15
Console.WriteLine(score); // 15
score -= 3; // score = 15 - 3 = 12
Console.WriteLine(score); // 12
score *= 2; // score = 12 * 2 = 24
Console.WriteLine(score); // 24
score /= 4; // score = 24 / 4 = 6
Console.WriteLine(score); // 6
score %= 5; // score = 6 % 5 = 1
Console.WriteLine(score); // 1
Составные операторы не только сокращают код, но и читаются естественнее: «увеличь счёт на 10» вместо «счёт равен счёт плюс 10». Программисты используют их постоянно.
💡 Запомни: += работает и со строками! string message = "Привет"; message += ", мир!"; — это то же самое, что конкатенация. Но для чисел это гораздо полезнее.
Класс Math — математика в C#
C# предоставляет встроенный класс Math, который содержит множество математических функций. Тебе не нужно писать их самому — просто вызывай готовые методы. Все методы класса Math статические, поэтому вызываются через имя класса: Math.MethodName().
Возвращает абсолютное значение числа — расстояние от нуля. Всегда неотрицательное.
Console.WriteLine(Math.Abs(-5)); // 5
Console.WriteLine(Math.Abs(5)); // 5
Console.WriteLine(Math.Abs(-3.14)); // 3.14
Console.WriteLine(Math.Abs(0)); // 0
Возвращает большее (Max) или меньшее (Min) из двух чисел. Принимает два аргумента одного типа.
int maxVal = Math.Max(10, 20); // 20
int minVal = Math.Min(10, 20); // 10
double maxD = Math.Max(3.14, 2.71); // 3.14
double minD = Math.Min(3.14, 2.71); // 2.71
// Можно вкладывать вызовы:
int maxOfThree = Math.Max(5, Math.Max(9, 3)); // 9
Возводит число (первый аргумент) в указанную степень (второй аргумент). Результат всегда double.
Console.WriteLine(Math.Pow(2, 3)); // 8 (2 в кубе)
Console.WriteLine(Math.Pow(5, 2)); // 25 (5 в квадрате)
Console.WriteLine(Math.Pow(10, 0)); // 1 (любое число в степени 0 = 1)
Console.WriteLine(Math.Pow(16, 0.5)); // 4 (квадратный корень через степень 1/2)
Console.WriteLine(Math.Pow(2, 10)); // 1024 (2 в 10-й степени)
Извлекает квадратный корень числа. Если число отрицательное — вернёт NaN (Not a Number).
Console.WriteLine(Math.Sqrt(25)); // 5
Console.WriteLine(Math.Sqrt(2)); // 1.41421356237
Console.WriteLine(Math.Sqrt(0.25)); // 0.5
Console.WriteLine(Math.Sqrt(-1)); // NaN (Not a Number)
// Используем для вычисления гипотенузы:
double a = 3;
double b = 4;
double c = Math.Sqrt(a * a + b * b); // 5.0 (теорема Пифагора)
Три способа округлить дробное число:
- Round — математическое округление (0.5 → вверх, 0.4 → вниз)
- Floor — всегда вниз (пол — пол внизу)
- Ceiling — всегда вверх (потолок — потолок сверху)
double d = 3.75;
Console.WriteLine(Math.Round(d)); // 4 (3.75 → 4)
Console.WriteLine(Math.Floor(d)); // 3 (всегда вниз)
Console.WriteLine(Math.Ceiling(d)); // 4 (всегда вверх)
double e = 3.49;
Console.WriteLine(Math.Round(e)); // 3 (3.49 → 3)
Console.WriteLine(Math.Floor(e)); // 3
Console.WriteLine(Math.Ceiling(e)); // 4 (даже 3.0001 → 4)
// Округление до N знаков после запятой:
Console.WriteLine(Math.Round(3.14159, 2)); // 3.14
Console.WriteLine(Math.Round(3.14159, 4)); // 3.1416
Console.WriteLine(Math.Floor(3.99)); // 3
Console.WriteLine(Math.Ceiling(3.01)); // 4
// Отрицательные числа:
Console.WriteLine(Math.Floor(-1.5)); // -2 (вниз от -1.5 это -2!)
Console.WriteLine(Math.Ceiling(-1.5)); // -1 (вверх от -1.5 это -1)
Math.Floor(-1.5) даёт -2, потому что «пол» всегда ниже. -2 < -1.5, значит это «пол». А -1 > -1.5, это «потолок». Логика, но новичков это удивляет.Константы Math.PI и Math.E
Класс Math содержит две полезные константы:
Console.WriteLine(Math.PI); // 3.14159265358979
Console.WriteLine(Math.E); // 2.71828182845905
// Вычисляем длину окружности:
double radius = 5.0;
double circumference = 2 * Math.PI * radius;
Console.WriteLine($"Длина окружности: {circumference:F2}"); // 31.42
Случайные числа: класс Random
Случайные числа нужны в играх, тестировании, симуляциях и даже в криптографии. В C# за генерацию случайных чисел отвечает класс Random. Он «псевдослучайный» — числа выглядят случайными, но вычисляются по формуле.
Основные методы класса Random:
| Метод | Описание | Пример |
|---|---|---|
Next() | Любое целое от 0 до 2 147 483 646 | rnd.Next() |
Next(max) | От 0 до max-1 | rnd.Next(10) → 0..9 |
Next(min, max) | От min до max-1 | rnd.Next(1, 7) → 1..6 (как кубик) |
NextDouble() | Дробное от 0.0 до 1.0 (не включая 1.0) | rnd.NextDouble() |
// Создаём генератор случайных чисел
Random rnd = new Random();
// Целое число от 0 до 99
int randomInt = rnd.Next(100);
Console.WriteLine($"Случайное 0..99: {randomInt}");
// Кубик D6 (1 до 6)
int dice = rnd.Next(1, 7);
Console.WriteLine($"Кубик: {dice}");
// Дробное число 0.0..1.0
double randomDouble = rnd.NextDouble();
Console.WriteLine($"Дробное: {randomDouble:F4}");
// Случайное число от 5.0 до 10.0
double customDouble = 5.0 + rnd.NextDouble() * 5.0;
Console.WriteLine($"Дробное 5..10: {customDouble:F2}");
⚠️ Типичные ошибки с Random:
- Создавать новый Random в цикле. Если создать два Random подряд в быстрой последовательности — оба выдадут одинаковые числа, потому что инициализируются одинаковым значением системного таймера.
- Забыть про диапазон.
rnd.Next(1, 7)даёт числа от 1 до 6 (7 не входит). Запомни: первый включён, второй исключён. - Путать NextDouble и Next. NextDouble не принимает аргументов и даёт 0.0..1.0. Next с одним аргументом даёт 0..max-1.
System.Security.Cryptography.RandomNumberGenerator, но это тема продвинутого уровня.Мини-тест: проверь себя
Попробуй ответить на вопросы. Это поможет закрепить материал.
Вопрос 1: Чему равно 17 % 5?
✅ Ответ: B (17 = 5*3 + 2, остаток 2)
Вопрос 2: Что выведет этот код?
int a = 7;
int b = 2;
double result = a / b;
Console.WriteLine(result);
✅ Ответ: C (7 / 2 = 3 — целочисленное деление, потом 3 → 3.0)
Вопрос 3: Что вернёт Math.Ceiling(-2.3)?
✅ Ответ: B (-2. Ceiling — это «потолок», т.е. наименьшее целое, которое >= -2.3. Это -2.)
Вопрос 4: Как получить случайное число от 50 до 100 (включительно)?
✅ Ответ: A (100 включительно, поэтому max = 101, т.к. второй аргумент не включается.)
Вопрос 5: Что выведет этот код?
int x = 5;
int y = ++x * 2;
Console.WriteLine($"{x} {y}");
✅ Ответ: C (++x сначала увеличивает x до 6, потом 6 * 2 = 12.)
Практическое задание
Напиши программу «Калькулятор здоровья». Она должна:
- Запросить у пользователя имя, возраст, рост (в см) и вес (в кг)
- Вычислить ИМТ (индекс массы тела): вес / (рост в метрах)2
- Округлить ИМТ до одного знака после запятой
- Вычислить, сколько лет осталось до 100 лет
- Определить, чётный или нечётный возраст пользователя
- Сгенерировать случайное «число здоровья» от 1 до 100 и вывести его
- Использовать класс Math (хотя бы Sqrt, Pow, Round)
👀 Показать решение
using System;
class Program
{
static void Main()
{
Console.Write("Введите имя: ");
string name = Console.ReadLine();
Console.Write("Введите возраст: ");
int age = int.Parse(Console.ReadLine());
Console.Write("Введите рост (см): ");
double heightCm = double.Parse(Console.ReadLine());
Console.Write("Введите вес (кг): ");
double weight = double.Parse(Console.ReadLine());
// Расчёт ИМТ
double heightM = heightCm / 100.0;
double bmi = weight / Math.Pow(heightM, 2);
bmi = Math.Round(bmi, 1);
// До 100 лет
int yearsTo100 = 100 - age;
// Чётность
bool isEven = age % 2 == 0;
// Случайное число здоровья
Random rnd = new Random();
int healthScore = rnd.Next(1, 101);
// Вывод
Console.WriteLine($"{"=".PadRight(45, '=')}");
Console.WriteLine($" Здоровье: {name}");
Console.WriteLine($"{"=".PadRight(45, '=')}");
Console.WriteLine($" Возраст: {age} лет {(isEven ? "(чётный)" : "(нечётный)")}");
Console.WriteLine($" До 100 лет: {yearsTo100} лет");
Console.WriteLine($" ИМТ: {bmi}");
Console.WriteLine($" Здоровье: {healthScore}/100");
Console.WriteLine($"{"=".PadRight(45, '=')}");
}
}
Всё вместе — симулятор битвы
Напишем программу, которая симулирует один раунд битвы между героем и монстром. Используем все изученные концепции: арифметику, Math, Random, инкременты, составные операторы.
using System;
class Program
{
static void Main()
{
Random rnd = new Random();
// Характеристики героя
string heroName = "Артур";
int heroHp = 100;
int heroAttack = 20;
int heroDefense = 10;
// Характеристики монстра
string monsterName = "Гоблин";
int monsterHp = 80;
int monsterAttack = 15;
int monsterDefense = 5;
Console.WriteLine($"⚔️ {heroName} против {monsterName}!");
Console.WriteLine($"{"=".PadRight(40, '=')}");
// Раунд атаки героя
int heroRoll = rnd.Next(1, 21); // бросок удачи 1..20
int heroDamage = heroAttack + heroRoll; // урон = атака + бросок
int actualDamage = Math.Max(1, heroDamage - monsterDefense); // с учётом защиты
monsterHp -= actualDamage; // составной оператор
monsterHp = Math.Max(0, monsterHp); // не ниже 0
Console.WriteLine($"{heroName} наносит {actualDamage} урона!");
Console.WriteLine($"У {monsterName} осталось {monsterHp} HP");
// Раунд атаки монстра (если жив)
if (monsterHp > 0)
{
int monsterRoll = rnd.Next(1, 21);
int monsterDamage = monsterAttack + monsterRoll;
int actualMonsterDmg = Math.Max(1, monsterDamage - heroDefense);
heroHp -= actualMonsterDmg;
heroHp = Math.Max(0, heroHp);
Console.WriteLine($"{monsterName} наносит {actualMonsterDmg} урона!");
Console.WriteLine($"У {heroName} осталось {heroHp} HP");
}
// Итог
Console.WriteLine($"{"=".PadRight(40, '=')}");
if (monsterHp <= 0)
Console.WriteLine($"🏆 {heroName} победил!");
else if (heroHp <= 0)
Console.WriteLine($"💀 {monsterName} победил...");
else
Console.WriteLine($"🤝 Оба живы. Следующий раунд!");
// Статистика
Console.WriteLine($"{"=".PadRight(40, '=')}");
Console.WriteLine($"Итоговое HP: {heroName}={heroHp}, {monsterName}={monsterHp}");
// Процент оставшегося здоровья
double heroPercent = Math.Round((double)heroHp / 100 * 100, 1);
double monsterPercent = Math.Round((double)monsterHp / 80 * 100, 1);
Console.WriteLine($"Здоровье: {heroPercent}% vs {monsterPercent}%");
}
}
Math.Max(0, monsterHp) гарантирует, что HP не уйдёт в минус. Math.Max(1, heroDamage - monsterDefense) гарантирует минимум 1 урона. Это идиоматические приёмы в C#.Что важно запомнить
Тест: 2.4: Работа с числами
7 вопросов