README.txt in nio-0.2.3 vs README.txt in nio-0.2.4
- old
+ new
@@ -1,560 +1,519 @@
-=Description
-
-Nio (Numeric input/output) is a Ruby package for text-formatted input/output
-and conversion of scalar numeric types.
-
-This library formats numbers as text numerals and reads them back into numeric
-objects. The numeric types Integer Rational, BigDecimal and Float are supported.
-The numeral format is controlled with Nio::Fmt objects. Conversion between
-the numerical types is also provided.
-
-Nio offers low level services; formats supported are only positional notation and
-types are only scalar.
-This means that composite types such as Complex are not supported
-an that fraction notation (X/Y) is not supported for rationals;
-those are higher level services that could be implemented using Nio
-but are not the subject of this package.
-
-The implementation of Nio is pure Ruby, and not specially fast; but it is complete and accurate.
-
-Nio has some interesting features, though:
-
-* Correctly rounded conversions of all supported types to and from
- positional numerals in any base.
-* Handling of digit repetitions (repeating decimals or, in general, <i>repeating numerals</i>).
- With this method rational numbers can be represented exactly as numerals.
-* Discrimitation of significant digits of the representation of Float in any base.
- (insignificant digits are those that can take any value without altering the Float value they specify.)
-* Floating point tolerance classes
-
-All definitions are inside the module Nio that acts as a namespace, and methods added
-to classes outside of Nio have names that begin with the prefix <tt>nio_</tt>. Only
-the module nio/sugar.rb, which must be required separately, breaks this rule by defining
-some methods such as Float#to_r.
-
-Limitations:
-* The current version does not support UTF-8 or other multi-byte encodings (digits and separators must be one-byte characters).
-* This code is not very fast, since it is implemented in pure Ruby (no C extensions are used).
-* Error handling needs to improve also in future versions, specially on input and format parameters checking.
-
-=Installation
-
-The easiest way to install Nio is using gems:
-
- gem install --remote nio
-
-==Downloads
-
-The latest version of Nio and its source code can be downloaded form
-* http://rubyforge.org/project/showfiles.php?group_id=4445
-
-The source code uses nuweb (a {<i>literate programming system</i>}[http://en.wikipedia.org/wiki/Literate_programming]) to generate
-the Ruby code for Nio. For more details you can download the nuweb source code package, <tt>nio-source</tt>
-and the documented source package, <tt>nio-source-pdf</tt>, which contains PDF files.
-
-
-=Documentation
-
-For a general introduction and some details, read on below.
-
-* See the the API to the Nio::Fmt object for all the <b>formatting options</b>.
-* For <b>type conversions</b> see Fmt.convert().
-* For some notational <b>shortcuts</b> see nio/sugar.rb[link:files/lib/nio/sugar_rb.html].
-* To *extend* the formatting to other types see the documentation for the module Nio::Formattable.
-* If you want to use the <b>floating point tolerance</b> see the classes Nio::Tolerance and Nio::BigTolerance,
- which can be defined with <tt>Nio::Tol()</tt> and <tt>Nio::BigTol()</tt> as described in the module Nio.
-* The function <b>BigDec()</b> is a convenient constructor to define/convert BigDecimals, also described in module Nio.
-
-=Basic use
-
-First we must require the library; we request also the optional nio/sugar.rb for convenient notational shortcuts,
-and include the module Nio to avoid writing too many Nio::s.
-
- require 'rubygems'
- require 'nio'
- require 'nio/sugar'
- include Nio
-
-Let's define a nice number to do some tests:
-
- x = Math.sqrt(2)+100
-
-=== Writing
-
-Now let's try the formatted output:
-
- puts x.nio_write -> 101.41421356237309
- puts x.nio_write(Fmt.mode(:fix,4)) -> 101.4142
- puts x.nio_write(Fmt.mode(:sig,4)) -> 101.4
- puts x.nio_write(Fmt.mode(:sci,4)) -> 1.014E2
- puts x.nio_write(Fmt.mode(:gen,4)) -> 101.4
- puts (1e7*x).nio_write(Fmt.mode(:gen,4)) -> 1.014E9
- puts (1e7*x).nio_write(Fmt.mode(:gen,4).show_plus) -> +1.014E9
- puts x.nio_write(Fmt.mode(:gen,:exact)) -> 101.41421356237309
-
-We've seen some formatting modes:
-* <tt>:fix</tt> (similar to F in C printf or in Fortran) which shows a number of
- _fixed_ decimals (4 in the example).
-* <tt>:sig</tt> is just like :fix, but the number of decimals specified means
- significant digits.
-* <tt>:sci</tt> (similar to E in C printf or in Fortran) is for scientific
- or exponential notation.
-* <tt>:gen</tt> (similar to G in C printf or in Fortran) is the general notational
- (which also the default), the number of decimals is for significant digits, and
- :sig is used if possible; if it would be too long it uses :sci instead.
-In one example above we used <tt>show_plus</tt> to force the display of the sign.
-And in the last line we used <tt>:exact</tt> for the number of digits, (which
-is also the default), and this adjust automatically the number of digits to
-show the value _exactly_, meaning that if we convert the numeral back to a
-numeric object of the original type, the same exact value will be produced.
-
-Now let's see some other formatting aspects. The separators can be defined:
-
- x *= 1111
- fmt = Fmt.mode(:fix,4)
- puts x.nio_write(fmt.sep(',')) -> 112671,1913
- puts x.nio_write(fmt.sep(',','.',[3])) -> 112.671,1913
- puts x.nio_write(fmt.sep(',',' ',[2,3])) -> 1 126 71,1913
-
-The number can be adjusted in a field of specific width:
-
-fmt = Fmt.mode(:fix,2)
- puts 11.2.nio_write(fmt.width(8)) -> 11.20
- puts 11.2.nio_write(fmt.width(8,:right,'*')) -> ***11.20
- puts 11.2.nio_write(fmt.width(8,:right,'*').show_plus) -> **+11.20
- puts 11.2.nio_write(fmt.width(8,:internal,'*').show_plus) -> +**11.20
- puts 11.2.nio_write(fmt.width(8,:left,'*')) -> 11.20***
- puts 11.2.nio_write(fmt.width(8,:center,'*')) -> *11.20**
- puts 11.2.nio_write(fmt.pad0s(8)) -> 00011.20
- puts BigDec(11.2).nio_write(fmt.pad0s(8)) -> 00011.20
- puts Rational(112,10).nio_write(fmt.pad0s(8)) -> 00011.20
- puts 112.nio_write(fmt.pad0s(8)) -> 00112.00
-
-The numerical base does not need to be 10:
-
- puts 34222223344.nio_write(fmt.base(16)) -> 7f7cdaff0.00
- puts x.nio_write(Fmt.base(16)) -> 1b81f.30F6ED22C
- puts x.nio_write(Fmt.mode(:fix,4).base(2)) -> 11011100000011111.0011
- puts 1.234333E-23.nio_write(Fmt.base(2).prec(20)) -> 1.1101110110000010011E-77
-
-The sugar module give us some alternatives for the writing notation:
-
- puts Fmt << x -> 112671.1912677965
- puts Fmt.mode(:fix,4) << x -> 112671.1913
-
- puts Fmt.write(x) -> 112671.1912677965
- puts Fmt.mode(:fix,4).write(4) -> 4.0000
-
-===Reading
-
-To read a numeral we must specify the numeric class we want to convert it to:
-
- puts Float.nio_read('0.1') -> 0.1
- puts BigDecimal.nio_read('0.1') -> 0.1E0
- puts Rational.nio_read('0.1') -> 1/10
- puts Integer.nio_read('0.1') -> 0
-
-A format can also be specified, although some aspects (such as the precision)
-will be ignored.
-
- puts Float.nio_read('0,1',Fmt.sep(',')) -> 0.1
- puts Float.nio_read('122.344,1',Fmt.sep(',')) -> 122344.1
- puts Float.nio_read('122,344.1',Fmt.sep('.')) -> 122344.1
-
-There are also some sweet alternatives for reading:
-
- puts Fmt.read(Float,'0.1') -> 0.1
- puts Fmt.sep(',').read(Float,'0,1') -> 0.1
-
- puts Fmt >> [Float, '0.1'] -> 0.1
- puts Fmt.sep(',') >> [Float, '0,1'] -> 0.1
-
-===Floating point
-
-Now let's see something trickier; we will use the floating point result
-of dividing 2 by 3 and will use a format with 20 fixed digits:
-
- x = 2.0/3
- fmt = Fmt.mode(:fix,20)
-
- puts x.nio_write(fmt) -> 0.66666666666666663
-
-If you count the digits you will find only 17. Where are the other 3?
-
-Here we're dealing with an approximate numerical type, Float, that has a limited
-internal precision and we asked for a higher precision on the output, which we
-didn't get. Nio refuses to show non-significant digits.
-
-We can use a placeholder for the digits so that Nio shows us something rather than
-just ignoring the digits:
-
- puts x.nio_write(fmt.insignificant_digits('#')) -> 0.66666666666666663###
-
-Nio is hiding those digits because it assumes that the Float value is an approximation,
-but we can force it to actually compute the mathematical _exact_ value of the Float:
-
- puts x.nio_write(fmt.approx_mode(:exact)) -> 0.66666666666666662966
-
-===Default format
-
-The default format Fmt.default is used when no format is specified;
-it can be changed by assigning to it:
-
- Fmt.default = Fmt.default.sep(',')
- puts 1.23456.nio_write -> 1,23456
- puts 1.23456.nio_write(Fmt.prec(3)) -> 1,23
-
-But note that Fmt.new doesn't use the current default (it's
-the hard-wired value at which Fmt.default starts):
-
- puts 1.23456.nio_write(Fmt.new.prec(3)) -> 1.23
-
-There are also other named prefined formats:
-
- puts 123456.78.nio_write(Fmt[:dot]) -> 123456.78
- puts 123456.78.nio_write(Fmt[:dot_th]) -> 123,456.78
- puts 123456.78.nio_write(Fmt[:comma]) -> 123456,78
- puts 123456.78.nio_write(Fmt[:code]) -> 123456,78
-
-The <tt>_th</tt> indicates that thousands separators are used;
-the :code format is intended for programming languages such as Ruby, C, SQL, etc.
-These formats can be changed by assigning to them, and also other named formats
-can be defined:
-
- Fmt[:code] = Fmt.new.prec(1)
- puts 123456.78.nio_write(Fmt[:code]) -> 123456.8
- Fmt[:locale_money] = Fmt.sep(',','.',[3]).prec(:fix,2)
- puts 123456.78.nio_write(Fmt[:locale_money]) -> 123.456,78
-
-===Conversions
-
-Nio can also convert values between numerical types, e.g. from Float to Rational:
-
- puts Nio.convert(2.0/3, Rational) -> 2/3
- puts Nio.convert(2.0/3, Rational, :exact) -> 6004799503160661/9007199254740992
-
-The default (approximate) conversions assumes that the value is inexact and tries to find
-a nice simple value near it. When we request <tt>:exact</tt> conversion the actual internal value
-of the floating point number is preserved.
-
-Let's see some more examples:
-
- puts Nio.convert(2.0/3, BigDecimal) -> 0.666666666666666666666667E0
- puts Nio.convert(2.0/3, BigDecimal, :exact) -> 0.66666666666666662965923251249478198587894439697265625E0
- puts Nio.convert(Rational(2,3), Float) -> 0.666666666666667
- puts Nio.convert(Rational(2,3), BigDecimal) -> 0.666666666666666666666667E0
- puts Nio.convert(BigDecimal('2')/3, Rational) -> 2/3
- puts Nio.convert(BigDecimal('2')/3, Rational, :exact) -> 666666666666666666666667/1000000000000000000000000
- puts Nio.convert(2.0/3, BigDecimal) -> 0.666666666666666666666667E0
- puts Nio.convert(2.0/3, BigDecimal, :exact) -> 0.66666666666666662965923251249478198587894439697265625E0
- puts Nio.convert(BigDecimal('2')/3, Float) -> 0.666666666666667
- puts Nio.convert(BigDecimal('2')/3, Float, :exact) -> 0.666666666666667
-
-
-
-=Details
-
-===Defining formats
-
-Say you want a numeric format based on the current default but with some aspects
-changed, e.g. using comma as the decimal separator and with only 3 digits
-of precision, we could do:
-
- fmt = Fmt.default
- fmt = fmt.sep(',')
- fmt = fmt.prec(3)
-
-That wasn't very nice. Fmt is a mutable class and have methods to modify
-its state that end with a bang. We can use them to make this look better,
-but note that we must create a copy of the default format before we
-modify it:
-
- fmt = Fmt.default.dup
- fmt.sep! ','
- fmt.prec! 3
-
-Note that we had to make a duplicate of the default format in
-order to modify it or we would have got an error
-(if you want to modify the default format
-you have to assign to it).
-
-Now we can simplify this a little by passing a block to Fmt.default:
-
- fmt = Fmt.default { |f|
- f.sep! ','
- f.prec! 3
- }
-
-But there's a more concise way to define the format by avoiding
-the bang-methods and chaining all modifications:
-
- fmt = Fmt.default.sep(',').prec(3)
-
-Or even (using shortcut methods such as Fmt.sep or Fmt.prec):
-
- fmt = Fmt.sep(',').prec(3)
-
-If we don't want to base the new format on the current default, but use
-the initial default instead, we would substitute new for default above,
-except in the last case which always uses the default.
-For example:
-
- fmt = Fmt.new { |f|
- f.sep! ','
- f.prec! 3
- }
-
- fmt = Fmt.new.sep(',').prec(3)
-
-If a particular case needs a format similar to fmt but with some modification
-we would use, for example:
-
- puts 0.1234567.nio_write(fmt.prec(5)) -> 0.12346
- puts 0.1234567.nio_write(fmt) -> 0.123
-
-===Exact and aproximate values
-
-Float and BigDecimal are approximate in the sense that
-a given value within the range of these types, (defined either
-by an expression or as result of a computation) may no be
-exactly represented and has to be substituted by the closest possible value.
-With Float, which generally is a binary floating point, there's an
-additional mismatch with the notation (decimal) used for input and
-output.
-
-For the following examples we assume that Float is an IEEE754 double precision
-binary type (i.e. <tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>) which is the
-common case (in all Ruby platforms I know of, at least).
-
- 0.1.nio_write(Fmt.prec(:exact)) -> 0.1
-
-Well, that seems pretty clear... but let's complicate things a little:
-
- 0.1.nio_write(Fmt.prec(:exact).show_all_digits) -> 0.10000000000000001
-
-Mmmm where does that last one came from? Now we're seen a little more exactly what
-the actual value stored in the Float (the closest Float to 0.1) looks like.
-
-But why didn't we see the second one-digit in the first try?
-We requested "exact" precision!
-
-Well, we didn't get it because it is not needed to specify exactly the inner value
-of Float(1.0); when we convert 0.1 and round it to the nearest Float we get the
-same value as when we use 0.10000000000000001.
-Since we didn't request to see "all digits", we got only as few as possible.
-
- 0.1.nio_write(Fmt.prec(:exact).approx_mode(:exact)) -> 0.1000000000000000055511151231257827021181583404541015625
-
-Hey! Where did all that stuff came from? Now we're really seeing the "exact" value of Float.
-(We asked the conversion method to consider the Float an exactly defined value,
-rather than an approximation to some other value).
-But, why didn't we get all those digits when we asked for "all digits"?.
-
-Because most are not significant;
-the default "approx_mode" is to consider Float an approximate value and show only significant digits.
-We define insignificant digits as those that can be replaced by any other digit without altering the Float
-value when the number is rounded to the nearest float. By looking at our example we see that the 17 first digits
-(just before the 555111...) must be significant: they cannot take an arbitrary value without altering the Float.
-In fact all have well specified values except the last one that can be either 0 or 1 (but no other value). The next
-digits (first insignificant, a 5) can be replaced by any other digit * (from 0 to 9) and the expression
-0.10000000000000000* would still be rounded to Float(0.1)
-
-
-===Insignificance
-
-So, let's summarize the situation about inexact numbers: When the approximate mode of a format is <tt>:only_sig</tt>,
-the digits of inexact (i.e. floating point) numbers are classified as significant or insignificant.
-The latter are only shown if the property <tt>:all_digits</tt> is true
-(which it is by default for <tt>:fix</tt>)
-but since they are not meaningful they use a special character that by default is empty. You can
-define that character to see where they are:
- puts 0.1.nio_write(Fmt.mode(:fix,20).insignificant_digits('#')) -> 0.10000000000000001###
-
-If we hadn't use the special character we would'nt even seen those digits:
-
- puts 0.1.nio_write(Fmt.mode(:fix,20)) -> 0.10000000000000001
-
-When the aproximate mode is defined as :exact, there's no distinction between significant or insignificant
-digits, the number is taken as exactly defined and all digits are shown with their value.
-
- puts 0.1.nio_write(Fmt.mode(:fix,20,:approx_mode=>:exact) -> 0.10000000000000000555
-
-===Repeating Numerals
-
-The common term is <b>repeating decimal</b> or <b>recurring decimal</b>, but since Nio support them for any base,
-we'll call them <i>repeating numerals</i>.
-
- Rational(1,3).nio_write -> 0.333...
-
-We usually find that notation for the decimal expansion of 1/3 in texts, but that doesn't seem very accurate for
-a conversion library, or is it?
-
- Rational.nio_read('0.333...') -> Rational(1,3)
-
-It works the other way! In fact the seemingly loose 0.333... was an exact representation for Nio.
-
-All Rational numbers can be expressed as a repeating numeral in any base. Repeating numerals may have an infinite
-number of digits, but from some point on they're just repetitions of the same (finite) sequence of digits.
-
-By default Nio expresses that kind of repetition by appending two repetitions of the repeating sequence
-after it and adding the ellipsis (so the repeated sequence appears three times, and, by the way Nio
-uses three points rather than a real ellipsis characters).
-This allow Nio to recognize the repeating sequence on input.
-We can use a more economical notation by just marking the repeating sequence, rather than repeating it:
- Rational(1,3).nio_write(Fmt.rep(:nreps=>0)) -> 0.<3>
-We just requested for 0 as the number of repetitions (the default is 2) and got the sequence delimited by <>
-(we can change those characters; we can even use just a left separator).
-This is shorter and would allow to show the number better with special typography
-(e.g. a bar over the repeated digits, a different color, etc.)
-
-===BigDec()
-
-BigDec() is a handy convenience to define BigDecimals; it permits us
-to use BigDec(1) instead of BigDecimal('1')
-(I find it tedious to type all those quotes.)
-It can also be used with Float arguments, e.g.:
- BigDec(0.5)
-But this is a questionable use (for example it has been disregarded in Python Decimal.)
-It is allowed here because BigDec's purpose is to be a shortcut notation
-(BigDecimal() on the other hand should probably not accept Floats).
-
-Users must be aware of the problems and details of the implementation.
-Currently BigDec(x) for float x doesn't try to convert the exact value of x,
-which can be achieved with <tt>BigDec(0.1,:exact)</tt>, but tries instead to produce
-a simple value.
-
---
- Dilemma: leav BigDec as now (simplify) o change to use default fmt conversion
- a) => BigDec(1.0/3) == BigDec(Rational(1)/3)
- b) => BigDec(1.0/3) == BigDec("0.3333333333333333")
- in a, can we assure that NFmt.convert(BigDec(x),Float)==x ?
-++
-
-Since a floating point literal will, in general, convert to a Float of slightly different value,
-and several distinct literals can convert to the same value, there will always be some compromise.
-Here we've chosen to simplify values so that <tt>BigDec(0.1)==BigDecimal('0.1')</tt>,
-but this implies that, for example, <tt>BigDecimal('0.10000000000000001')</tt> cannot be defined
-with BigDec(), because <tt>Float(0.10000000000000001)==Float(0.1)</tt>.
-
-In any case using BigDec on Floats have some risks because it relies on the Ruby interpreter
-to parse floating point literal, and its behaviour is not stricly specified; in the usual case
-(IEEE Double Floats and round-to-even) BigDec() will behave well, but some platforms may
-behave differently.
-
-===Rounding
-
-Rounding is performed on both input and output.
-When a value is formatted for output the number is rounded to the number of digits
-that has been specified.
-
-But also when a value must be read from text rounding it is necessary to choose the nearest
-numeric representation (e.g. Float value).
-
-Nio supports three rounding modes which determine how to round _ties_:
-[<tt>:inf</tt>]
- round to infinity
- 1.5 -> 2
- -1.5 -> -2
-[<tt>:even</tt>] round to even (to the nearest even digit)
- 1.5 -> 2
- 2.5 -> 2
- -1.5 -> -2
- -2.5 -> -2
-[<tt>:zero</tt>] round to zero
- 1.5 -> 1
- -1.5 -> -1
-
-Rounding can be set with Nio::Fmt#mode and Nio::Fmt#prec for specific formats, and
-the default rounding mode can be changed with Fmt.default_rounding_mode().
-
-For round-trip conversions, a number should use the same rounding mode on input and output.
-
-For Floats there's an additional issue here, because when we use floating point literals
-on the Ruby code (such as 0.1 or 1E23) they are parsed and converted to Floating point values
-by the Ruby interpreter which must apply some kind of rounding when the expression to be parsed
-is equidistant from two Float values.
-All the Ruby implementations I have tried have IEEE754 Double Float
-(<tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>)
-and floating point literals seem to be rounded according to the round-to-even rule, so that
-is the initial default rounding mode.
-
-====Examples
-
-We assume the common implementation of float (<tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>) here.
-In that case, we can use the value 1E23, which is equidistant from two Floats
-to check which kind of roundig does the interpreter use.
-If it's round-to-even (the common case) we'll have:
- 1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:even) --> true
- 1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:zero) --> true
-But if rounding is to infinity the previous check will be false and this will hold:
- 1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:inf) --> true
-(Well, with this example we can't really distinguish :even from :zero, but :zero is most probably not used)
-
-Now, if we're using the same default rounding for Nio we will have:
- 1E23.nio_write == "1E23" --> true
-Which will make you feel warm and fuzzy. But if the system rounding is different
-we will get one of these ugly values:
- fmt_inf = Nio::Fmt.mode(:gen,:exact,:round=>:inf)
- fmt_even = Nio::Fmt.mode(:gen,:exact,:round=>:even)
- Float.nio_read('1E23',fmt_inf).nio_write(fmt_even) -> "1.0000000000000001E23"
- Float.nio_read('1E23',fmt_even).nio_write(fmt_inf) -> "9.999999999999999E22"
-
-If the Ruby interpreter doesn't support any of the roundings of Nio, or if it doesn't correctly
-round, the best solution would be to avoid using Float literals and use Float#nio_read instead.
-
-===Conversions
-
-Accurate conversion between numerical types can be performed with Nio.convert.
-It takes three arguments: the value to convert, the class to convert it to (Float,BigDecimal,
-Integer or Rational) and the conversion mode, either :exact, which is..., well, quite exact,
-and :approx which tries to find a simpler value (e.g. 0.1 rather than 0.10000000000000001...)
-within the accuray of the original value.
-The :approx mode may be pretty slow in some cases.
-
- Nio.convert(0.1,BigDecimal,:exact) -> 0.1000000000 0000000555 1115123125 ...
- Nio.convert(0.1,BigDecimal,:approx) -> 0.1
-
-We can check out the accuracy of the conversions:
-
- Nio.convert(BigDec('1.234567890123456'),Float)==1.234567890123456 -> true
- Nio.convert(BigDec(355)/226,Float)==(355.0/226) -> true
-
-Thay may not look very impressive, but is much more accurate than BigDecimal#to_f
-(at least in Ruby versions up to 1.8.6, mswin32 (specially) and linux) for which:
-
- BigDecimal('1.234567890123456').to_f == 1.234567890123456 -> false
- (BigDecimal("355")/226).to_f == (355.0/226.0) -> false
-
-=License
-
-This code is free to use under the terms of the GNU GENERAL PUBLIC LICENSE.
-
-=Contact
-
-Nio has been developed by Javier Goizueta (mailto:javier@goizueta.info).
-
-You can contact me through Rubyforge:http://rubyforge.org/sendmessage.php?touser=25432
-
-
-=More Information
-
-* <b>What Every Computer Scientist Should Know About Floating-Point Arithmetic</b>
- David Goldberg
- - http://docs.sun.com/source/806-3568/ncg_goldberg.html
-
-* <b>How to Read Floating Point Numbers Accurately</b>
- William D. Clinger
- - http://citeseer.ist.psu.edu/224562.html
-
-* <b>Printing Floating-Point Numbers Quickly and Accurately</b>
- Robert G. Burger & R. Kent Dybvig
- - http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
-
-* <b>Repeating Decimal</b>
- - http://mathworld.wolfram.com/RepeatingDecimal.html
- - http://en.wikipedia.org/wiki/Recurring_decimal
-
-* For <b>floating point rationalization algorithms</b>, see my commented
- source code for the <tt>rntlzr</tt> module from Nio,
- which you can download in PDF here:
- - http://perso.wanadoo.es/jgoizueta/dev/goi/rtnlzr.pdf
+=Description
+
+Nio (Numeric input/output) is a Ruby package for text-formatted input/output
+and conversion of scalar numeric types.
+
+This library formats numbers as text numerals and reads them back into numeric
+objects. The numeric types Integer Rational, BigDecimal and Float are supported.
+The numeral format is controlled with Nio::Fmt objects. Conversion between
+the numerical types is also provided.
+
+Nio offers low level services; formats supported are only positional notation and
+types are only scalar.
+This means that composite types such as Complex are not supported
+an that fraction notation (X/Y) is not supported for rationals;
+those are higher level services that could be implemented using Nio
+but are not the subject of this package.
+
+The implementation of Nio is pure Ruby, and not specially fast; but it is complete and accurate.
+
+Nio has some interesting features, though:
+
+* Correctly rounded conversions of all supported types to and from
+ positional numerals in any base.
+* Handling of digit repetitions (repeating decimals or, in general, <i>repeating numerals</i>).
+ With this method rational numbers can be represented exactly as numerals.
+* Discrimitation of significant digits of the representation of Float in any base.
+ (insignificant digits are those that can take any value without altering the Float value they specify.)
+
+All definitions are inside the module Nio that acts as a namespace, and methods added
+to classes outside of Nio have names that begin with the prefix <tt>nio_</tt>.
+
+Limitations:
+* The current version does not support UTF-8 or other multi-byte encodings (digits and separators must be one-byte characters).
+* This code is not very fast, since it is implemented in pure Ruby (no C extensions are used).
+* Error handling needs to improve also in future versions, specially on input and format parameters checking.
+
+=Installation
+
+The easiest way to install Nio is using gems:
+
+ gem install --remote nio
+
+==Downloads
+
+The latest version of Nio and its source code can be downloaded form
+* http://rubyforge.org/project/showfiles.php?group_id=4445
+
+The source code uses nuweb (a {<i>literate programming system</i>}[http://en.wikipedia.org/wiki/Literate_programming]) to generate
+the Ruby code for Nio. For more details you can download the nuweb source code package, <tt>nio-source</tt>
+and the documented source package, <tt>nio-source-pdf</tt>, which contains PDF files.
+
+
+=Documentation
+
+For a general introduction and some details, read on below.
+
+* See the the API to the Nio::Fmt object for all the <b>formatting options</b>.
+* For <b>type conversions</b> see Fmt.convert().
+* For some notational <b>shortcuts</b> see nio/sugar.rb[link:files/lib/nio/sugar_rb.html].
+* To *extend* the formatting to other types see the documentation for the module Nio::Formattable.
+
+=Basic use
+
+First we must require the library; we request also the optional nio/sugar.rb for convenient notational shortcuts,
+and include the module Nio to avoid writing too many Nio::s.
+
+ require 'rubygems'
+ require 'nio'
+ require 'nio/sugar'
+ include Nio
+
+Let's define a nice number to do some tests:
+
+ x = Math.sqrt(2)+100
+
+=== Writing
+
+Now let's try the formatted output:
+
+ puts x.nio_write -> 101.41421356237309
+ puts x.nio_write(Fmt.mode(:fix,4)) -> 101.4142
+ puts x.nio_write(Fmt.mode(:sig,4)) -> 101.4
+ puts x.nio_write(Fmt.mode(:sci,4)) -> 1.014E2
+ puts x.nio_write(Fmt.mode(:gen,4)) -> 101.4
+ puts (1e7*x).nio_write(Fmt.mode(:gen,4)) -> 1.014E9
+ puts (1e7*x).nio_write(Fmt.mode(:gen,4).show_plus) -> +1.014E9
+ puts x.nio_write(Fmt.mode(:gen,:exact)) -> 101.41421356237309
+
+We've seen some formatting modes:
+* <tt>:fix</tt> (similar to F in C printf or in Fortran) which shows a number of
+ _fixed_ decimals (4 in the example).
+* <tt>:sig</tt> is just like :fix, but the number of decimals specified means
+ significant digits.
+* <tt>:sci</tt> (similar to E in C printf or in Fortran) is for scientific
+ or exponential notation.
+* <tt>:gen</tt> (similar to G in C printf or in Fortran) is the general notational
+ (which also the default), the number of decimals is for significant digits, and
+ :sig is used if possible; if it would be too long it uses :sci instead.
+In one example above we used <tt>show_plus</tt> to force the display of the sign.
+And in the last line we used <tt>:exact</tt> for the number of digits, (which
+is also the default), and this adjust automatically the number of digits to
+show the value _exactly_, meaning that if we convert the numeral back to a
+numeric object of the original type, the same exact value will be produced.
+
+Now let's see some other formatting aspects. The separators can be defined:
+
+ x *= 1111
+ fmt = Fmt.mode(:fix,4)
+ puts x.nio_write(fmt.sep(',')) -> 112671,1913
+ puts x.nio_write(fmt.sep(',','.',[3])) -> 112.671,1913
+ puts x.nio_write(fmt.sep(',',' ',[2,3])) -> 1 126 71,1913
+
+The number can be adjusted in a field of specific width:
+
+fmt = Fmt.mode(:fix,2)
+ puts 11.2.nio_write(fmt.width(8)) -> 11.20
+ puts 11.2.nio_write(fmt.width(8,:right,'*')) -> ***11.20
+ puts 11.2.nio_write(fmt.width(8,:right,'*').show_plus) -> **+11.20
+ puts 11.2.nio_write(fmt.width(8,:internal,'*').show_plus) -> +**11.20
+ puts 11.2.nio_write(fmt.width(8,:left,'*')) -> 11.20***
+ puts 11.2.nio_write(fmt.width(8,:center,'*')) -> *11.20**
+ puts 11.2.nio_write(fmt.pad0s(8)) -> 00011.20
+ puts Rational(112,10).nio_write(fmt.pad0s(8)) -> 00011.20
+ puts 112.nio_write(fmt.pad0s(8)) -> 00112.00
+
+The numerical base does not need to be 10:
+
+ puts 34222223344.nio_write(fmt.base(16)) -> 7f7cdaff0.00
+ puts x.nio_write(Fmt.base(16)) -> 1b81f.30F6ED22C
+ puts x.nio_write(Fmt.mode(:fix,4).base(2)) -> 11011100000011111.0011
+ puts 1.234333E-23.nio_write(Fmt.base(2).prec(20)) -> 1.1101110110000010011E-77
+
+The sugar module give us some alternatives for the writing notation:
+
+ puts Fmt << x -> 112671.1912677965
+ puts Fmt.mode(:fix,4) << x -> 112671.1913
+
+ puts Fmt.write(x) -> 112671.1912677965
+ puts Fmt.mode(:fix,4).write(4) -> 4.0000
+
+===Reading
+
+To read a numeral we must specify the numeric class we want to convert it to:
+
+ puts Float.nio_read('0.1') -> 0.1
+ puts BigDecimal.nio_read('0.1') -> 0.1E0
+ puts Rational.nio_read('0.1') -> 1/10
+ puts Integer.nio_read('0.1') -> 0
+
+A format can also be specified, although some aspects (such as the precision)
+will be ignored.
+
+ puts Float.nio_read('0,1',Fmt.sep(',')) -> 0.1
+ puts Float.nio_read('122.344,1',Fmt.sep(',')) -> 122344.1
+ puts Float.nio_read('122,344.1',Fmt.sep('.')) -> 122344.1
+
+There are also some sweet alternatives for reading:
+
+ puts Fmt.read(Float,'0.1') -> 0.1
+ puts Fmt.sep(',').read(Float,'0,1') -> 0.1
+
+ puts Fmt >> [Float, '0.1'] -> 0.1
+ puts Fmt.sep(',') >> [Float, '0,1'] -> 0.1
+
+===Floating point
+
+Now let's see something trickier; we will use the floating point result
+of dividing 2 by 3 and will use a format with 20 fixed digits:
+
+ x = 2.0/3
+ fmt = Fmt.mode(:fix,20)
+
+ puts x.nio_write(fmt) -> 0.66666666666666663
+
+If you count the digits you will find only 17. Where are the other 3?
+
+Here we're dealing with an approximate numerical type, Float, that has a limited
+internal precision and we asked for a higher precision on the output, which we
+didn't get. Nio refuses to show non-significant digits.
+
+We can use a placeholder for the digits so that Nio shows us something rather than
+just ignoring the digits:
+
+ puts x.nio_write(fmt.insignificant_digits('#')) -> 0.66666666666666663###
+
+Nio is hiding those digits because it assumes that the Float value is an approximation,
+but we can force it to actually compute the mathematical _exact_ value of the Float:
+
+ puts x.nio_write(fmt.approx_mode(:exact)) -> 0.66666666666666662966
+
+===Default format
+
+The default format Fmt.default is used when no format is specified;
+it can be changed by assigning to it:
+
+ Fmt.default = Fmt.default.sep(',')
+ puts 1.23456.nio_write -> 1,23456
+ puts 1.23456.nio_write(Fmt.prec(3)) -> 1,23
+
+But note that Fmt.new doesn't use the current default (it's
+the hard-wired value at which Fmt.default starts):
+
+ puts 1.23456.nio_write(Fmt.new.prec(3)) -> 1.23
+
+There are also other named prefined formats:
+
+ puts 123456.78.nio_write(Fmt[:dot]) -> 123456.78
+ puts 123456.78.nio_write(Fmt[:dot_th]) -> 123,456.78
+ puts 123456.78.nio_write(Fmt[:comma]) -> 123456,78
+ puts 123456.78.nio_write(Fmt[:code]) -> 123456,78
+
+The <tt>_th</tt> indicates that thousands separators are used;
+the :code format is intended for programming languages such as Ruby, C, SQL, etc.
+These formats can be changed by assigning to them, and also other named formats
+can be defined:
+
+ Fmt[:code] = Fmt.new.prec(1)
+ puts 123456.78.nio_write(Fmt[:code]) -> 123456.8
+ Fmt[:locale_money] = Fmt.sep(',','.',[3]).prec(:fix,2)
+ puts 123456.78.nio_write(Fmt[:locale_money]) -> 123.456,78
+
+===Conversions
+
+Nio can also convert values between numerical types, e.g. from Float to Rational:
+
+ puts Nio.convert(2.0/3, Rational) -> 2/3
+ puts Nio.convert(2.0/3, Rational, :exact) -> 6004799503160661/9007199254740992
+
+The default (approximate) conversions assumes that the value is inexact and tries to find
+a nice simple value near it. When we request <tt>:exact</tt> conversion the actual internal value
+of the floating point number is preserved.
+
+Let's see some more examples:
+
+ puts Nio.convert(2.0/3, BigDecimal) -> 0.666666666666666666666667E0
+ puts Nio.convert(2.0/3, BigDecimal, :exact) -> 0.66666666666666662965923251249478198587894439697265625E0
+ puts Nio.convert(Rational(2,3), Float) -> 0.666666666666667
+ puts Nio.convert(Rational(2,3), BigDecimal) -> 0.666666666666666666666667E0
+ puts Nio.convert(BigDecimal('2')/3, Rational) -> 2/3
+ puts Nio.convert(BigDecimal('2')/3, Rational, :exact) -> 666666666666666666666667/1000000000000000000000000
+ puts Nio.convert(2.0/3, BigDecimal) -> 0.666666666666666666666667E0
+ puts Nio.convert(2.0/3, BigDecimal, :exact) -> 0.66666666666666662965923251249478198587894439697265625E0
+ puts Nio.convert(BigDecimal('2')/3, Float) -> 0.666666666666667
+ puts Nio.convert(BigDecimal('2')/3, Float, :exact) -> 0.666666666666667
+
+
+
+=Details
+
+===Defining formats
+
+Say you want a numeric format based on the current default but with some aspects
+changed, e.g. using comma as the decimal separator and with only 3 digits
+of precision, we could do:
+
+ fmt = Fmt.default
+ fmt = fmt.sep(',')
+ fmt = fmt.prec(3)
+
+That wasn't very nice. Fmt is a mutable class and have methods to modify
+its state that end with a bang. We can use them to make this look better,
+but note that we must create a copy of the default format before we
+modify it:
+
+ fmt = Fmt.default.dup
+ fmt.sep! ','
+ fmt.prec! 3
+
+Note that we had to make a duplicate of the default format in
+order to modify it or we would have got an error
+(if you want to modify the default format
+you have to assign to it).
+
+Now we can simplify this a little by passing a block to Fmt.default:
+
+ fmt = Fmt.default { |f|
+ f.sep! ','
+ f.prec! 3
+ }
+
+But there's a more concise way to define the format by avoiding
+the bang-methods and chaining all modifications:
+
+ fmt = Fmt.default.sep(',').prec(3)
+
+Or even (using shortcut methods such as Fmt.sep or Fmt.prec):
+
+ fmt = Fmt.sep(',').prec(3)
+
+If we don't want to base the new format on the current default, but use
+the initial default instead, we would substitute new for default above,
+except in the last case which always uses the default.
+For example:
+
+ fmt = Fmt.new { |f|
+ f.sep! ','
+ f.prec! 3
+ }
+
+ fmt = Fmt.new.sep(',').prec(3)
+
+If a particular case needs a format similar to fmt but with some modification
+we would use, for example:
+
+ puts 0.1234567.nio_write(fmt.prec(5)) -> 0.12346
+ puts 0.1234567.nio_write(fmt) -> 0.123
+
+===Exact and aproximate values
+
+Float and BigDecimal are approximate in the sense that
+a given value within the range of these types, (defined either
+by an expression or as result of a computation) may no be
+exactly represented and has to be substituted by the closest possible value.
+With Float, which generally is a binary floating point, there's an
+additional mismatch with the notation (decimal) used for input and
+output.
+
+For the following examples we assume that Float is an IEEE754 double precision
+binary type (i.e. <tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>) which is the
+common case (in all Ruby platforms I know of, at least).
+
+ 0.1.nio_write(Fmt.prec(:exact)) -> 0.1
+
+Well, that seems pretty clear... but let's complicate things a little:
+
+ 0.1.nio_write(Fmt.prec(:exact).show_all_digits) -> 0.10000000000000001
+
+Mmmm where does that last one came from? Now we're seen a little more exactly what
+the actual value stored in the Float (the closest Float to 0.1) looks like.
+
+But why didn't we see the second one-digit in the first try?
+We requested "exact" precision!
+
+Well, we didn't get it because it is not needed to specify exactly the inner value
+of Float(1.0); when we convert 0.1 and round it to the nearest Float we get the
+same value as when we use 0.10000000000000001.
+Since we didn't request to see "all digits", we got only as few as possible.
+
+ 0.1.nio_write(Fmt.prec(:exact).approx_mode(:exact)) -> 0.1000000000000000055511151231257827021181583404541015625
+
+Hey! Where did all that stuff came from? Now we're really seeing the "exact" value of Float.
+(We asked the conversion method to consider the Float an exactly defined value,
+rather than an approximation to some other value).
+But, why didn't we get all those digits when we asked for "all digits"?.
+
+Because most are not significant;
+the default "approx_mode" is to consider Float an approximate value and show only significant digits.
+We define insignificant digits as those that can be replaced by any other digit without altering the Float
+value when the number is rounded to the nearest float. By looking at our example we see that the 17 first digits
+(just before the 555111...) must be significant: they cannot take an arbitrary value without altering the Float.
+In fact all have well specified values except the last one that can be either 0 or 1 (but no other value). The next
+digits (first insignificant, a 5) can be replaced by any other digit * (from 0 to 9) and the expression
+0.10000000000000000* would still be rounded to Float(0.1)
+
+
+===Insignificance
+
+So, let's summarize the situation about inexact numbers: When the approximate mode of a format is <tt>:only_sig</tt>,
+the digits of inexact (i.e. floating point) numbers are classified as significant or insignificant.
+The latter are only shown if the property <tt>:all_digits</tt> is true
+(which it is by default for <tt>:fix</tt>)
+but since they are not meaningful they use a special character that by default is empty. You can
+define that character to see where they are:
+ puts 0.1.nio_write(Fmt.mode(:fix,20).insignificant_digits('#')) -> 0.10000000000000001###
+
+If we hadn't use the special character we would'nt even seen those digits:
+
+ puts 0.1.nio_write(Fmt.mode(:fix,20)) -> 0.10000000000000001
+
+When the aproximate mode is defined as :exact, there's no distinction between significant or insignificant
+digits, the number is taken as exactly defined and all digits are shown with their value.
+
+ puts 0.1.nio_write(Fmt.mode(:fix,20,:approx_mode=>:exact) -> 0.10000000000000000555
+
+===Repeating Numerals
+
+The common term is <b>repeating decimal</b> or <b>recurring decimal</b>, but since Nio support them for any base,
+we'll call them <i>repeating numerals</i>.
+
+ Rational(1,3).nio_write -> 0.333...
+
+We usually find that notation for the decimal expansion of 1/3 in texts, but that doesn't seem very accurate for
+a conversion library, or is it?
+
+ Rational.nio_read('0.333...') -> Rational(1,3)
+
+It works the other way! In fact the seemingly loose 0.333... was an exact representation for Nio.
+
+All Rational numbers can be expressed as a repeating numeral in any base. Repeating numerals may have an infinite
+number of digits, but from some point on they're just repetitions of the same (finite) sequence of digits.
+
+By default Nio expresses that kind of repetition by appending two repetitions of the repeating sequence
+after it and adding the ellipsis (so the repeated sequence appears three times, and, by the way Nio
+uses three points rather than a real ellipsis characters).
+This allow Nio to recognize the repeating sequence on input.
+We can use a more economical notation by just marking the repeating sequence, rather than repeating it:
+ Rational(1,3).nio_write(Fmt.rep(:nreps=>0)) -> 0.<3>
+We just requested for 0 as the number of repetitions (the default is 2) and got the sequence delimited by <>
+(we can change those characters; we can even use just a left separator).
+This is shorter and would allow to show the number better with special typography
+(e.g. a bar over the repeated digits, a different color, etc.)
+
+===Rounding
+
+Rounding is performed on both input and output.
+When a value is formatted for output the number is rounded to the number of digits
+that has been specified.
+
+But also when a value must be read from text rounding it is necessary to choose the nearest
+numeric representation (e.g. Float value).
+
+Nio supports three rounding modes which determine how to round _ties_:
+[<tt>:inf</tt>]
+ round to infinity
+ 1.5 -> 2
+ -1.5 -> -2
+[<tt>:even</tt>] round to even (to the nearest even digit)
+ 1.5 -> 2
+ 2.5 -> 2
+ -1.5 -> -2
+ -2.5 -> -2
+[<tt>:zero</tt>] round to zero
+ 1.5 -> 1
+ -1.5 -> -1
+
+Rounding can be set with Nio::Fmt#mode and Nio::Fmt#prec for specific formats, and
+the default rounding mode can be changed with Fmt.default_rounding_mode().
+
+For round-trip conversions, a number should use the same rounding mode on input and output.
+
+For Floats there's an additional issue here, because when we use floating point literals
+on the Ruby code (such as 0.1 or 1E23) they are parsed and converted to Floating point values
+by the Ruby interpreter which must apply some kind of rounding when the expression to be parsed
+is equidistant from two Float values.
+All the Ruby implementations I have tried have IEEE754 Double Float
+(<tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>)
+and floating point literals seem to be rounded according to the round-to-even rule, so that
+is the initial default rounding mode.
+
+====Examples
+
+We assume the common implementation of float (<tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>) here.
+In that case, we can use the value 1E23, which is equidistant from two Floats
+to check which kind of roundig does the interpreter use.
+If it's round-to-even (the common case) we'll have:
+ 1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:even) --> true
+ 1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:zero) --> true
+But if rounding is to infinity the previous check will be false and this will hold:
+ 1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:inf) --> true
+(Well, with this example we can't really distinguish :even from :zero, but :zero is most probably not used)
+
+Now, if we're using the same default rounding for Nio we will have:
+ 1E23.nio_write == "1E23" --> true
+Which will make you feel warm and fuzzy. But if the system rounding is different
+we will get one of these ugly values:
+ fmt_inf = Nio::Fmt.mode(:gen,:exact,:round=>:inf)
+ fmt_even = Nio::Fmt.mode(:gen,:exact,:round=>:even)
+ Float.nio_read('1E23',fmt_inf).nio_write(fmt_even) -> "1.0000000000000001E23"
+ Float.nio_read('1E23',fmt_even).nio_write(fmt_inf) -> "9.999999999999999E22"
+
+If the Ruby interpreter doesn't support any of the roundings of Nio, or if it doesn't correctly
+round, the best solution would be to avoid using Float literals and use Float#nio_read instead.
+
+===Conversions
+
+Accurate conversion between numerical types can be performed with Nio.convert.
+It takes three arguments: the value to convert, the class to convert it to (Float, BigDecimal,
+Flt::Num, Integer or Rational) and the conversion mode, either :exact, which is..., well, quite exact,
+and :approx which tries to find a simpler value (e.g. 0.1 rather than 0.10000000000000001...)
+within the accuray of the original value.
+The :approx mode may be pretty slow in some cases.
+
+ Nio.convert(0.1,BigDecimal,:exact) -> 0.1000000000 0000000555 1115123125 ...
+ Nio.convert(0.1,BigDecimal,:approx) -> 0.1
+
+We can check out the accuracy of the conversions:
+
+ Nio.convert(BigDecimal('1.234567890123456'),Float)==1.234567890123456 -> true
+ Nio.convert(BigDecimal('355')/226,Float)==(355.0/226) -> true
+
+Thay may not look very impressive, but is much more accurate than BigDecimal#to_f
+(at least in Ruby versions up to 1.8.6, mswin32 (specially) and linux) for which:
+
+ BigDecimal('1.234567890123456').to_f == 1.234567890123456 -> false
+ (BigDecimal('355')/226).to_f == (355.0/226.0) -> false
+
+=License
+
+This code is free to use under the terms of the GNU GENERAL PUBLIC LICENSE.
+
+=Contact
+
+Nio has been developed by Javier Goizueta (mailto:javier@goizueta.info).
+
+You can contact me through Rubyforge:http://rubyforge.org/sendmessage.php?touser=25432
+
+
+=More Information
+
+* <b>What Every Computer Scientist Should Know About Floating-Point Arithmetic</b>
+ David Goldberg
+ - http://docs.sun.com/source/806-3568/ncg_goldberg.html
+
+* <b>How to Read Floating Point Numbers Accurately</b>
+ William D. Clinger
+ - http://citeseer.ist.psu.edu/224562.html
+
+* <b>Printing Floating-Point Numbers Quickly and Accurately</b>
+ Robert G. Burger & R. Kent Dybvig
+ - http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
+
+* <b>Repeating Decimal</b>
+ - http://mathworld.wolfram.com/RepeatingDecimal.html
+ - http://en.wikipedia.org/wiki/Recurring_decimal
+
+* For <b>floating point rationalization algorithms</b>, see my commented
+ source code for the <tt>rntlzr</tt> module from Nio,
+ which you can download in PDF here:
+ - http://perso.wanadoo.es/jgoizueta/dev/goi/rtnlzr.pdf