nio 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/License.txt +20 -0
- data/Manifest.txt +34 -0
- data/README.txt +560 -0
- data/Rakefile +4 -0
- data/SOURCE.txt +31 -0
- data/config/hoe.rb +73 -0
- data/config/requirements.rb +17 -0
- data/lib/nio/flttol.rb +654 -0
- data/lib/nio/fmt.rb +1872 -0
- data/lib/nio/repdec.rb +496 -0
- data/lib/nio/rtnlzr.rb +406 -0
- data/lib/nio/sugar.rb +99 -0
- data/lib/nio/tools.rb +44 -0
- data/lib/nio/version.rb +9 -0
- data/lib/nio.rb +8 -0
- data/log/debug.log +0 -0
- data/script/destroy +14 -0
- data/script/destroy.cmd +1 -0
- data/script/generate +14 -0
- data/script/generate.cmd +1 -0
- data/script/txt2html +74 -0
- data/script/txt2html.cmd +1 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +27 -0
- data/tasks/environment.rake +7 -0
- data/tasks/nuweb.rake +69 -0
- data/tasks/website.rake +17 -0
- data/test/data.yaml +101 -0
- data/test/test_fmt.rb +373 -0
- data/test/test_helper.rb +32 -0
- data/test/test_repdec.rb +88 -0
- data/test/test_rtnlzr.rb +125 -0
- data/test/test_tools.rb +40 -0
- metadata +88 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Javier Goizueta
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
SOURCE.txt
|
6
|
+
Rakefile
|
7
|
+
config/hoe.rb
|
8
|
+
config/requirements.rb
|
9
|
+
lib/nio.rb
|
10
|
+
lib/nio/version.rb
|
11
|
+
lib/nio/repdec.rb
|
12
|
+
lib/nio/flttol.rb
|
13
|
+
lib/nio/tools.rb
|
14
|
+
lib/nio/rtnlzr.rb
|
15
|
+
lib/nio/fmt.rb
|
16
|
+
lib/nio/sugar.rb
|
17
|
+
log/debug.log
|
18
|
+
script/destroy
|
19
|
+
script/destroy.cmd
|
20
|
+
script/generate
|
21
|
+
script/generate.cmd
|
22
|
+
script/txt2html
|
23
|
+
script/txt2html.cmd
|
24
|
+
setup.rb
|
25
|
+
tasks/nuweb.rake
|
26
|
+
tasks/deployment.rake
|
27
|
+
tasks/environment.rake
|
28
|
+
tasks/website.rake
|
29
|
+
test/data.yaml
|
30
|
+
test/test_helper.rb
|
31
|
+
test/test_tools.rb
|
32
|
+
test/test_repdec.rb
|
33
|
+
test/test_rtnlzr.rb
|
34
|
+
test/test_fmt.rb
|
data/README.txt
ADDED
@@ -0,0 +1,560 @@
|
|
1
|
+
=Description
|
2
|
+
|
3
|
+
Nio (Numeric input/output) is a Ruby package for text-formatted input/output
|
4
|
+
and conversion of scalar numeric types.
|
5
|
+
|
6
|
+
This library formats numbers as text numerals and reads them back into numeric
|
7
|
+
objects. The numeric types Integer Rational, BigDecimal and Float are supported.
|
8
|
+
The numeral format is controlled with Nio::Fmt objects. Conversion between
|
9
|
+
the numerical types is also provided.
|
10
|
+
|
11
|
+
Nio offers low level services; formats supported are only positional notation and
|
12
|
+
types are only scalar.
|
13
|
+
This means that composite types such as Complex are not supported
|
14
|
+
an that fraction notation (X/Y) is not supported for rationals;
|
15
|
+
those are higher level services that could be implemented using Nio
|
16
|
+
but are not the subject of this package.
|
17
|
+
|
18
|
+
The implementation of Nio is pure Ruby, and not specially fast; but it is complete and accurate.
|
19
|
+
|
20
|
+
Nio has some interesting features, though:
|
21
|
+
|
22
|
+
* Correctly rounded conversions of all supported types to and from
|
23
|
+
positional numerals in any base.
|
24
|
+
* Handling of digit repetitions (repeating decimals or, in general, <i>repeating numerals</i>).
|
25
|
+
With this method rational numbers can be represented exactly as numerals.
|
26
|
+
* Discrimitation of significant digits of the representation of Float in any base.
|
27
|
+
(insignificant digits are those that can take any value without altering the Float value they specify.)
|
28
|
+
* Floating point tolerance classes
|
29
|
+
|
30
|
+
All definitions are inside the module Nio that acts as a namespace, and methods added
|
31
|
+
to classes outside of Nio have names that begin with the prefix <tt>nio_</tt>. Only
|
32
|
+
the module nio/sugar.rb, which must be required separately, breaks this rule by defining
|
33
|
+
some methods such as Float#to_r.
|
34
|
+
|
35
|
+
Limitations:
|
36
|
+
* The current version does not support UTF-8 or other multi-byte encodings (digits and separators must be one-byte characters).
|
37
|
+
* This code is not very fast, since it is implemented in pure Ruby (no C extensions are used).
|
38
|
+
* Error handling needs to improve also in future versions, specially on input and format parameters checking.
|
39
|
+
|
40
|
+
=Installation
|
41
|
+
|
42
|
+
The easiest way to install Nio is using gems:
|
43
|
+
|
44
|
+
gem install --remote nio
|
45
|
+
|
46
|
+
==Downloads
|
47
|
+
|
48
|
+
The latest version of Nio and its source code can be downloaded form
|
49
|
+
* http://rubyforge.org/project/showfiles.php?group_id=4445
|
50
|
+
|
51
|
+
The source code uses nuweb (a {<i>literate programming system</i>}[http://en.wikipedia.org/wiki/Literate_programming]) to generate
|
52
|
+
the Ruby code for Nio. For more details you can download the nuweb source code package, <tt>nio-source</tt>
|
53
|
+
and the documented source package, <tt>nio-source-pdf</tt>, which contains PDF files.
|
54
|
+
|
55
|
+
|
56
|
+
=Documentation
|
57
|
+
|
58
|
+
For a general introduction and some details, read on below.
|
59
|
+
|
60
|
+
* See the the API to the Nio::Fmt object for all the <b>formatting options</b>.
|
61
|
+
* For <b>type conversions</b> see Fmt.convert().
|
62
|
+
* For some notational <b>shortcuts</b> see nio/sugar.rb[link:files/lib/nio/sugar_rb.html].
|
63
|
+
* To *extend* the formatting to other types see the documentation for the module Nio::Formattable.
|
64
|
+
* If you want to use the <b>floating point tolerance</b> see the classes Nio::Tolerance and Nio::BigTolerance,
|
65
|
+
which can be defined with <tt>Nio::Tol()</tt> and <tt>Nio::BigTol()</tt> as described in the module Nio.
|
66
|
+
* The function <b>BigDec()</b> is a convenient constructor to define/convert BigDecimals, also described in module Nio.
|
67
|
+
|
68
|
+
=Basic use
|
69
|
+
|
70
|
+
First we must require the library; we request also the optional nio/sugar.rb for convenient notational shortcuts,
|
71
|
+
and include the module Nio to avoid writing too many Nio::s.
|
72
|
+
|
73
|
+
require 'rubygems'
|
74
|
+
require 'nio'
|
75
|
+
require 'nio/sugar'
|
76
|
+
include Nio
|
77
|
+
|
78
|
+
Let's define a nice number to do some tests:
|
79
|
+
|
80
|
+
x = Math.sqrt(2)+100
|
81
|
+
|
82
|
+
=== Writing
|
83
|
+
|
84
|
+
Now let's try the formatted output:
|
85
|
+
|
86
|
+
puts x.nio_write -> 101.41421356237309
|
87
|
+
puts x.nio_write(Fmt.mode(:fix,4)) -> 101.4142
|
88
|
+
puts x.nio_write(Fmt.mode(:sig,4)) -> 101.4
|
89
|
+
puts x.nio_write(Fmt.mode(:sci,4)) -> 1.014E2
|
90
|
+
puts x.nio_write(Fmt.mode(:gen,4)) -> 101.4
|
91
|
+
puts (1e7*x).nio_write(Fmt.mode(:gen,4)) -> 1.014E9
|
92
|
+
puts (1e7*x).nio_write(Fmt.mode(:gen,4).show_plus) -> +1.014E9
|
93
|
+
puts x.nio_write(Fmt.mode(:gen,:exact)) -> 101.41421356237309
|
94
|
+
|
95
|
+
We've seen some formatting modes:
|
96
|
+
* <tt>:fix</tt> (similar to F in C printf or in Fortran) which shows a number of
|
97
|
+
_fixed_ decimals (4 in the example).
|
98
|
+
* <tt>:sig</tt> is just like :fix, but the number of decimals specified means
|
99
|
+
significant digits.
|
100
|
+
* <tt>:sci</tt> (similar to E in C printf or in Fortran) is for scientific
|
101
|
+
or exponential notation.
|
102
|
+
* <tt>:gen</tt> (similar to G in C printf or in Fortran) is the general notational
|
103
|
+
(which also the default), the number of decimals is for significant digits, and
|
104
|
+
:sig is used if possible; if it would be too long it uses :sci instead.
|
105
|
+
In one example above we used <tt>show_plus</tt> to force the display of the sign.
|
106
|
+
And in the last line we used <tt>:exact</tt> for the number of digits, (which
|
107
|
+
is also the default), and this adjust automatically the number of digits to
|
108
|
+
show the value _exactly_, meaning that if we convert the numeral back to a
|
109
|
+
numeric object of the original type, the same exact value will be produced.
|
110
|
+
|
111
|
+
Now let's see some other formatting aspects. The separators can be defined:
|
112
|
+
|
113
|
+
x *= 1111
|
114
|
+
fmt = Fmt.mode(:fix,4)
|
115
|
+
puts x.nio_write(fmt.sep(',')) -> 112671,1913
|
116
|
+
puts x.nio_write(fmt.sep(',','.',[3])) -> 112.671,1913
|
117
|
+
puts x.nio_write(fmt.sep(',',' ',[2,3])) -> 1 126 71,1913
|
118
|
+
|
119
|
+
The number can be adjusted in a field of specific width:
|
120
|
+
|
121
|
+
fmt = Fmt.mode(:fix,2)
|
122
|
+
puts 11.2.nio_write(fmt.width(8)) -> 11.20
|
123
|
+
puts 11.2.nio_write(fmt.width(8,:right,'*')) -> ***11.20
|
124
|
+
puts 11.2.nio_write(fmt.width(8,:right,'*').show_plus) -> **+11.20
|
125
|
+
puts 11.2.nio_write(fmt.width(8,:internal,'*').show_plus) -> +**11.20
|
126
|
+
puts 11.2.nio_write(fmt.width(8,:left,'*')) -> 11.20***
|
127
|
+
puts 11.2.nio_write(fmt.width(8,:center,'*')) -> *11.20**
|
128
|
+
puts 11.2.nio_write(fmt.pad0s(8)) -> 00011.20
|
129
|
+
puts BigDec(11.2).nio_write(fmt.pad0s(8)) -> 00011.20
|
130
|
+
puts Rational(112,10).nio_write(fmt.pad0s(8)) -> 00011.20
|
131
|
+
puts 112.nio_write(fmt.pad0s(8)) -> 00112.00
|
132
|
+
|
133
|
+
The numerical base does not need to be 10:
|
134
|
+
|
135
|
+
puts 34222223344.nio_write(fmt.base(16)) -> 7f7cdaff0.00
|
136
|
+
puts x.nio_write(Fmt.base(16)) -> 1b81f.30F6ED22C
|
137
|
+
puts x.nio_write(Fmt.mode(:fix,4).base(2)) -> 11011100000011111.0011
|
138
|
+
puts 1.234333E-23.nio_write(Fmt.base(2).prec(20)) -> 1.1101110110000010011E-77
|
139
|
+
|
140
|
+
The sugar module give us some alternatives for the writing notation:
|
141
|
+
|
142
|
+
puts Fmt << x -> 112671.1912677965
|
143
|
+
puts Fmt.mode(:fix,4) << x -> 112671.1913
|
144
|
+
|
145
|
+
puts Fmt.write(x) -> 112671.1912677965
|
146
|
+
puts Fmt.mode(:fix,4).write(4) -> 4.0000
|
147
|
+
|
148
|
+
===Reading
|
149
|
+
|
150
|
+
To read a numeral we must specify the numeric class we want to convert it to:
|
151
|
+
|
152
|
+
puts Float.nio_read('0.1') -> 0.1
|
153
|
+
puts BigDecimal.nio_read('0.1') -> 0.1E0
|
154
|
+
puts Rational.nio_read('0.1') -> 1/10
|
155
|
+
puts Integer.nio_read('0.1') -> 0
|
156
|
+
|
157
|
+
A format can also be specified, although some aspects (such as the precision)
|
158
|
+
will be ignored.
|
159
|
+
|
160
|
+
puts Float.nio_read('0,1',Fmt.sep(',')) -> 0.1
|
161
|
+
puts Float.nio_read('122.344,1',Fmt.sep(',')) -> 122344.1
|
162
|
+
puts Float.nio_read('122,344.1',Fmt.sep('.')) -> 122344.1
|
163
|
+
|
164
|
+
There are also some sweet alternatives for reading:
|
165
|
+
|
166
|
+
puts Fmt.read(Float,'0.1') -> 0.1
|
167
|
+
puts Fmt.sep(',').read(Float,'0,1') -> 0.1
|
168
|
+
|
169
|
+
puts Fmt >> [Float, '0.1'] -> 0.1
|
170
|
+
puts Fmt.sep(',') >> [Float, '0,1'] -> 0.1
|
171
|
+
|
172
|
+
===Floating point
|
173
|
+
|
174
|
+
Now let's see something trickier; we will use the floating point result
|
175
|
+
of dividing 2 by 3 and will use a format with 20 fixed digits:
|
176
|
+
|
177
|
+
x = 2.0/3
|
178
|
+
fmt = Fmt.mode(:fix,20)
|
179
|
+
|
180
|
+
puts x.nio_write(fmt) -> 0.66666666666666663
|
181
|
+
|
182
|
+
If you count the digits you will find only 17. Where are the other 3?
|
183
|
+
|
184
|
+
Here we're dealing with an approximate numerical type, Float, that has a limited
|
185
|
+
internal precision and we asked for a higher precision on the output, which we
|
186
|
+
didn't get. Nio refuses to show non-significant digits.
|
187
|
+
|
188
|
+
We can use a placeholder for the digits so that Nio shows us something rather than
|
189
|
+
just ignoring the digits:
|
190
|
+
|
191
|
+
puts x.nio_write(fmt.insignificant_digits('#')) -> 0.66666666666666663###
|
192
|
+
|
193
|
+
Nio is hiding those digits because it assumes that the Float value is an approximation,
|
194
|
+
but we can force it to actually compute the mathematical _exact_ value of the Float:
|
195
|
+
|
196
|
+
puts x.nio_write(fmt.approx_mode(:exact)) -> 0.66666666666666662966
|
197
|
+
|
198
|
+
===Default format
|
199
|
+
|
200
|
+
The default format Fmt.default is used when no format is specified;
|
201
|
+
it can be changed by assigning to it:
|
202
|
+
|
203
|
+
Fmt.default = Fmt.default.sep(',')
|
204
|
+
puts 1.23456.nio_write -> 1,23456
|
205
|
+
puts 1.23456.nio_write(Fmt.prec(3)) -> 1,23
|
206
|
+
|
207
|
+
But note that Fmt.new doesn't use the current default (it's
|
208
|
+
the hard-wired value at which Fmt.default starts):
|
209
|
+
|
210
|
+
puts 1.23456.nio_write(Fmt.new.prec(3)) -> 1.23
|
211
|
+
|
212
|
+
There are also other named prefined formats:
|
213
|
+
|
214
|
+
puts 123456.78.nio_write(Fmt[:dot]) -> 123456.78
|
215
|
+
puts 123456.78.nio_write(Fmt[:dot_th]) -> 123,456.78
|
216
|
+
puts 123456.78.nio_write(Fmt[:comma]) -> 123456,78
|
217
|
+
puts 123456.78.nio_write(Fmt[:code]) -> 123456,78
|
218
|
+
|
219
|
+
The <tt>_th</tt> indicates that thousands separators are used;
|
220
|
+
the :code format is intended for programming languages such as Ruby, C, SQL, etc.
|
221
|
+
These formats can be changed by assigning to them, and also other named formats
|
222
|
+
can be defined:
|
223
|
+
|
224
|
+
Fmt[:code] = Fmt.new.prec(1)
|
225
|
+
puts 123456.78.nio_write(Fmt[:code]) -> 123456.8
|
226
|
+
Fmt[:locale_money] = Fmt.sep(',','.',[3]).prec(:fix,2)
|
227
|
+
puts 123456.78.nio_write(Fmt[:locale_money]) -> 123.456,78
|
228
|
+
|
229
|
+
===Conversions
|
230
|
+
|
231
|
+
Nio can also convert values between numerical types, e.g. from Float to Rational:
|
232
|
+
|
233
|
+
puts Nio.convert(2.0/3, Rational) -> 2/3
|
234
|
+
puts Nio.convert(2.0/3, Rational, :exact) -> 6004799503160661/9007199254740992
|
235
|
+
|
236
|
+
The default (approximate) conversions assumes that the value is inexact and tries to find
|
237
|
+
a nice simple value near it. When we request <tt>:exact</tt> conversion the actual internal value
|
238
|
+
of the floating point number is preserved.
|
239
|
+
|
240
|
+
Let's see some more examples:
|
241
|
+
|
242
|
+
puts Nio.convert(2.0/3, BigDecimal) -> 0.666666666666666666666667E0
|
243
|
+
puts Nio.convert(2.0/3, BigDecimal, :exact) -> 0.66666666666666662965923251249478198587894439697265625E0
|
244
|
+
puts Nio.convert(Rational(2,3), Float) -> 0.666666666666667
|
245
|
+
puts Nio.convert(Rational(2,3), BigDecimal) -> 0.666666666666666666666667E0
|
246
|
+
puts Nio.convert(BigDecimal('2')/3, Rational) -> 2/3
|
247
|
+
puts Nio.convert(BigDecimal('2')/3, Rational, :exact) -> 666666666666666666666667/1000000000000000000000000
|
248
|
+
puts Nio.convert(2.0/3, BigDecimal) -> 0.666666666666666666666667E0
|
249
|
+
puts Nio.convert(2.0/3, BigDecimal, :exact) -> 0.66666666666666662965923251249478198587894439697265625E0
|
250
|
+
puts Nio.convert(BigDecimal('2')/3, Float) -> 0.666666666666667
|
251
|
+
puts Nio.convert(BigDecimal('2')/3, Float, :exact) -> 0.666666666666667
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
=Details
|
256
|
+
|
257
|
+
===Defining formats
|
258
|
+
|
259
|
+
Say you want a numeric format based on the current default but with some aspects
|
260
|
+
changed, e.g. using comma as the decimal separator and with only 3 digits
|
261
|
+
of precision, we could do:
|
262
|
+
|
263
|
+
fmt = Fmt.default
|
264
|
+
fmt = fmt.sep(',')
|
265
|
+
fmt = fmt.prec(3)
|
266
|
+
|
267
|
+
That wasn't very nice. Fmt is a mutable class and have methods to modify
|
268
|
+
its state that end with a bang. We can use them to make this look better,
|
269
|
+
but note that we must create a copy of the default format before we
|
270
|
+
modify it:
|
271
|
+
|
272
|
+
fmt = Fmt.default.dup
|
273
|
+
fmt.sep! ','
|
274
|
+
fmt.prec! 3
|
275
|
+
|
276
|
+
Note that we had to make a duplicate of the default format in
|
277
|
+
order to modify it or we would have got an error
|
278
|
+
(if you want to modify the default format
|
279
|
+
you have to assign to it).
|
280
|
+
|
281
|
+
Now we can simplify this a little by passing a block to Fmt.default:
|
282
|
+
|
283
|
+
fmt = Fmt.default { |f|
|
284
|
+
f.sep! ','
|
285
|
+
f.prec! 3
|
286
|
+
}
|
287
|
+
|
288
|
+
But there's a more concise way to define the format by avoiding
|
289
|
+
the bang-methods and chaining all modifications:
|
290
|
+
|
291
|
+
fmt = Fmt.default.sep(',').prec(3)
|
292
|
+
|
293
|
+
Or even (using shortcut methods such as Fmt.sep or Fmt.prec):
|
294
|
+
|
295
|
+
fmt = Fmt.sep(',').prec(3)
|
296
|
+
|
297
|
+
If we don't want to base the new format on the current default, but use
|
298
|
+
the initial default instead, we would substitute new for default above,
|
299
|
+
except in the last case which always uses the default.
|
300
|
+
For example:
|
301
|
+
|
302
|
+
fmt = Fmt.new { |f|
|
303
|
+
f.sep! ','
|
304
|
+
f.prec! 3
|
305
|
+
}
|
306
|
+
|
307
|
+
fmt = Fmt.new.sep(',').prec(3)
|
308
|
+
|
309
|
+
If a particular case needs a format similar to fmt but with some modification
|
310
|
+
we would use, for example:
|
311
|
+
|
312
|
+
puts 0.1234567.nio_write(fmt.prec(5)) -> 0.12346
|
313
|
+
puts 0.1234567.nio_write(fmt) -> 0.123
|
314
|
+
|
315
|
+
===Exact and aproximate values
|
316
|
+
|
317
|
+
Float and BigDecimal are approximate in the sense that
|
318
|
+
a given value within the range of these types, (defined either
|
319
|
+
by an expression or as result of a computation) may no be
|
320
|
+
exactly represented and has to be substituted by the closest possible value.
|
321
|
+
With Float, which generally is a binary floating point, there's an
|
322
|
+
additional mismatch with the notation (decimal) used for input and
|
323
|
+
output.
|
324
|
+
|
325
|
+
For the following examples we assume that Float is an IEEE754 double precision
|
326
|
+
binary type (i.e. <tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>) which is the
|
327
|
+
common case (in all Ruby platforms I know of, at least).
|
328
|
+
|
329
|
+
0.1.nio_write(Fmt.prec(:exact)) -> 0.1
|
330
|
+
|
331
|
+
Well, that seems pretty clear... but let's complicate things a little:
|
332
|
+
|
333
|
+
0.1.nio_write(Fmt.prec(:exact).show_all_digits) -> 0.10000000000000001
|
334
|
+
|
335
|
+
Mmmm where does that last one came from? Now we're seen a little more exactly what
|
336
|
+
the actual value stored in the Float (the closest Float to 0.1) looks like.
|
337
|
+
|
338
|
+
But why didn't we see the second one-digit in the first try?
|
339
|
+
We requested "exact" precision!
|
340
|
+
|
341
|
+
Well, we didn't get it because it is not needed to specify exactly the inner value
|
342
|
+
of Float(1.0); when we convert 0.1 and round it to the nearest Float we get the
|
343
|
+
same value as when we use 0.10000000000000001.
|
344
|
+
Since we didn't request to see "all digits", we got only as few as possible.
|
345
|
+
|
346
|
+
0.1.nio_write(Fmt.prec(:exact).approx_mode(:exact)) -> 0.1000000000000000055511151231257827021181583404541015625
|
347
|
+
|
348
|
+
Hey! Where did all that stuff came from? Now we're really seeing the "exact" value of Float.
|
349
|
+
(We asked the conversion method to consider the Float an exactly defined value,
|
350
|
+
rather than an approximation to some other value).
|
351
|
+
But, why didn't we get all those digits when we asked for "all digits"?.
|
352
|
+
|
353
|
+
Because most are not significant;
|
354
|
+
the default "approx_mode" is to consider Float an approximate value and show only significant digits.
|
355
|
+
We define insignificant digits as those that can be replaced by any other digit without altering the Float
|
356
|
+
value when the number is rounded to the nearest float. By looking at our example we see that the 17 first digits
|
357
|
+
(just before the 555111...) must be significant: they cannot take an arbitrary value without altering the Float.
|
358
|
+
In fact all have well specified values except the last one that can be either 0 or 1 (but no other value). The next
|
359
|
+
digits (first insignificant, a 5) can be replaced by any other digit * (from 0 to 9) and the expression
|
360
|
+
0.10000000000000000* would still be rounded to Float(0.1)
|
361
|
+
|
362
|
+
|
363
|
+
===Insignificance
|
364
|
+
|
365
|
+
So, let's summarize the situation about inexact numbers: When the approximate mode of a format is <tt>:only_sig</tt>,
|
366
|
+
the digits of inexact (i.e. floating point) numbers are classified as significant or insignificant.
|
367
|
+
The latter are only shown if the property <tt>:all_digits</tt> is true
|
368
|
+
(which it is by default for <tt>:fix</tt>)
|
369
|
+
but since they are not meaningful they use a special character that by default is empty. You can
|
370
|
+
define that character to see where they are:
|
371
|
+
puts 0.1.nio_write(Fmt.mode(:fix,20).insignificant_digits('#')) -> 0.10000000000000001###
|
372
|
+
|
373
|
+
If we hadn't use the special character we would'nt even seen those digits:
|
374
|
+
|
375
|
+
puts 0.1.nio_write(Fmt.mode(:fix,20)) -> 0.10000000000000001
|
376
|
+
|
377
|
+
When the aproximate mode is defined as :exact, there's no distinction between significant or insignificant
|
378
|
+
digits, the number is taken as exactly defined and all digits are shown with their value.
|
379
|
+
|
380
|
+
puts 0.1.nio_write(Fmt.mode(:fix,20,:approx_mode=>:exact) -> 0.10000000000000000555
|
381
|
+
|
382
|
+
===Repeating Numerals
|
383
|
+
|
384
|
+
The common term is <b>repeating decimal</b> or <b>recurring decimal</b>, but since Nio support them for any base,
|
385
|
+
we'll call them <i>repeating numerals</i>.
|
386
|
+
|
387
|
+
Rational(1,3).nio_write -> 0.333...
|
388
|
+
|
389
|
+
We usually find that notation for the decimal expansion of 1/3 in texts, but that doesn't seem very accurate for
|
390
|
+
a conversion library, or is it?
|
391
|
+
|
392
|
+
Rational.nio_read('0.333...') -> Rational(1,3)
|
393
|
+
|
394
|
+
It works the other way! In fact the seemingly loose 0.333... was an exact representation for Nio.
|
395
|
+
|
396
|
+
All Rational numbers can be expressed as a repeating numeral in any base. Repeating numerals may have an infinite
|
397
|
+
number of digits, but from some point on they're just repetitions of the same (finite) sequence of digits.
|
398
|
+
|
399
|
+
By default Nio expresses that kind of repetition by appending two repetitions of the repeating sequence
|
400
|
+
after it and adding the ellipsis (so the repeated sequence appears three times, and, by the way Nio
|
401
|
+
uses three points rather than a real ellipsis characters).
|
402
|
+
This allow Nio to recognize the repeating sequence on input.
|
403
|
+
We can use a more economical notation by just marking the repeating sequence, rather than repeating it:
|
404
|
+
Rational(1,3).nio_write(Fmt.rep(:nreps=>0)) -> 0.<3>
|
405
|
+
We just requested for 0 as the number of repetitions (the default is 2) and got the sequence delimited by <>
|
406
|
+
(we can change those characters; we can even use just a left separator).
|
407
|
+
This is shorter and would allow to show the number better with special typography
|
408
|
+
(e.g. a bar over the repeated digits, a different color, etc.)
|
409
|
+
|
410
|
+
===BigDec()
|
411
|
+
|
412
|
+
BigDec() is a handy convenience to define BigDecimals; it permits us
|
413
|
+
to use BigDec(1) instead of BigDecimal('1')
|
414
|
+
(I find it tedious to type all those quotes.)
|
415
|
+
It can also be used with Float arguments, e.g.:
|
416
|
+
BigDec(0.5)
|
417
|
+
But this is a questionable use (for example it has been disregarded in Python Decimal.)
|
418
|
+
It is allowed here because BigDec's purpose is to be a shortcut notation
|
419
|
+
(BigDecimal() on the other hand should probably not accept Floats).
|
420
|
+
|
421
|
+
Users must be aware of the problems and details of the implementation.
|
422
|
+
Currently BigDec(x) for float x doesn't try to convert the exact value of x,
|
423
|
+
which can be achieved with <tt>BigDec(0.1,:exact)</tt>, but tries instead to produce
|
424
|
+
a simple value.
|
425
|
+
|
426
|
+
--
|
427
|
+
Dilemma: leav BigDec as now (simplify) o change to use default fmt conversion
|
428
|
+
a) => BigDec(1.0/3) == BigDec(Rational(1)/3)
|
429
|
+
b) => BigDec(1.0/3) == BigDec("0.3333333333333333")
|
430
|
+
in a, can we assure that NFmt.convert(BigDec(x),Float)==x ?
|
431
|
+
++
|
432
|
+
|
433
|
+
Since a floating point literal will, in general, convert to a Float of slightly different value,
|
434
|
+
and several distinct literals can convert to the same value, there will always be some compromise.
|
435
|
+
Here we've chosen to simplify values so that <tt>BigDec(0.1)==BigDecimal('0.1')</tt>,
|
436
|
+
but this implies that, for example, <tt>BigDecimal('0.10000000000000001')</tt> cannot be defined
|
437
|
+
with BigDec(), because <tt>Float(0.10000000000000001)==Float(0.1)</tt>.
|
438
|
+
|
439
|
+
In any case using BigDec on Floats have some risks because it relies on the Ruby interpreter
|
440
|
+
to parse floating point literal, and its behaviour is not stricly specified; in the usual case
|
441
|
+
(IEEE Double Floats and round-to-even) BigDec() will behave well, but some platforms may
|
442
|
+
behave differently.
|
443
|
+
|
444
|
+
===Rounding
|
445
|
+
|
446
|
+
Rounding is performed on both input and output.
|
447
|
+
When a value is formatted for output the number is rounded to the number of digits
|
448
|
+
that has been specified.
|
449
|
+
|
450
|
+
But also when a value must be read from text rounding it is necessary to choose the nearest
|
451
|
+
numeric representation (e.g. Float value).
|
452
|
+
|
453
|
+
Nio supports three rounding modes which determine how to round _ties_:
|
454
|
+
[<tt>:inf</tt>]
|
455
|
+
round to infinity
|
456
|
+
1.5 -> 2
|
457
|
+
-1.5 -> -2
|
458
|
+
[<tt>:even</tt>] round to even (to the nearest even digit)
|
459
|
+
1.5 -> 2
|
460
|
+
2.5 -> 2
|
461
|
+
-1.5 -> -2
|
462
|
+
-2.5 -> -2
|
463
|
+
[<tt>:zero</tt>] round to zero
|
464
|
+
1.5 -> 1
|
465
|
+
-1.5 -> -1
|
466
|
+
|
467
|
+
Rounding can be set with Nio::Fmt#mode and Nio::Fmt#prec for specific formats, and
|
468
|
+
the default rounding mode can be changed with Fmt.default_rounding_mode().
|
469
|
+
|
470
|
+
For round-trip conversions, a number should use the same rounding mode on input and output.
|
471
|
+
|
472
|
+
For Floats there's an additional issue here, because when we use floating point literals
|
473
|
+
on the Ruby code (such as 0.1 or 1E23) they are parsed and converted to Floating point values
|
474
|
+
by the Ruby interpreter which must apply some kind of rounding when the expression to be parsed
|
475
|
+
is equidistant from two Float values.
|
476
|
+
All the Ruby implementations I have tried have IEEE754 Double Float
|
477
|
+
(<tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>)
|
478
|
+
and floating point literals seem to be rounded according to the round-to-even rule, so that
|
479
|
+
is the initial default rounding mode.
|
480
|
+
|
481
|
+
====Examples
|
482
|
+
|
483
|
+
We assume the common implementation of float (<tt>Float::RADIX==2 && Float::MANT_DIG==53</tt>) here.
|
484
|
+
In that case, we can use the value 1E23, which is equidistant from two Floats
|
485
|
+
to check which kind of roundig does the interpreter use.
|
486
|
+
If it's round-to-even (the common case) we'll have:
|
487
|
+
1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:even) --> true
|
488
|
+
1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:zero) --> true
|
489
|
+
But if rounding is to infinity the previous check will be false and this will hold:
|
490
|
+
1E23 == Float.nio_read('1E23',Nio::Fmt.mode(:gen,:exact,:round=>:inf) --> true
|
491
|
+
(Well, with this example we can't really distinguish :even from :zero, but :zero is most probably not used)
|
492
|
+
|
493
|
+
Now, if we're using the same default rounding for Nio we will have:
|
494
|
+
1E23.nio_write == "1E23" --> true
|
495
|
+
Which will make you feel warm and fuzzy. But if the system rounding is different
|
496
|
+
we will get one of these ugly values:
|
497
|
+
fmt_inf = Nio::Fmt.mode(:gen,:exact,:round=>:inf)
|
498
|
+
fmt_even = Nio::Fmt.mode(:gen,:exact,:round=>:even)
|
499
|
+
Float.nio_read('1E23',fmt_inf).nio_write(fmt_even) -> "1.0000000000000001E23"
|
500
|
+
Float.nio_read('1E23',fmt_even).nio_write(fmt_inf) -> "9.999999999999999E22"
|
501
|
+
|
502
|
+
If the Ruby interpreter doesn't support any of the roundings of Nio, or if it doesn't correctly
|
503
|
+
round, the best solution would be to avoid using Float literals and use Float#nio_read instead.
|
504
|
+
|
505
|
+
===Conversions
|
506
|
+
|
507
|
+
Accurate conversion between numerical types can be performed with Nio.convert.
|
508
|
+
It takes three arguments: the value to convert, the class to convert it to (Float,BigDecimal,
|
509
|
+
Integer or Rational) and the conversion mode, either :exact, which is..., well, quite exact,
|
510
|
+
and :approx which tries to find a simpler value (e.g. 0.1 rather than 0.10000000000000001...)
|
511
|
+
within the accuray of the original value.
|
512
|
+
The :approx mode may be pretty slow in some cases.
|
513
|
+
|
514
|
+
Nio.convert(0.1,BigDecimal,:exact) -> 0.1000000000 0000000555 1115123125 ...
|
515
|
+
Nio.convert(0.1,BigDecimal,:approx) -> 0.1
|
516
|
+
|
517
|
+
We can check out the accuracy of the conversions:
|
518
|
+
|
519
|
+
Nio.convert(BigDec('1.234567890123456'),Float)==1.234567890123456 -> true
|
520
|
+
Nio.convert(BigDec(355)/226,Float)==(355.0/226) -> true
|
521
|
+
|
522
|
+
Thay may not look very impressive, but is much more accurate than BigDecimal#to_f
|
523
|
+
(at least in Ruby versions up to 1.8.6, mswin32 (specially) and linux) for which:
|
524
|
+
|
525
|
+
BigDecimal('1.234567890123456').to_f == 1.234567890123456 -> false
|
526
|
+
(BigDecimal("355")/226).to_f == (355.0/226.0) -> false
|
527
|
+
|
528
|
+
=License
|
529
|
+
|
530
|
+
This code is free to use under the terms of the GNU GENERAL PUBLIC LICENSE.
|
531
|
+
|
532
|
+
=Contact
|
533
|
+
|
534
|
+
Nio has been developed by Javier Goizueta (mailto:javier@goizueta.info).
|
535
|
+
|
536
|
+
You can contact me through Rubyforge:http://rubyforge.org/sendmessage.php?touser=25432
|
537
|
+
|
538
|
+
|
539
|
+
=More Information
|
540
|
+
|
541
|
+
* <b>What Every Computer Scientist Should Know About Floating-Point Arithmetic</b>
|
542
|
+
David Goldberg
|
543
|
+
- http://docs.sun.com/source/806-3568/ncg_goldberg.html
|
544
|
+
|
545
|
+
* <b>How to Read Floating Point Numbers Accurately</b>
|
546
|
+
William D. Clinger
|
547
|
+
- http://citeseer.ist.psu.edu/224562.html
|
548
|
+
|
549
|
+
* <b>Printing Floating-Point Numbers Quickly and Accurately</b>
|
550
|
+
Robert G. Burger & R. Kent Dybvig
|
551
|
+
- http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
|
552
|
+
|
553
|
+
* <b>Repeating Decimal</b>
|
554
|
+
- http://mathworld.wolfram.com/RepeatingDecimal.html
|
555
|
+
- http://en.wikipedia.org/wiki/Recurring_decimal
|
556
|
+
|
557
|
+
* For <b>floating point rationalization algorithms</b>, see my commented
|
558
|
+
source code for the <tt>rntlzr</tt> module from Nio,
|
559
|
+
which you can download in PDF here:
|
560
|
+
- http://perso.wanadoo.es/jgoizueta/dev/goi/rtnlzr.pdf
|
data/Rakefile
ADDED
data/SOURCE.txt
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
=Notes on the source code
|
2
|
+
|
3
|
+
The Ruby code in this project is generated from the source code
|
4
|
+
in the directory "source" by the Rake task *tangle*.
|
5
|
+
|
6
|
+
You can get the updated source code from the SVN repository
|
7
|
+
at Rubyforge (svn://rubyforge.org/var/svn/nio/trunk)
|
8
|
+
or from the "nio-source" package releases from the project.
|
9
|
+
|
10
|
+
The nuweb source files have extension .w
|
11
|
+
|
12
|
+
There's also a rake task named *weave* which generates
|
13
|
+
a documented form of the source code in PDF format,
|
14
|
+
which is saved to the directory source/pdf.
|
15
|
+
|
16
|
+
This tasks requires the program nuweb which converts
|
17
|
+
the nuweb sources (files with extension .w) in
|
18
|
+
directory source to either ruby files or
|
19
|
+
LaTeX files for the documentated source.
|
20
|
+
For the weave task latex and dvipdfm are also necessary.
|
21
|
+
|
22
|
+
You can get the PDF files corresponding to each realese
|
23
|
+
in the "nio-source-pdf" packages.
|
24
|
+
|
25
|
+
The necessary nuweb tool is available here for windows:
|
26
|
+
http://perso.wanadoo.es/jgoizueta/dev/tools/nuweb-win32.zip
|
27
|
+
|
28
|
+
For other systems the source code and an necessary patch
|
29
|
+
can be obtained here:
|
30
|
+
http://www.goizueta.info/javier/index.html?pg=dev/req.en.html
|
31
|
+
|