Choinka programisty Ruby
Czy programiści Ruby 💎 programują swoje choinki 🎄 święteczne?
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