Liczba π (pi) to nietypowa liczba niewymierna. Towarzyszy nam od wieków, uczy się o niej dzieci, a wciąż wzbudza wielkie emocje nie tylko wśród matematyków.

Dzień Liczby Pi przypada na 14-tego marca. 🇺🇸 Amerykański zapis tej daty to 3.14, a to jest właśnie przybliżenie pi o którym uczymy się w szkole podstawowej.

Dlaczego pi jest tak ważne? Otóż w historii ludzkości koło było jednym z ważniejszych wynalazków. Zmieniło całkowicie oblicze transportu lądowego. A liczba pi to stosunek obwodu koła do długości średnicy tego koła. Mówiąc inaczej znając średnicę koła (podwojony promień koła) oraz liczbę π można określić długość okręgu, czyli jaką odległość “koło przejechało” po wykonaniu pełnego obrotu.

π

Gdyby π było liczbą naturalną lub nawet liczbą wymierną np. 3.14 to dla Ruby developera wyglądałaby tak:

Math::PI
 => 3.141592653589793

W większości przypadków takie przybliżenie liczby pi w języku Ruby wystarczy. Dokładność liczby π w tym przypadku wynika z ograniczeń klasy Float.

Math::PI.class
 => Float

Nawet jeśli sami spróbujemy podać w Ruby dokładniejszą wartość liczby pi to zostanie ona “obcięta” do przedstawionej stałej.

3.141592653589793238462643383279
 => 3.141592653589793

Aby przedstawić liczbę pi zawierającą więcej cyfr po przecinku trzeba podejść do problemu bardziej kreatywnie. Możemy użyć tablicy (klasa Array) i poszczególne cyfry zapisywać jako osobne elementy tej tablicy lub użyć łańcucha znaków (klasa String). Skorzystamy z drugiego sposobu, ze względu na łatwość zapisu.

pi_as_string = '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912983367336244065664308602139494639522473719070217986094370277053921717629317675238467481846766940513200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892354201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859502445945534690830264252230825334468503526193118817101000313783875288658753320838142061717766914730359825349042875546873115956286388235378759375195778185778053217122680661300192787661119590921642019893809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151557485724245415069595082953311686172785588907509838175463746493931925506040092770167113900984882401285836160356370766010471018194295559619894676783744944825537977472684710404753464620804668425906949129331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279678235478163600934172164121992458631503028618297455570674983850549458858692699569092721079750930295532116534498720275596023648066549911988183479775356636980742654252786255181841757467289097777279380008164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333454776241686251898356948556209921922218427255025425688767179049460165346680498862723279178608578438382796797668145410095388378636095068006422512520511739298489608412848862694560424196528502221066118630674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009946576407895126946839835259570982582262052248940772671947826848260147699090264013639443745530506820349625245174939965143142980919065925093722169646151570985838741059788595977297549893016175392846813826868386894277415599185592524595395943104997252468084598727364469584865383673622262609912460805124388439045124413654976278079771569143599770012961608944169486855584840635342207222582848864815845602850601684273945226746767889525213852254995466672782398645659611635488623057745649803559363456817432411251507606947945109659609402522887971089314566913686722874894056010150330861792868092087476091782493858900971490967598526136554978189312978482168299894872265880485756401427047755513237964145152374623436454285844479526586782105114135473573952311342716610213596953623144295248493718711014576540359027993440374200731057853906219838744780847848968332144571386875194350643021845319104848100537061468067491927819119793995206141966342875444064374512371819217999839101591956181467514269123974894090718649423196156794520809514655022523160388193014209376213785595663893778708303906979207734672218256259966150142150306803844773454920260541466592520149744285073251866600213243408819071048633173464965145390579626856100550810665879699816357473638405257145910289706414011097120628043903975951567715770042033786993600723055876317635942187312514712053292819182618612586732157919841484882916447060957527069572209175671167229109816909152801735067127485832228718352093539657251210835791513698820914442100675103346711031412671113699086585163983150197016515116851714376576183515565088490998985998238734552833163550764791853589322618548963213293308985706420467525907091548141654985946163718027098199430992448895757128289059232332609729971208443357326548938239119325974636673058360414281388303203824903758985243744170291327656180937734440307074692112019130203303801976211011004492932151608424448596376698389522868478312355265821314495768572624334418930396864262434107732269780280731891544110104468232527162010526522721116603966655730925471105578537634668206531098965269186205647693125705863566201855810072936065987648611791045334885034611365768675324944166803962657978771855608455296'

Powyższe rozwinięcie liczby pi w postaci napisu pochodzi ze strony Wolfram Alpha. Sprawdźmy liczbę cyfr po przecinku.

pi_as_string.size
 => 4159

Należy pamiętać, że liczba 4159 to długość całego Stringa, który składa się z również z cyfry 3 oraz “.” (kropki). Rzeczywista ilość cyfr rozwinięcia π w naszym przypadku to 4157. Zapis ten niestety nie pozwala nam na wykonywanie obliczeń. Spróbujmy więc użyć innego rozwiązania w Ruby jakim jest klasa BigDecimal.

Według dokumentacji klasa BigDecimal zapewnia arytmetykę dziesiętną zmiennoprzecinkową o dowolnej precyzji. Sprawdźmy to!

Na początku musimy intencjonalnie załadować tę klasę. Pomimo iż BigDecimal należy do standardowej biblioteki Ruby to nie jest domyślnie ładowana.

require 'bigdecimal'
 => true

BigDecimal na wejście może dostać liczbę całkowitą lub napis.

BigDecimal(0)
 => 0.0
BigDecimal(1)
 => 0.1e1
BigDecimal(10)
 => 0.1e2
BigDecimal(256)
 => 0.256e3
BigDecimal(-1000)
 => -0.1e4
BigDecimal('1024')
 => 0.1024e4
BigDecimal('0.5')
 => 0.5e0
BigDecimal('3.141592653589793238462643383279')
 => 0.3141592653589793238462643383279e1

Najważniejsze jest jednak to, że BigDecimal pozwala na obliczenia matematyczne z zachowaniem należytej precyzji. Dla porównania zestawimy wyniki obliczeń używając stałej Math::PI oraz liczby wyrażonej przy pomocy BigDecimal.

Math::PI
 => 3.141592653589793
Math::PI * 0.5
 => 1.5707963267948966
Math::PI * 0.25
 => 0.7853981633974483
Math::PI * 0.125
 => 0.39269908169872414
Math::PI ** 2
 => 9.869604401089358

BigDecimal('3.141592653589793')
 => 0.3141592653589793e1
BigDecimal('3.141592653589793') * 0.5
 => 0.15707963267948965e1
BigDecimal('3.141592653589793') * 0.25
 => 0.78539816339744825e0
BigDecimal('3.141592653589793') * 0.125
 => 0.392699081698724125e0
BigDecimal('3.141592653589793') ** 2
 => 0.9869604401089357120529513782849e1

Można zauważyć, że obliczenia wykonywane na Float są zaokrąglone, podczas, gdy obliczenia a na BigDecimal dokładne. Skorzystajmy ze zmiennej pi_as_string.

pi_number = BigDecimal(pi_as_string)
 => 0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912983367336244065664308602139494639522473719070217986094370277053921717629317675238467481846766940513200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892354201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859502445945534690830264252230825334468503526193118817101000313783875288658753320838142061717766914730359825349042875546873115956286388235378759375195778185778053217122680661300192787661119590921642019893809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151557485724245415069595082953311686172785588907509838175463746493931925506040092770167113900984882401285836160356370766010471018194295559619894676783744944825537977472684710404753464620804668425906949129331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279678235478163600934172164121992458631503028618297455570674983850549458858692699569092721079750930295532116534498720275596023648066549911988183479775356636980742654252786255181841757467289097777279380008164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333454776241686251898356948556209921922218427255025425688767179049460165346680498862723279178608578438382796797668145410095388378636095068006422512520511739298489608412848862694560424196528502221066118630674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009946576407895126946839835259570982582262052248940772671947826848260147699090264013639443745530506820349625245174939965143142980919065925093722169646151570985838741059788595977297549893016175392846813826868386894277415599185592524595395943104997252468084598727364469584865383673622262609912460805124388439045124413654976278079771569143599770012961608944169486855584840635342207222582848864815845602850601684273945226746767889525213852254995466672782398645659611635488623057745649803559363456817432411251507606947945109659609402522887971089314566913686722874894056010150330861792868092087476091782493858900971490967598526136554978189312978482168299894872265880485756401427047755513237964145152374623436454285844479526586782105114135473573952311342716610213596953623144295248493718711014576540359027993440374200731057853906219838744780847848968332144571386875194350643021845319104848100537061468067491927819119793995206141966342875444064374512371819217999839101591956181467514269123974894090718649423196156794520809514655022523160388193014209376213785595663893778708303906979207734672218256259966150142150306803844773454920260541466592520149744285073251866600213243408819071048633173464965145390579626856100550810665879699816357473638405257145910289706414011097120628043903975951567715770042033786993600723055876317635942187312514712053292819182618612586732157919841484882916447060957527069572209175671167229109816909152801735067127485832228718352093539657251210835791513698820914442100675103346711031412671113699086585163983150197016515116851714376576183515565088490998985998238734552833163550764791853589322618548963213293308985706420467525907091548141654985946163718027098199430992448895757128289059232332609729971208443357326548938239119325974636673058360414281388303203824903758985243744170291327656180937734440307074692112019130203303801976211011004492932151608424448596376698389522868478312355265821314495768572624334418930396864262434107732269780280731891544110104468232527162010526522721116603966655730925471105578537634668206531098965269186205647693125705863566201855810072936065987648611791045334885034611365768675324944166803962657978771855608455296e1

BigDecimal pozwala na sprawdzenie precyzji podanej liczby. Przy czym przez precyzję należy rozumieć zarówno cyfry przed przecinkiem jak i po przecinku.

pi_number.precision
 => 4158

Robi wrażenie, prawda? Należy jednak pamiętać, że obliczenia dużej precyzji mogą trwać dłużej niż zakładamy, a przez to stają się niepraktyczne. Zróbmy prosty test. Z góry ostrzegam, że do pomiaru czasu nie będę używał metod z klasy Time tj. Time.now, tylko tzw. “monotonic clock”. ⏲️ Dobra, pora na zadanie, które posłuży nam do testu. Zadaniem jest podniesienie liczby pi do potęgi pi. Wzór: π π

# przygotowanie danych
starting_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# obliczenia
ending_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
reference_time = ending_time - starting_time
 => 0.07567894399835495

Jak widać nawet samo wywołanie metod do pomiaru zajmuje już jakiś czas. Nazwijmy ten wynik czasem referencyjnym. Stanowić on będzie dla nas punkt odniesienia. Oczywiście wynik podawany jest w sekundach. Sprawdźmy więc ile czasu zajmie potęgowanie na liczbach całkowitych.

pi_number = 3
starting_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
pi_number ** pi_number
ending_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
time_difference_for_integers = ending_time - starting_time
 => 0.07845575100145652

Bardzo, bardzo mało. Pamiętajmy, że interesuje nas różnica pomiędzy eksperymentem zawierającym obliczenie potęgi liczb całkowitych, a czasem referencyjnym.

time_difference_for_integers - reference_time
 => 0.002776807003101567

W kolejnym kroku powtórzymy eksperyment na liczbach typu Float.

pi_number = Math::PI
starting_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
pi_number ** pi_number
ending_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
time_difference_for_floats = ending_time - starting_time
 => 0.08089218199893367
time_difference_for_floats - reference_time
 => 0.005213238000578713

Tą samą dokładność liczby pi użyjemy, aby wykonać eksperyment na liczbach typu BigDecimal.

pi_number = BigDecimal(Math::PI.to_s)
starting_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
pi_number ** pi_number
ending_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
time_difference_for_small_decimals = ending_time - starting_time
 => 0.06370469199828221
time_difference_for_small_decimals - reference_time
 => -0.011974252000072738

Uzyskaliśmy dosyć ciekawy przypadek. Czas oczywiście nie może być ujemny i nie jest. Po prostu przedstawione eksperymenty powinniśmy powtórzyć wielokrotnie, a wyniki uśrednić. W tym przypadku prawdopodobnie zwolniły się jakieś zasoby CPU i wynik został wyliczony szybciej niż podczas pomiaru czasu referencyjnego. Przy obliczeniach z tak małą precyzją śmiało można założyć, że pomiar czasu jest bliski zeru.

Pora na zwiększenie precyzji obliczeń.

pi_number = BigDecimal(pi_as_string)
starting_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
pi_number ** pi_number
ending_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
time_difference_for_big_decimals = ending_time - starting_time
 => 181.04171283699907
time_difference_for_big_decimals - reference_time
 => 180.9660338930007

Proste potęgowanie a zajęło ponad 3 minuty. Czy to dużo? Oczywiście, że dużo. Czy jest to tego warte? Zależy co jest ważniejsze? Precyzja obliczeń czy czas tych obliczeń. Wszystko zależy od postawionego problemu.

Warto wiedzieć, że biblioteka bigdecimal w Ruby posiada pewne rozwiązania, które mogą ułatwić nam pracę.

require 'bigdecimal/util'
 => true

Dodatek ten umożliwia prostą zamianę liczb typu Integer lub liczb zapisanych w obiekcie typu String na obiekt BigDecimal. Służy do tego metoda to_d.

0.to_d
 => 0.0
42.to_d
 => 0.42e2
'3.14'.to_d
 => 0.314e1

Pozostając w temacie liczby pi istnieje w języku Ruby również bigdecimal/math. Moduł ten dostarcza metodę PI, która oblicza wartość liczby pi z określoną liczbą cyfr dokładności. Argument wejściowy nie oznacza liczby cyfr, ale precyzję obliczeń.

require 'bigdecimal/math'
 => true

BigMath.PI(1)
 => 0.31415926535897932364198143965603e1
BigMath.PI(2)
 => 0.31415926535897932384671233672993238432e1
BigMath.PI(7)
 => 0.3141592653589793238462641821456037373156626e1
BigMath.PI(12)
 => 0.314159265358979323846264338336544487694763327962e1

Zwiększenie precyzji o 1 nie zawsze da dokładniejszy wynik. Podane powyżej przykłady pomijają argumenty, które dawały takie same wyniki. Aby osiągnąć precyzję podobną do wartości BigDecimal(pi_as_string) musimy na wejście podać 4124.

big_math_pi_number = BigMath.PI(4124)
 => 0.3141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381...
big_math_pi_number.precs
 => [4176, 9243]
big_math_pi_number.to_s.size
 => 4164

Mam nadzieję, że udało mi się pokazać wyjątkowość liczby pi oraz to jak można tę liczbę przedstawić w języku Ruby. 🎉


Spodobało Ci się? Napisz nam o tym i czytaj nas regularnie. Możesz również podzielić się tym artykułem z innymi (Facebook, Twitter). Będzie nam niezmiernie miło.

Zostaw komentarz