Тонкости работы с числами в C#

В обзоре типов данных C# было в том числе было сказано, что от того, какой тип будет иметь наша переменная будет зависеть и то какие максимальные и минимальные значения она сможет принимать. Например, мы не сможем присвоить переменной типа byte значение более 255, так как Visual Studio сразу же сообщит нам, что тип byte такие значения не допускает. В этой части я постараюсь рассмотреть некоторые тонкости работы с целочисленными и вещественными типами данных в C#.

Работа с целыми числами в C#

Как мы уже знаем, в C# есть несколько целочисленных типов данных, которые характеризуются максимально и минимально возможными пределами. Например, переменная типа int может содержать значения от от -2 147 483 648 до 2 147 483 647.  Чтобы узнать пределы, которые может принимать переменная определенного типа можно выполнить вот такие действия:

Console.WriteLine($"Max value = {int.MaxValue}");
Console.WriteLine($"Min value = {int.MinValue}");

Здесь мы узнаем предельные значения для типа int.

В приведенном примере мы использовали интерполяцию строк (перед строкой стоит символ $). О том, что такое интерполяция строк и как она работает мы поговорим позднее, когда будем изучать строки в C#

Теперь, узнав максимальные и минимальные значения для типа int попробуем выполнить вот такое действие:

int i = int.MaxValue;
i = i + 1;
Console.WriteLine(i);

Вопреки всем ожиданиям, Visual Studio не сделает нам никаких предупреждений, программа скомпилируется и, даже, выдаст нам ответ. Правда не 2 147 483 648, а вот такой:

-2147483648

Всё дело в том, что если при вычислении значения переменной результат выходит за пределы типа, то возникает условие потери значимости или переполнения. Результат вычисления переменной должен находиться в диапазоне от минимального до максимального значения. Так, в приведенном выше примере мы намеренно получили переполнение и результат оказался равным минимальному значению для int. Соответственно, можно получить обратный результат, например,

int i = int.MinValue;
i = i - 2;
Console.WriteLine(i);

при этом нетрудно догадаться, что значение окажется равным

2147483646

то есть на единицу меньше, чем максимальное значение для типа int. Условия переполнения, в принципе, достаточно просто не допускать при разработке приложений, хотя периодически такие условия и имеют место быть в различных проектах. Теперь посмотрим ещё на один пример:

long longValue = 1000000 * 1000000;
Console.WriteLine(longValue);

По логике вещей, тип long в C# должен вполне справиться с задачей так как:

Результат вычисления  1000 000 * 1000 000 1 000 000 000 000
Максимальное значение для long 9 223 372 036 854 775 807

Вроде бы мы всё сделали корректно — указали для переменной тип long, но Visual Studio выдает ошибку:

Ошибка CS0220 Переполнение при выполнении операции во время компиляции в режиме проверки.

Дело в том, что по умолчанию целочисленные литералы воспринимаются как int. Компилятор видит два числа — оба воспринимает как int и результат же ожидает как int, а так как результат вычисления выражения явно выходит за пределы int возникает ошибка. Чтобы этого не происходило можно воспользоваться двумя вариантами:

  1. Использовать суффикс L, чтобы показать компилятору, что перед ним число типа long.
  2. Завести переменную типа long

Продемонстрируем оба варианта, которые приведут к тому, что C# правильно вычислит произведение двух чисел:

long longValue = 1000000 * 1000000L;//используем суффикс
Console.WriteLine(longValue);
long longValue2 = 1000000;
Console.WriteLine(longValue2*1000000);//используем переменную типа long

В обоих случаях результат будет верным:

1000000000000

1000000000000

Работа с вещественными числами

Любой школьник знает, что периодическая десятичная дробь 0.3 не равняется 1/3. Однако, не каждый, кто начинает изучать C# знает, что результатом вот такого действия:

Console.WriteLine(1/3);

будет

0

Здесь, опять же срабатывает правило — целое число (int) делится на целое число (int), следовательно и ответ должен быть int. А так как int не может содержать вещественных чисел, то получаем 0. Выход из сложившейся ситуации точно такое же, как и в предыдущем примере — надо указать компилятору, что хотя бы одно из чисел является вещественным. Для вещественных типов данных в C# можно использовать следующие суффиксы:

Суффикс Тип данных
d или D double
f или F float
m или M decimal

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

Console.WriteLine($"float: {1 / 3f}");
Console.WriteLine($"double: {1 / 3d}");
Console.WriteLine($"decimal: {1 / 3m}");

Вывод консоли будет следующим:

float: 0,33333334

double: 0,3333333333333333

decimal: 0,3333333333333333333333333333

как видите, тип float имеет самую низкую точность вычисления. В C# вещественные литералы по умолчанию определяются как double.

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

  1. Если в выражении есть тип double, оно оценивается как double.
  2. Если в выражении нет типа double, оно оценивается как float.

Можно также смешивать целочисленные типы и тип decimal в выражении. В этом случае целочисленные типы неявно преобразуются в тип decimal, а выражение вычисляется как decimal. Однако, тип decimal нельзя смешивать с типами float и double в выражении. В этом случае, если требуется выполнить арифметические операции, операции сравнения или равенства, необходимо явно преобразовать операнды из типа или в тип decimal, например, вот так:

double a = 1.0;
decimal b = 2.1m;
Console.WriteLine(a + (double)b);

Если не производить явное преобразование decimal к double, то Visual Studio также выдаст ошибку:

Ошибка CS0019 Оператор «+» невозможно применить к операнду типа «double» и «decimal»

Итого

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

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии