- Autor: Krzysztof Molenda
- Wersja: 1.45 (2022.03.16)
Twoim zadaniem jest realizacja w C# typu Rational, reprezentującego matematyczną koncepcję liczby wymiernej. Realizacja typu powinna być spójna, kompletna, dobrze udokumentowana i przetestowana. Powinna dostarczać naturalnych dla użytkownika mechanizmów użycia (naturalnych, tzn. jak w matematyce oraz podobnych do tych, które stosowane są w analogicznych konstrukcjach języka C#).
Zadanie to ma charakter edukacyjny. Jest ono realizowane na większości kursów programowania obiektowego, ze względu na odpowiednią złożoność oraz zakres tematyczny.
Realizując je:
- Poznasz kolejne etapy projektowania typu.
- Nauczysz się panować nad złożonym kodem oraz wieloma implementowanymi funkcjonalnościami. Dowiesz się, jak 'łańcuchować' kod.
- Poznasz techniki tworzenia testów jednostkowych dla Twojego kodu. Dowiesz się, na czym polega technika TDD (Test-Driven Development).
- Nauczysz się poprawnie dokumentować swój kod i generować dokumentację, np. w
html.
Można jednak znaleźć inne uzasadnienia utworzenia takiego typu danych w C#.
Uruchom, w ramach eksperymentu, poniższy kod C#, który wyjaśni wszystko:
Console.WriteLine( 0.1 + 0.2 == 0.3 );Błędny wynik spowodowany jest niedokładnością binarnej reprezentacji liczb zmiennoprzecinkowych. Częściowym rozwiązaniem tego problemu może byc praca na wartościach dokładnych - liczbach reprezentowanych w formie ułamków.
Platformy .NET dostarczają predefiniowane typy liczbowe:
-
sbyte,byte,short,ushort,int,uint,long,ulong- reprezentacja liczb całkowitych, bez utraty dokładności, w formie typu wartościowego -
float,double- zmiennoprzecinkowa reprezentacja liczb rzeczywistych, z potencjalną utratą dokładności, w formie typu wartościowego (odpowiednio 32-bitowa i 64-bitowa) -
Ciekawostka: w .Net 5 zaimplementowano typ
System.Half- 16-bitowa zmiennoprzecinkowa reprezentacja liczby rzeczywistej (https://docs.microsoft.com/en-us/dotnet/api/system.half), w formie typu wartościowego -
decimal- reprezentacja liczb rzeczywistych, ze zwiększoną dokładnością, ale pamięciożerna (128 bitów), w formie typu wartościowego -
BigInteger- reprezentacja liczby całkowitej o dowolnym (praktycznie nieograniczonym) zakresie (potrzebne w kryptografii) - w przestrzeni nazwSystem.Numerics -
Complex- reprezentacja liczby zespolonej - w przestrzeni nazwSystem.Numerics
W standardowych bibliotekach .NET (C#) jednak nie ma typu realizującego koncepcję ułamka (Rational). Jej implementacja jest za to w pakiecie Microsoft Solver Foundation, zrealizowana w formie struktury, w dość specyficzny sposób i z bardzo ubogą dokumentacją (prawdopodobnie tylko dla potrzeb narzędzia Solver). Implementacja ta nie jest rozwijana/utrzymywana.
Przy tworzeniu specjalistycznego oprogramowania może okazać się potrzebną realizacja typu Rational, np. do prezentacji liczb w formie ułamkowej, do wykonania obliczeń dokładnych na ułamkach.
Zadanie to można zrealizować w 3 wariantach:
- Reprezentacja wewnętrzna ułamka (
Rational32) bazująca na typieint(dla licznika i mianownika). - Reprezentacja wewnętrzna ułamka (
Rational64) bazująca na typieulong(dla licznika i mianownika), wymagane będzie zapamiętanie znaku ułamka. - Reprezentacja wewnętrzna ułamka (
BigRational) bazująca na typieBigInteger(dla licznika i mianownika).
Założenia ogólne dotyczące implementacji typu Rational:
-
Implementowany typ realizuje matematyczną koncepcję liczby wymiernej (https://en.wikipedia.org/wiki/Rational_number).
-
Realizacja typu w formie struktury języka C#.
-
Instancje typu są niezmiennicze (immutable).
-
Licznik i mianownik widziane są przez użytkownika końcowego jako wartości typu
BigInteger. -
Typ
Rationalreprezentuje koncepcję liczby, zatem jego instancje "współdziałają" z liczbami reprezentowanymi przez inne typy (int, ...,double, ...). Współdziałanie to obejmuje konwersje (jawne i niejawne), porównywanie (operatory relacyjne) oraz działania arytmetyczne. -
Wszystkie publiczne składniki klasy są przetestowane (odpowiednie testy jednostkowe).
-
Klasa oraz jej składniki publiczne są udokumentowane (dokumentacja XML w kodzie).
-
Dokumentacja API w
html. -
W miarę możliwości należy wykorzystać nowe możliwości C# (C#8 i wyżej).
W poniższym opracowaniu przedstawione są etapy realizacji typu w wariancie BigRational. Po zakończeniu ćwiczenia sugerowane jest - dla utrwalenia wiedzy i umiejętności - realizacja typu w wariantach Rational32 oraz Rational64. Pojawią się drobne różnice i niuanse implementacyjne.
Zachęcam do zapoznania się z kodem źródłowym implementacji bibliotecznych struktur:
BigInteger- struktura bazowa do realizacji typuBigRationalComplex- struktura podobna doRational- też para liczb, ale o innej interpretacji- Niezależnej realizacji typu: https://github.com/tompazourek/Rationals - udostepnionej w formie pakietu NuGet - implementacja nieznacznie różni się od prezentowanej tutaj
- Niezależnej realizacji typu, poddanej ocenie przez użytkowników Stack Overflow https://codereview.stackexchange.com/questions/95681/rational-number-implementation
Zadanie zrealizuj w Visual Studio 2022 i .Net6. Możesz również wykorzystać lekkie środowisko VS Code lub JetBrains Rider.
Każdy z podanych poniżej kroków przedstawia:
- instrukcje do wykonania
- podpowiedzi
- gotowe pełne lub częściowe rozwiązanie
Sugeruję próbę samodzielnego rozwiązania w oparciu o instrukcje i podpowiedzi, zaś później weryfikację w oparciu o przedstawioną propozycję rozwiązania!
⚠️ Kolejne kroki realizowane są w trybie przyrastającej wiedzy i umiejętności. Na początku podawane są zagadnienia i techniki stosunkowo proste, dokładnie omówione, z biegiem czasu prezentowane są zagadnienia trudniejsze, omawiane szybciej i pobieżnie. Zatem, wykonuj wszystkie polecenia w podanej kolejności, stosując zasadę: najpierw spróbuj sam, później zobacz, jak robię to ja.
-
Krok 0. Konfiguracja środowiska projektu
utworzysz solution oraz 3 projekty: typu Class Library, Console application oraz Unit test.
-
Krok 1. Podstawowa funkcjonalność
zdefiniujesz wewnętrzną reprezentację danych ułamka (pola) zapewniając niezmienniczość tworzonych obiektów, zdefiniujesz konstruktory oraz tekstową reprezentację ułamka, utworzysz testy jednostkowe
-
Krok 2. Równość/tożsamość ułamków
określisz, kiedy dwa ułamki są sobie równe - tożsame, "takie same" (implementacja
IEquatable,IEquatable<Rational>, generowanie hashkodu, przeciążenie operatorów==oraz!=), utworzysz testy jednostkowe -
zdefiniujesz podstawowe operatory relacyjne (implementacja
IComparable<Rational>, przeciążenie operatorów<,>), utworzysz testy jednostkowe -
zdefiniujesz mechanizmy konwersji (jawnej, niejawnej) z i do ułamka, utworzysz testy jednostkowe
-
Krok 5. Operacje arytmetyczne na ułamkach
zdefiniujesz podstawowe działania arytmetyczne na ułamkach (przeciążenie operatorów arytmetycznych
+,*, ... ) oraz wybrane funkcje użytkowe (Min,Max,Pow, ...) wzorując się na wbudowanych typach liczbowych, utworzysz testy jednostkowe -
Krok 6. Rozszerzenia klasy
BigRationalpoznasz mechanizmy rozszerzania funkcjonalności wcześniej zaprojektowanej klasy
-
Krok 7. Refaktoryzacja i dokumentacja kodu
uporządkujesz kod czyniąc go bardziej czytelnym i łatwiejszym do zarządzania, wprowadzisz optymalizacje, opracujesz/uzupełnisz dokumentację, wygenerujesz dokumentację w
html -
Krok ostatni. Przygotowanie biblioteki. Publikacja biblioteki jako pakietu NuGet