Klasa BigInteger

Dziś trochę o .NET Framework i strukturze BigInteger. Myślę, że komuś może to oszczędzić czasu na pisaniu własnych implementacji. Każdy z nas korzystał z typów takich jak short, int czy long. W większości przypadków w zupełności one wystarczającą i pokrywają większość scenariuszy. Czasami jednak aplikacja musi operować na dużo większych liczbach niż 32 czy 64 bitowe zmienne.

BigInteger potrafi przechować dowolnie wielką liczbę całkowitą (ujemną lub dodatnią). BigInteger znajduje się w bibliotece System.Numerics i należy ją najpierw dołączyć do projektu aby móc korzystać z tej struktury. Inicjalizacja struktury może odbywać się na kilka sposób. Na przykład możliwe jest przekazanie liczby za pomocą konstruktora:

BigInteger bigInteger1 = new BigInteger(13131.424f);
BigInteger bigInteger2 = new BigInteger(242);

Console.WriteLine(bigInteger1);
Console.WriteLine(bigInteger2);

Liczba zmiennoprzecinkowa (float) zostanie ucięta i zamiast 13131.424, zostanie przechowana wartość 13131, co wydaje się naturalne. Konstruktor jednak przyjmuje różne typy danych takie jak decimal, int, float itp.

Możliwe jest również operacja przypisania:

int smallNumber = 343;
BigInteger bigNumber = smallNumber;

Console.WriteLine(bigNumber);

Z powyższego kodu wynika, że BigInteger zachowuje się jak zwykła zmienna (struktura posiada przeładowane operatory).

Praktyczniejszym chyba jednak przykładem inicjalizacji BigInteger jest przekazanie tablicy bajtów. Oczywiście w praktyce przekazuje się duże tablice, opisujące wielkie liczby ale dla celów wpisu ograniczymy się do dwuelementowej tablicy bajtów – łatwiej będzie to zrozumieć:

byte[] number = new byte[] {5, 10};
BigInteger bigNumber = new BigInteger(number);

Console.WriteLine(bigNumber);

Cyfra pięć w zapisie binarnym to: 00000101 z kolei dziesięć: 00001010. Łącząc te wartości otrzymujemy 0000101000000101 (little endian ) co w zapisie dziesiętnym daje 2565. Taką właśnie liczbę otrzymamy wywołując Console.WriteLine na BigInteger.

Równie praktyczną metodą jest parsowanie stirng’a:

BigInteger bigNumber = BigInteger.Parse("3543453543636341112414141");
Console.WriteLine(bigNumber);

Parse zachowuje się jak Int.Parse. Jeśli podana wartość to nieprawidłowa liczba, zostanie wyrzucony wyjątek. Z tego względu lepiej to zapisać:

try
{
    BigInteger bigNumber = BigInteger.Parse("3543453543636341112414141");
    Console.WriteLine(bigNumber);
}
catch (FormatException)
{
    Console.WriteLine("Błąd");
}

Istnieje również funkcja TryParse, która jest lepsza w przypadkach gdy nieprawidłowy input jest czymś normalnym:

BigInteger bigNumber;
if (BigInteger.TryParse("3543453543636341112414141", out bigNumber))
  Console.WriteLine(bigNumber);
else
  Console.WriteLine("Błąd");

BigInteger ma kilka przydatnych metod do wykonywania operacji matematycznych. Na przykład za pomocą Pow można podnieść liczbę do dowolnej potęgi:

BigInteger bigNumber1 = BigInteger.Parse("3543453543636341112414141");
bigNumber1=BigInteger.Pow(bigNumber1, 10);
Console.WriteLine(bigNumber1);

Ponadto, jak wspomniałem, BigInteger przeładowuje operatory dzięki czemu w łatwy sposób można wykonać operacje dodawania, mnożenia itp.:

BigInteger bigNumber1 = BigInteger.Parse("3543453543636341112414141");
Console.WriteLine(bigNumber1*bigNumber1);
Console.WriteLine(bigNumber1+bigNumber1);
Console.WriteLine(bigNumber1/bigNumber1);
Console.WriteLine(bigNumber1-bigNumber1);

BigInteger został tak zaprojektowany, aby maksymalnie przypominać zwykłe typy (int, long). Oprócz podstawowych operatów (+,/,-,*) można wykonywać operacje na bitach (shift, XOR itp).

Należy pamiętać, że struktura BigInteger jest immutable. Oznacza to, że nie możemy zmodyfikować żadnego pola wewnętrznego tego pola. Operacja Pow() zwraca nowy obiekt, podobnie ze standardową sumą czy ilorazem. Dla powyższych przykładów jest to naturalne jednak dla inkrementacji okazuje się to bardzo mylące:

BigInteger bigNumber1 = BigInteger.Parse("1");
bigNumber1++;
Console.WriteLine(bigNumber1);

Funkcja nie modyfikuje tutaj bigNumber1 a tworzy nowy obiekt i następnie przypisuje go do bigNumber1. Efekt końcowy jest taki sam, ale implementacja wewnętrzna wykonuje więcej operacji niż mogłoby się wydawać. Z tego względu lepsze jest (jeśli to możliwe) inkrementowanie zwykłego Int32 lub Int64 a następne wykonanie operacji Add na BigInteger. Inkrementacja w pętli BigInteger nie jest dobrym pomysłem.

Leave a Reply

Your email address will not be published.