Ruby / Rational class

 ( log69 | 2014. augusztus 31., vasárnap - 21:46 )

Lebegőpontossal dolgozni sokszor szívás, mivel nem ábrázolható mindig a decimális véges bináris formában.

$ python
>>> 0.3 - 0.1
0.19999999999999998

Ruby-ban is, de van más lehetőség is:

$ irb
> 0.3 - 0.1
=> 0.19999999999999998
> "0.3".to_r - "0.1".to_r
=> (1/5)
> ("0.3".to_r - "0.1".to_r).to_f
=> 0.2

Mondjuk 20x lassabb is :)

> Benchmark.realtime{ 1000000.times{ 0.3 - 0.1 } }
=> 0.10152744
> Benchmark.realtime{ 1000000.times{ "0.3".to_r - "0.1".to_r } }
=> 2.040160752

http://ruby-doc.org/core/Rational.html

Megj.: Perl és Lua-nál 0.2 jön ki. Kerekítenének vajon a megjelenítésnél?

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Repl.it ruby-ban (1.8.7) kerek 0.2 jön ki, ellenben jruby 1.7.3-ban (tudom nem ez a legfrissebb, de ez van kéznél) szintén 0.19999999999999998.

Repl.it pythonban és luában viszont 0.20000000000000004. :)

$ lua -v
Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio
$ lua -e "print(0.3 - 0.1)"
0.2

$ perl -v
This is perl 5, version 18, subversion 2 (v5.18.2) built for x86_64-linux-thread-multi
$ perl -e "print 0.3 - 0.1"
0.2

Valszeg csak kerekítenek egy adott hiba % alatt.

Egyébként egy alternatíva repl.it helyett:
https://eval.in/186228

Akkor már ideone.com, az több nyelvet ismer (igaz a coffeescriptet, io-t, slasht nem).

Érdekesség:

> 0.3.to_r
=> (5404319552844595/18014398509481984)
> 0.3.to_s
=> "0.3"
> 0.3.to_s.to_r
=> (3/10)

Tehát érdemes string-be konvertálni Rational előtt. Érthető, mivel a kiírt lebegőpontos 0.3 érték a memóriában nem ábrázolható pontosan binárisan, de megcsinálták ezek szerint a Rational osztályban hogy tudjon string-ből konvertálni pontosan.

> Benchmark.realtime{ 1000000.times{ 0.3.to_s.to_r } }
=> 2.922457332

Bináris formában a decimális 0.1 végtelen kettedes tört, így nem igazán meglepő ez. Persze a kiíratásnál akár kerekíthetne is. Például néhány számjeggyel pontosabban számoljon, mint amit megjelenít.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Igen, ez nyelvfüggetlen probléma. Ruby-nál még használható BigDecimal is.

http://ruby-doc.org/stdlib/libdoc/bigdecimal/rdoc/BigDecimal.html

> require "bigdecimal"
=> true
> (BigDecimal("0.3") - BigDecimal("0.1")).to_f
=> 0.2

Hasonló Rational sebességéhez:

> Benchmark.realtime{ 1000000.times{ BigDecimal("0.3") - BigDecimal("0.1") } }
=> 2.900123281

Ez is érdekes, kisebb pontosságot ad BigDecimal hatványozásnál mint a sima Float:

> (0.3 ** 0.1) ** 10
=> 0.29999999999999993
> ((BigDecimal("0.3") ** BigDecimal("0.1")) ** BigDecimal("10")).to_f
=> 0.29999999808741157

Illetve az sem jó megoldás hogy valamennyivel pontosabban számoljon mint ahogy megjelenít, mivel hatványnál már hatványozottan csökken a pontos tizedes jegyek száma.

Felül szerkesztettem és kiírtam egyébként hogy ez a probléma, hogy egyértelmű legyen a blog-om célja.

> a = "5.3".to_r / "6.7".to_r * "4.3".to_r
=> (2279/670)
> a.to_f
=> 3.401492537313433

Rational-ban még jó, hogy kihasználja Ruby arbitrary prec. integer-ét, tehát bármilyen hosszú véges lebegőpontost feltud írni törtként:

a = "3.14159".to_r ** 10
p a
p a.to_f

(9364725646787226922299505202262723704312737441654490401 / 100000000000000000000000000000000000000000000000000)
93647.25646787226