- 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
Rational
reprezentuje 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 typuBigRational
Complex
- 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
BigRational
poznasz 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