Using C# Checked Keyword And .NET Numerics BigInteger

This article focuses on the importance of understanding data size in order to avoid hard-to-detect bugs. Let's say we would like to calculate the factorial of the first 25 integers. The simple program to do so would look like this,
  1. class Program  
  2. {  
  3.    public static void Main(string[] args)  
  4.    {  
  5.       for (int n = 1; n <= 25; n++)  
  6.       {  
  7.          int f = 1; // Variable calculating factorial  
  8.          int k = n;  
  9.          while (k > 1)  
  10.          {  
  11.             f *= k;  
  12.             k--;  
  13.          }  
  14.          Console.WriteLine("Factorial of {0} is: {1}", n, f);  
  15.       }  
  16.       Console.WriteLine();  
  17.       Console.Write("Press any key to continue . . . ");  
  18.       Console.ReadKey(true);  
  19.    }  
  20. }  
The code would produce the following output,
 
Factorial of 1 is: 1
Factorial of 2 is: 2
Factorial of 3 is: 6
Factorial of 4 is: 24
Factorial of 5 is: 120
Factorial of 6 is: 720
Factorial of 7 is: 5040
Factorial of 8 is: 40320
Factorial of 9 is: 362880
Factorial of 10 is: 3628800
Factorial of 11 is: 39916800
Factorial of 12 is: 479001600
Factorial of 13 is: 1932053504
Factorial of 14 is: 1278945280
Factorial of 15 is: 2004310016
Factorial of 16 is: 2004189184
Factorial of 17 is: -288522240
Factorial of 18 is: -898433024
Factorial of 19 is: 109641728
Factorial of 20 is: -2102132736
Factorial of 21 is: -1195114496
Factorial of 22 is: -522715136
Factorial of 23 is: 862453760
Factorial of 24 is: -775946240
Factorial of 25 is: 2076180480
Press any key to continue . . .
 
Not quite what one would expect. Somwhere around 13 something when wrong. That something is that the user just experienced integer overflow. The C# implementation ot the "int" type (which we used for variable "f") on the Windows machine is a signed 32 bits word that can only fit numbers up to 2,147,483,647. Notice that not only did program produce invalid output but there was no warning whatsoever. C# by default does not check arithmetic calculations for overflow. This is dictated by the pertformance requirements. Any type of the checking would slow programs and in most everyday cases checking for overflow is not required.

But there are situations like the one in the example above when the programmer cannot be sure of the data size. To avoid this type of error users should use "Checked" keyword. The modified program would look like this,

  1. public static void Main(string[] args)  
  2. {  
  3.    try  
  4.    {  
  5.       int n = 0;  
  6.       for (n = 1; n <= 25; n++)  
  7.       {  
  8.          int f = 1;  
  9.          int k = n;  
  10.          while (k > 1)  
  11.          {  
  12.             checked // Calculations will be checked for overflow  
  13.             {  
  14.                f *= k;  
  15.             }  
  16.             k--;  
  17.          }  
  18.          Console.WriteLine("Factorial of {0} is: {1}", n, f);  
  19.       }  
  20.    }  
  21.    catch (OverflowException)  
  22.    {  
  23.         Console.WriteLine("Overflow");  
  24.    }  
  25.    Console.WriteLine();  
  26.    Console.Write("Press any key to continue . . . ");  
  27.    Console.ReadKey(true);  
  28. }  
The improved program would produce the following output,
 
Factorial of 1 is: 1
Factorial of 2 is: 2
Factorial of 3 is: 6
Factorial of 4 is: 24
Factorial of 5 is: 120
Factorial of 6 is: 720
Factorial of 7 is: 5040
Factorial of 8 is: 40320
Factorial of 9 is: 362880
Factorial of 10 is: 3628800
Factorial of 11 is: 39916800
Factorial of 12 is: 479001600
Overflow
Press any key to continue . . .
 
The good news here is that we've got a warning of failure and the program terminated. The bad news is that we have not finished the desired calculations. With factorial function growth rate traditional C# variables are not the best choice here though. Even unsigned long ones will overflow before achieving a factorial of 25. If precision isn't  critical one could use float or double for "f" variable to achieve approximation. However the best choice in this case would be to use .NET Numerics library. The library provides structure "BigInteger" that has "unlimited"size. In reality size is limited by the available memory.
 
The program that calculated factotrial properly and precisely should be written as, 
  1. using System;  
  2. using System.Numerics;  
  3.   
  4. namespace BigNum  
  5. {  
  6.    class Program  
  7.    {  
  8.       public static void Main(string[] args)  
  9.       {  
  10.          int n = 0;  
  11.          for (n = 1; n <= 25; n++)  
  12.          {  
  13.             BigInteger f = 1;  
  14.             int k = n;  
  15.             while (k > 1)  
  16.             {  
  17.                f *= k;  
  18.                k--;  
  19.             }  
  20.             Console.WriteLine("Factorial of {0} is: {1}", n, f);  
  21.          }  
  22.          Console.WriteLine();  
  23.          Console.Write("Press any key to continue . . . ");  
  24.          Console.ReadKey(true);  
  25.       }  
  26.    }  
  27. }  
And the output of the program will look like this,
 
Factorial of 1 is: 1
Factorial of 2 is: 2
Factorial of 3 is: 6
Factorial of 4 is: 24
Factorial of 5 is: 120
Factorial of 6 is: 720
Factorial of 7 is: 5040
Factorial of 8 is: 40320
Factorial of 9 is: 362880
Factorial of 10 is: 3628800
Factorial of 11 is: 39916800
Factorial of 12 is: 479001600
Factorial of 13 is: 6227020800
Factorial of 14 is: 87178291200
Factorial of 15 is: 1307674368000
Factorial of 16 is: 20922789888000
Factorial of 17 is: 355687428096000
Factorial of 18 is: 6402373705728000
Factorial of 19 is: 121645100408832000
Factorial of 20 is: 2432902008176640000
Factorial of 21 is: 51090942171709440000
Factorial of 22 is: 1124000727777607680000
Factorial of 23 is: 25852016738884976640000
Factorial of 24 is: 620448401733239439360000
Factorial of 25 is: 15511210043330985984000000
Press any key to continue . . .
 
This last program would easily get you to factorial 1000! or higher with precision. Here it is for fun,
 
Factorial of 1000 is,
 
402387260077093773543702433923003985719374864210714632543
7999104299385123986290205920442084869694048004799886101971
960586316668729948085589013238296699445909974245040870737
599188236277271887325197795059509952761208749754624970436
014182780946464962910563938874378864873371191810458257836
478499770124766328898359557354325131853239584630755574091
14262417474349347553428646576611667797396668820291207379
143853719588249808126867838374559731746136085379534524221
586593201928090878297308431392844403281231558611036976801
3573042161687476096758713
4831202547858932076716913244842623613141250878020800026168
3151027341827977704784635868170164365024153691398281264810
2130927612448963599287051149649754199093422215668325720808
2133318611681155361583654698404670897560290095053761647584
7728421889679646244945160765353408198901385442487984959953
3191017233555566021394503997362807501378376153071277619268
4903435262520001588853514733161170210396817592151090778801
9393178114194545257223865541461062892187960223838971476088
5062768629671466746975629112340824392081601537808898939645
1826324367161676217916890977991190375403127462228998800519
544441428201218736174599264295658174662830295557029902432
415318161721046583203678690611726015878352075151628422554
026517048330422614397428693306169089796848259012545832716
822645806652676995865268227280707578139185817888965220816
434834482599326604336766017699961283186078838615027946595
513115655203609398818061213855860030143569452722420634463
179746059468257310379008402443243846565724501440282188525
247093519062092902313649327349756551395872055965422874977
4011413346962715422845862377387538230483865688976461927383
814900140767310446640259899490222221765904339901886018566
5264850617997023561938970178600408118897299183110211712298
459016419210688843871218556461249607987229085192968193723
886426148396573822911231250241866493531439701374285319266
498753372189406942814341185201580141233448280150513996942
901534830776445690990731524332782882698646027898643211390
835062170950025973898635542771967428222487575867657523442
202075736305694988250879689281627538488633969099598262809
561214509948717012445164612603790293091208890869420285106
401821543994571568059418727489980942547421735824010636774
045957417851608292301353580818400969963725242305608559037
006242712434169090041536901059339838357779394109700277534
720000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000
00000000000000000000000 
 
Truly the BigInteger at work here. Obviously the price to pay is performance. BigInteger consumes far more CPU clock cycles than conventional C# data types.