Date
i bez require 'date'
(tj skript mně opravdu fungoval tak jak je). Ale je pravda, že pokud chci něco takového zabalit do binárky přes rubyscript2exe, tak mně to hlásí chybu, která se právě odstraní tím require 'date'
. Domnívám se, že jde o nějakou drobnou nesrovnalost v cestách ke standardní knihovně v různých instalacích nebo operačních systémech.
Čísla (včetně literálů) jsou v Ruby objekty jako každé jiné. Můžete tedy například psát 5.times
, pokud chcete něco pětkrát iterovat. Všechny metody, které se dají volat na to "číslo 5" dostanete např. pomocí 5.methods
.
Co se týče celočíselných operací, tak datový typ Fixnum
drží celočíselnou proměnnou o max délce slova na dané architektuře. Pokud při nějakých operacích by něco mělo přetéct, je to automaticky konvertováno na Bignum
(celočíselný typ bez omezení délky). A naopak, pokud při operacích s Bignum
vyjde číslo, které se vejde do "slova", je automaticky konvertováno na Fixnum
.
Číslo s plovoucí desetinou čárkou jsou ale reprezentována třídou Float
, která odpovídá nativnímu typu 'číslo s plovoucí desetinou čárkou s dvojitou přesností' na dané architektuře. Obávám se, že tam by ke kumulaci chyb mohlo v určitých případech dojít, i když čísla s dvojitou přesností by měla být pro "normální život" dostatečně přesná.
Ruby v základní knihovně obsahuje ještě další třídy pro numerické výpočty - např. Complex
pro komplexní čísla, Matrix
pro práci s maticemi, Rational
pro práci se zlomky a další. Pro náročnější práci s číselnými vektory a maticemi existuje rozšíření Numerical Ruby (to už není v základní knihovně). Zajímavé je i použití Ruby pro výpočty v astrofyzice.
BigDecimal
ze standardní knihovny (je ale potřeba před použitím zadat require 'bigdecimal'
). Počítá decimálně (tedy nikoliv dvojkově), počet desetinných míst se dá nastavit dynamicky na libovolnou hodnotu. Cenou nejspíše bude (nezkoušel jsem) podstatné zpomalení oproti Float
, dále menší pohodlí při použití (konstanta s des. tečkou je typu Float
, konstanta BigDecimal
se musí normálně nainstancovat) a dále to, že tam nejsou automatické převody při přetečení Float
operací (tj. minimálně jeden operand musí být BigDecimal
). Samozřejmě i tak může docházet k zaokrouhlovacím chybám, např. Bignum("1", 90) / 3
bude přesné pouze na devadesát desetinných míst. Pokud by se mělo předcházet i takovýmto chybám, musel by se používat typ Rational
, který tím, že používá zlomky, nemá zaokrouhlovací chyby žádné. Použití zlomků by ale bylo ještě méně pohodlné, protože např. číslo 0.34
by se muselo zadávat jako Rational(34,100)
.
Není nutné používat "třidní" notaci. Pokud vím, tak v Ruby se dají také operátory "přetížit". Přesněji řečeno, nejedná se o typické přetížení jako v C++, spíše se jedná o předefinování standarních metod, které mají infixovou notaci. Nepohodlné by spíše bylo jen zadávání necelých konstant, infixovou notaci aritmetických operátorů mají všechny ty třídy, o kterých se bavíme. Takže například funguje:
a = Rational(1,3) # => 1/3 b = Rational(1,4) # => 1/4 c = Rational(1,5) # => 1/5 d = 1 + a/2 + b*c # => 73/60Výsledek v proměnné d je také typu
Rational
.
Jen pro představu jsem napsal kratičký testík, kdy se k nule přičítala milionkrát setina a měřil jsem čas pro Float (tj. binární reprezentace floating point), BigDecimal (decimální aritmetika s libovolnou přesností) a Rational (aritmetika se zlomky). Časy jsou:
Testík např pro BigDecimal vypadal takto:
t0 = Time.new a = BigDecimal('0', 50) b = BigDecimal('0.01') 1000000.times { a += b } puts Time.new - t0
Nevím. Autor jazyka je pravděpodobně toho názoru, že celočíselné dělení je užitečnější, než zlomky. Nicméně, pokud to tak chcete mít ve vaší aplikaci, tak není problém předefinovat operátor dělení standardní třídy Fixnum
takto:
class Fixnum def /(other) return Rational(self, other) end end
Rezervované slovo return
je v tomto případě nepovinné, dal jsem ho tam kvůli čitelnosti. Potom výraz 1/2
vrací výsledek Rational(1,2)
. Já bych to ale asi bez pořádně silného důvodu nedělal. Trpí tím, IMHO, čitelnost programu a kdoví kde všude se používá celočíselné dělení a jaké by to mohlo mít vedlejší efekty.
>>> class A: ... def __init__(self): ... self.a = 4 ... >>> a1 = A() >>> a2 = A() >>> a1.b = 5 >>> print a1.a 4 >>> print a2.a 4 >>> print a1.b 5 >>> print a2.b Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: A instance has no attribute 'b'