Система типов
Особенности выполнения арифметических операций
Особенности выполнения операций над целочисленными операндами и операндами с плавающей точкой связаны с особенностями выполнения арифметических операций и с ограниченной точностью переменных типа float и double.
float – 7 значащих цифр double – 16 значащих цифр
1000000*1000000==1000000000000, но максимально допустимое положительное значение для типа System.Int32 составляет 2147483647. В результате переполнения получается неверный результат –727379968.
Ограниченная точность значений типа System.Single проявляется при присвоении значений переменной типа System.Double. Приводимый ниже простой программный код иллюстрирует некоторые особенности арифметики .NET:
using System; class Class1 { const double epsilon = 0.00001D; static void Main(string[] args) { int valI = 1000000, resI; resI = (valI*valI)/valI; // –727379968/1000000 == –727 Console.WriteLine ("The result of action (1000000*1000000/1000000) is {0}", resI); float valF00 = 0.2F, resF; double valD00 = 0.2D, resD; // Тест на количество значащих цифр для значений типа double и float. resD = 12345678901234567890; Console.WriteLine(">>>>> {0:F10}",resD); resF = (float)resD; Console.WriteLine(">>>>> {0:F10}",resF); resD = (double)(valF00 + valF00); // 0.400000005960464 if (resD == 0.4D) Console.WriteLine("Yes! {0}",resD); else Console.WriteLine("No! {0}",resD); resF = valF00*5.0F; resD = valD00*5.0D; resF = (float)valD00*5.0F; resD = valF00*5.0D; //1.0000000149011612 if (resD == 1.0D) Console.WriteLine("Yes! {0}",resD); else Console.WriteLine("No! {0}",resD); resF = valF00*5.0F; resD = valF00*5.0F; //1.0000000149011612 if (resD.Equals(1.0D)) Console.WriteLine("Yes! {0}",resD); else Console.WriteLine("No! {0}",resD); if (Math.Abs(resD – 1.0D) < epsilon) Console.WriteLine("Yes! {0:F7}, {1:F7}",resD – 1.0D, epsilon); else Console.WriteLine("No! {0:F7}, {1:F7}",resD – 1.0D, epsilon); } }Листинг 2.1.
В результате выполнения программы выводится такой результат:
The result of action (1000000*1000000/1000000) is -727 >>>>> 12345678901234600000,0000000000 >>>>> 12345680000000000000,0000000000 No! 0,400000005960464 No! 1,00000001490116 No! 1,00000001490116 Yes! 0,0000000, 0,0000100
Особенности арифметики с плавающей точкой
- Если переменной типа float присвоить величину x из интервала –1.5E–45 < x < 1.5E–45 (x != 0), результатом операции окажется положительный ( x > 0 ) или отрицательный ( x < 0 ) нуль ( +0, –0 ).
- Если переменной типа double присвоить величину x из интервала –5E–324 < x < 5E–324 (x != 0), результатом операции окажется положительный ( x > 0 ) или отрицательный ( x < 0 ) нуль ( +0, –0 ).
- Если переменной типа float присвоить величину x, которая –3.4E+38 < x или x > 3.4E+38, результатом операции окажется положительная ( x > 0 ) или отрицательная ( x < 0 ) бесконечность ( +Infinity, –Infinity ).
- Если переменной типа double присвоить величину x, для которой –1.7E+308 > x или x < 1.7E+308, результатом операции окажется положительная ( x > 0 ) или отрицательная ( x < 0 ) бесконечность ( +Infinity, –Infinity ).
- Выполнение операции деления над значениями типов с плавающей точкой ( 0.0/0.0 ) дает NaN (Not a Number).
checked и unchecked. Контроль за переполнением
Причиной некорректных результатов выполнения арифметических операций является особенность представления значений арифметических типов.
Арифметические типы имеют ограниченные размеры. Поэтому любая арифметическая операция может привести к переполнению. По умолчанию в C# переполнение, возникающее при выполнении операций, никак не контролируется. Возможный неверный результат вычисления остается всего лишь результатом выполнения операции, и никого не касается, КАК эта операция выполнялась.
Механизм контроля за переполнением, возникающим при выполнении арифметических операций, обеспечивается ключевыми словами checked (включить контроль за переполнением) и unchecked (отключить контроль за переполнением), которые используются в составе выражений. Конструкции управления контролем за переполнением имеют две формы:
- операторную, которая обеспечивает контроль над выполнением одного выражения:
::::: short x = 32767; short y = 32767; short z = 0; try { z = checked(x + unchecked(x+y)); } catch (System.OverflowException e) { Console.Writeline("Переполнение при выполнении сложения"); } return z; :::::
При этом контролируемое выражение может быть произвольной формы и сложности и может содержать другие вхождения как контролируемых, так и неконтролируемых выражений;
- блочную, которая обеспечивает контроль над выполнением операций в блоке операторов:
::::: short x = 32767; short y = 32767; short z = 0, w = 0; try { unchecked { w = x+y; } checked { z = x+w; } } catch (System.OverflowException e) { Console.Writeline("Переполнение при выполнении сложения"); } return z; :::::
Естественно, контролируемые блоки при этом также могут быть произвольной сложности.
Константное выражение
Константное выражение – это либо элементарное константное выражение, к которым относятся:
- символьный литерал,
- целочисленный литерал,
- символьная константа,
- целочисленная константа,
- либо выражение, построенное на основе элементарных константных выражений с использованием скобок и символов операций, определенных на множестве значений данного типа.
Отличительные черты константного выражения:
- значение константного выражения не меняется при выполнении программы;
- значение константного выражения становится известно на этапе компиляции модуля, до начала выполнения модуля.