В статье рассмотрен вопрос переполнения типов данных, используемых для хранения целых чисел в языке C# (.NET), и способы обработки подобных ситуаций.
В языках программирования, таких как C, C++, C#, Java для работы с числовыми данными существует довольно много типов данных. Для операций с числами с плавающей запятой это, как правило, типы подобные float и double. Для операций с целыми числами – это byte, short, int, long и т.д., плюс варианты данных типов без знака, все они отличаются размером памяти, которая выделяется для хранения числа. В этой статье остановимся на особенностях работы с целыми числами в C#.
Язык C# предоставляет следующие типы для работы с целыми числами.
Тип | Описание |
sbyte | 8-разрядное число со знаком |
byte | 8-разрядное число без знака |
short | 16-разрядное число со знаком |
ushort | 16-разрядное число без знака |
int | 32-разрядное число со знаком |
uint | 32-разрядное число без знака |
long | 64-разрядное число со знаком |
ulong | 64-разрядное число без знака |
Для наших экспериментов напишем следующую программу.
using System; namespace simple { class Program { static void Main(string[] args) { byte a = 7; Console.WriteLine("out: " + a); } } }
Если вы ее откомпилируете и запустите, то на экране будет выведена строка:
out: 7
Далее все модификации кода будут касаться тела метода Main.
Изменим программу следующим образом.
byte a = 9; byte b = 7; byte c = (byte)(a + b); Console.WriteLine("out: " + c);
Результат сложения a и b необходимо привести к типу byte, т.к. по умолчанию тип суммы – это int. Работа этой программы вполне предсказуема, мы получим следующее:
out: 16
Если изменить значения a и b так, чтобы их сумма стала больше 255:
byte a = 150; byte b = 244; byte c = (byte)(a + b); Console.WriteLine("out: " + c);
Результат не столь тривиален (хотя для опытных разработчиков, он вполне закономерен):
out: 138
Данный ответ получился из-за того, что в переменной типа byte не может храниться число большее, чем 255, если мы приводим к данному типу значение большее, чем этот порог (в нашем случае это 150 + 244 = 394), то произойдет переполнение. Число 138 получилось следующим образом: из 394 мы должны вычитать 256 до тех пор, пока разность не станет меньше 255, в данном примере это нужно сделать один раз: 394 – 256 = 138. Самое неприятное заключается в том, что такая ситуация может произойти довольно неожиданно, и мы получим некорректный результат в процессе вычисления на “отлаженной и протестированной” программе, где-нибудь в продакшине. Если это критично, то мы может сделать арифметические операции проверяемыми на переполнение. При запуске такого кода, при переполнении, произойдет выброс исключения OverflowException.
byte a = 150; byte b = 244; byte c = checked((byte)(a + b)); Console.WriteLine("out: " + c);
Оператор checked следит за тем, чтобы в переданном ему выражении не было переполнения. Его можно записать в другом виде, более удобном для случая, когда вычисление требует нескольких операций.
byte a = 150; byte b = 244; byte c = 0; checked { a += 10; c = ((byte)(a + b)); } Console.WriteLine("out: " + c);
Проверку на переполнение можно включить глобально, для всех вычислений, для этого нужно Visual Studio зайти в свойства проекта, перейти на вкладку Build и нажать там на кнопку “Advanced…”. В результате откроется окно с настройками.
В нем можно поставить (или убрать) галочку в поле “Check for arithmetic overflow/underflow”. Если установить галочку, то все вычисления будут проверяться на переполнение, и если оно произойдет, то будет выброшено исключение OverflowException. В таком случае можно отдельно создавать блоки кода, в которых данная проверка производиться не будет, для этого используется оператор unchecked. Это может выглядеть так:
unchecked { a += 10; c = ((byte)(a + b)); }
Более подробно вопросы, связанные с вычислениями и типами данных, представлены в книге:
«CLR via C#. Программирование на платформе Microsoft.NET Framework 4.5 на языке C#» Джеффри Рихтер |
На этом всё! Спасибо! Корректных вам вычислений!