nio 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/nio/fmt.rb ADDED
@@ -0,0 +1,1872 @@
1
+ # Formatting numbers as text
2
+
3
+ # Copyright (C) 2003-2005, Javier Goizueta <javier@goizueta.info>
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+
10
+
11
+ require 'nio/tools'
12
+
13
+ require 'nio/repdec'
14
+
15
+ require 'nio/rtnlzr'
16
+
17
+ require 'rational'
18
+
19
+ require 'bigdecimal'
20
+
21
+ module Nio
22
+
23
+ # positional notation, unformatted numeric literal: used as intermediate form
24
+ class NeutralNum
25
+ include StateEquivalent
26
+ def initialize(s='',d='',p=nil,r=nil,dgs=DigitsDef.base(10), inexact=false, round=:inf)
27
+ set s,d,p,r,dgs,dgs, inexact, round
28
+ end
29
+ attr_reader :sign, :digits, :dec_pos, :rep_pos, :special, :inexact, :rounding
30
+ attr_writer :sign, :digits, :dec_pos, :rep_pos, :special, :inexact, :rounding
31
+
32
+ # set number
33
+ def set(s,d,p=nil,r=nil,dgs=DigitsDef.base(10),inexact=false,rounding=:inf,normalize=true)
34
+ @sign = s # sign: '+','-',''
35
+ @digits = d # digits string
36
+ @dec_pos = p==nil ? d.length : p # position of decimal point: 0=before first digit...
37
+ @rep_pos = r==nil ? d.length : r # first repeated digit (0=first digit...)
38
+ @dgs = dgs
39
+ @base = @dgs.radix
40
+ @inexact = inexact
41
+ @special = nil
42
+ @rounding = rounding
43
+ trimZeros unless inexact
44
+ self
45
+ end
46
+ # set infinite (:inf) and invalid (:nan) numbers
47
+ def set_special(s,sgn='') # :inf, :nan
48
+ @special = s
49
+ @sign = sgn
50
+ self
51
+ end
52
+
53
+ def base
54
+ @base
55
+ end
56
+ def base_digits
57
+ @dgs
58
+ end
59
+ def base_digits=(dd)
60
+ @dgs = dd
61
+ @base = @dgs.radix
62
+ end
63
+ def base=(b)
64
+ @dgs = DigitsDef.base(b)
65
+ @base=@dgs.radix
66
+ end
67
+
68
+ # check for special numbers (which have only special and sign attributes)
69
+ def special?
70
+ special != nil
71
+ end
72
+
73
+ # check for special numbers (which have only special and sign attributes)
74
+ def inexact?
75
+ @inexact
76
+ end
77
+
78
+ def dup
79
+ n = NeutralNum.new
80
+ if special?
81
+ n.set_special @special.dup, @sign.dup
82
+ else
83
+ #n.set @sign.dup, @digits.dup, @dec_pos.dup, @rep_pos.dup, @dgs.dup
84
+ # in Ruby 1.6.8 Float,BigNum,Fixnum doesn't respond to dup
85
+ n.set @sign.dup, @digits.dup, @dec_pos, @rep_pos, @dgs.dup, @inexact, @rounding
86
+ end
87
+ return n
88
+ end
89
+
90
+ def zero?
91
+ z = false
92
+ if !special
93
+ if digits==''
94
+ z = true
95
+ else
96
+ z = true
97
+ for i in (0...@digits.length)
98
+ if dig_value(i)!=0
99
+ z = false
100
+ break
101
+ end
102
+ end
103
+ end
104
+ end
105
+ z
106
+ end
107
+
108
+ def round!(n, mode=:fix, dir=nil)
109
+ dir ||= rounding
110
+ trimLeadZeros
111
+ if n==:exact
112
+ return unless @inexact
113
+ n = @digits.size
114
+ end
115
+
116
+ n += @dec_pos if mode==:fix
117
+ n = [n,@digits.size].min if @inexact
118
+
119
+ adj = 0
120
+ dv = :tie
121
+ if @inexact && n==@digits.size
122
+ dv = @inexact==:roundup ? :hi : :lo
123
+ else
124
+ v = dig_value(n)
125
+ v2 = 2*v
126
+ if v2 < @base # v<((@base+1)/2)
127
+ dv = :lo
128
+ elsif v2 > @base # v>(@base/2)
129
+ dv = :hi
130
+ else
131
+
132
+ (n+1...@digits.length).each do |i|
133
+ if dig_value(i)>0
134
+ dv = :hi
135
+ break
136
+ end
137
+ end
138
+
139
+ dv = :hi if dv==:tie && @rep_pos<=n
140
+ end
141
+ end
142
+
143
+ if dv==:hi
144
+ adj = +1
145
+ elsif dv==:tie
146
+ if dir==:inf # towards nearest +/-infinity
147
+ adj = +1
148
+ elsif dir==:even # to nearest even digit (IEEE unbiased rounding)
149
+ adj = +1 if (dig_value(n-1)%2)!=0
150
+ elsif dir==:zero # towards zero
151
+ adj=0
152
+ # elsif dir==:odd
153
+ # adj = +1 unless (dig_value(n-1)%2)!=0
154
+ end
155
+ end
156
+
157
+ if n>@digits.length
158
+ (@digits.length...n).each do |i|
159
+ @digits << dig_char(dig_value(i))
160
+ @rep_pos += 1
161
+ end
162
+ end
163
+
164
+ prefix = ''
165
+ i = n-1
166
+ while adj!=0
167
+ v = dig_value(i)
168
+ v += adj
169
+ adj = 0
170
+ if v<0
171
+ v += @base
172
+ adj = -1
173
+ elsif v>=@base
174
+ v -= @base
175
+ adj = +1
176
+ end
177
+ if i<0
178
+ prefix = dig_char(v)+prefix
179
+ elsif i<@digits.length
180
+ @digits[i] = dig_char(v)
181
+ end
182
+ i += -1
183
+ end
184
+
185
+ if n<0
186
+ @digits = ""
187
+ else
188
+ @digits = @digits[0...n]
189
+ end
190
+ @rep_pos = @digits.length
191
+
192
+ if prefix!=''
193
+ @digits = prefix + @digits
194
+ @dec_pos += prefix.length
195
+ @rep_pos += prefix.length
196
+ end
197
+
198
+
199
+ end
200
+
201
+ def round(n, mode=:fix, dir=nil)
202
+ dir ||= rounding
203
+ nn = dup
204
+ nn.round!(n,mode,dir)
205
+ return nn
206
+ end
207
+
208
+ def trimTrailZeros()
209
+ i = @digits.length
210
+ while i>0 && dig_value(i-1)==0
211
+ i -= 1
212
+ end
213
+ if @rep_pos>=i
214
+ @digits = @digits[0...i]
215
+ @rep_pos = i
216
+ end
217
+
218
+ if @digits==''
219
+ @digits = dig_char(0) # '0'
220
+ @rep_pos = 1
221
+ @dec_pos = 1
222
+ end
223
+
224
+ end
225
+
226
+ def trimLeadZeros()
227
+ i = 0
228
+ while i<@digits.length && dig_value(i)==0
229
+ i += 1
230
+ end
231
+ @digits = @digits[i...@digits.length]
232
+ @dec_pos -= i
233
+ @rep_pos -= i
234
+
235
+ if @digits==''
236
+ @digits = dig_char(0) # '0'
237
+ @rep_pos = 1
238
+ @dec_pos = 1
239
+ end
240
+
241
+ end
242
+
243
+ def trimZeros()
244
+ trimLeadZeros
245
+ trimTrailZeros
246
+ end
247
+
248
+ protected
249
+
250
+ def dig_value(i)
251
+ v = 0
252
+ if i>=@rep_pos
253
+ i -= @digits.length
254
+ i %= @digits.length - @rep_pos if @rep_pos<@digits.length
255
+ i += @rep_pos
256
+ end
257
+ if i>=0 && i<@digits.length
258
+ v = @dgs.digit_value(@digits[i]) #digcode_value(@digits[i])
259
+ end
260
+ return v>=0 && v<@base ? v : nil
261
+ end
262
+ #def digcode_value(c)
263
+ # v = c-?0
264
+ # if v>9
265
+ # v = 10 + c.chr.downcase[0] - ?a
266
+ # end
267
+ # v
268
+ # @dgs.digit_value(c)
269
+ #end
270
+
271
+ def dig_char(v)
272
+ c = ''
273
+ if v!=nil && v>=0 && v<@base
274
+ c = @dgs.digit_char(v).chr
275
+ end
276
+ c
277
+ end
278
+
279
+ end
280
+
281
+ class NeutralNum
282
+ public
283
+ def to_RepDec
284
+ n = RepDec.new(@base)
285
+ if special?
286
+
287
+ case special
288
+ when :nan
289
+ n.ip = :indeterminate
290
+ when :inf
291
+ if sign=='-'
292
+ n.ip = :posinfinity
293
+ else
294
+ n.ip :neginfinity
295
+ end
296
+ else
297
+ n = nil
298
+ end
299
+
300
+ else
301
+ if dec_pos<=0
302
+ n.ip = 0
303
+ n.d = text_to_digits(dig_char(0)*(-dec_pos) + digits)
304
+ elsif dec_pos >= digits.length
305
+ n.ip = digits.to_i(@base)
306
+ if rep_pos<dec_pos
307
+ i=0
308
+ (dec_pos-digits.length).times do
309
+ n.ip *= @base
310
+ n.ip += @dgs.digit_value(digits[rep_pos+i]) if rep_pos+i<digits.length
311
+ i += 1
312
+ i=0 if i>=digits.length-rep_pos
313
+ end
314
+ n.d = []
315
+ while i<digits.length-rep_pos
316
+ n.d << @dgs.digit_value(digits[rep_pos+i])
317
+ i += 1
318
+ end
319
+ new_rep_pos = n.d.size + dec_pos
320
+ n.d += text_to_digits(digits[rep_pos..-1])
321
+ self.rep_pos = new_rep_pos
322
+ else
323
+ n.ip *= @base**(dec_pos-digits.length)
324
+ n.d = []
325
+ end
326
+ else
327
+ n.ip = digits[0...dec_pos].to_i(@base)
328
+ n.d = text_to_digits(digits[dec_pos..-1])
329
+ if rep_pos<dec_pos
330
+ new_rep_pos = n.d.size + dec_pos
331
+ n.d += text_to_digits(digits[rep_pos..-1])
332
+ self.rep_pos = new_rep_pos
333
+ puts "--rep_pos=#{rep_pos}"
334
+ end
335
+ end
336
+ n.sign = -1 if sign=='-'
337
+ n.rep_i = rep_pos - dec_pos
338
+ end
339
+ n.normalize!(!inexact) # keep trailing zeros for inexact numbers
340
+ return n
341
+ end
342
+ protected
343
+ def text_to_digits(txt)
344
+ #txt.split('').collect{|c| @dgs.digit_value(c)}
345
+ ds = []
346
+ txt.each_byte{|b| ds << @dgs.digit_value(b)}
347
+ ds
348
+ end
349
+ end
350
+
351
+ class RepDec
352
+ public
353
+ def to_NeutralNum(base_dgs=nil)
354
+ num = NeutralNum.new
355
+ if !ip.is_a?(Integer)
356
+
357
+ case ip
358
+ when :indeterminate
359
+ num.set_special :nan
360
+ when :posinfinity
361
+ num.set_special :inf,'+'
362
+ when :neginfinity
363
+ num.set_special :inf,'-'
364
+ else
365
+ num = nil
366
+ end
367
+
368
+ else
369
+ base_dgs ||= DigitsDef.base(@radix)
370
+ # assert base_dgs.radix == @radix
371
+ signch = sign<0 ? '-' : '+'
372
+ decimals = ip.to_s(@radix)
373
+ dec_pos = decimals.length
374
+ d.each {|dig| decimals << base_dgs.digit_char(dig) }
375
+ rep_pos = rep_i==nil ? decimals.length : dec_pos + rep_i
376
+ num.set signch, decimals, dec_pos, rep_pos, base_dgs
377
+ end
378
+ return num
379
+ end
380
+ end
381
+
382
+ # A Fmt object defines a numeric format.
383
+ #
384
+ # The formatting aspects managed by Fmt are:
385
+ # * mode and precision
386
+ # - #mode() and #orec() set the main paramters
387
+ # - see also #show_all_digits(), #approx_mode(), #insignificant_digits(),
388
+ # #sci_digits() and #show_plus()
389
+ # * separators
390
+ # - see #sep() and #grouping()
391
+ # * field justfification
392
+ # - #width() and the shortcut #pad0s()
393
+ # * numerical base
394
+ # - #base()
395
+ # * repeating numerals
396
+ # - #rep()
397
+ #
398
+ # Note that for every aspect there are also corresponding _mutator_
399
+ # methos (its name ending with a bang) that modify an object in place,
400
+ # instead of returning an altered copy.
401
+ #
402
+ # This class also contains class methods for numeric conversion:
403
+ # * Fmt.convert
404
+ # and for default and other predefined formats:
405
+ # * Fmt.default / Fmt.default=
406
+ # * Fmt.[] / Fmt.[]=
407
+ #
408
+ # The actual formatted reading and writting if performed by
409
+ # * #nio_write() (Nio::Formattable#nio_write)
410
+ # * #nio_read() (Nio::Formattable::ClassMethods#nio_read)
411
+ # Finally numerical objects can be rounded according to a format:
412
+ # * #nio_round() (Nio::Formattable#nio_round)
413
+ class Fmt
414
+ include StateEquivalent
415
+
416
+ class Error < StandardError # :nodoc:
417
+ end
418
+ class InvalidOption < Error # :nodoc:
419
+ end
420
+ class InvalidFormat < Error # :nodoc:
421
+ end
422
+
423
+ @@default_rounding_mode = :even
424
+ def initialize()
425
+
426
+ @dec_sep = '.'
427
+ @grp_sep = ','
428
+ @grp = []
429
+
430
+ @ndig = :exact
431
+ @mode=:gen
432
+ @round=Fmt.default_rounding_mode
433
+ @all_digits = false
434
+ @approx = :only_sig
435
+ @non_sig = '' # marker for insignificant digits of inexact values e.g. '#','0'
436
+ @sci_format = 1 # number of integral digits in the mantissa: -1 for all
437
+
438
+ @show_plus = false
439
+
440
+ @rep_begin = '<'
441
+ @rep_end = '>'
442
+ @rep_auto = '...'
443
+ @rep_n = 2
444
+ @rep_in = true
445
+
446
+ @width = 0
447
+ @fill_char = ' '
448
+ @adjust=:right
449
+
450
+ @base_radix = 10
451
+ @base_uppercase = true
452
+ @base_digits = DigitsDef.base(@base_radix, !@base_uppercase)
453
+ @show_base = false
454
+ @base_indicators = { 2=>'b', 8=>'o', 10=>'', 16=>'h', 0=>'r'} # 0: generic (used with radix)
455
+ @base_prefix = false
456
+
457
+ @nan_txt = 'NAN'
458
+ @inf_txt = 'Infinity'
459
+
460
+ yield self if block_given?
461
+ end
462
+
463
+ # Defines the separators used in numerals. This is relevant to
464
+ # both input and output.
465
+ #
466
+ # The first argument is the radix point separator (usually
467
+ # a point or a comma; by default it is a point.)
468
+ #
469
+ # The second argument is the group separator.
470
+ #
471
+ # Finally, the third argument is an array that defines the groups
472
+ # of digits to separate.
473
+ # By default it's [], which means that no grouping will be produced on output
474
+ # (but the group separator defined will be ignored in input.)
475
+ # To produce the common thousands separation a value of [3] must be passed,
476
+ # which means that groups of 3 digits are used.
477
+ def sep(dec_sep,grp_sep=nil,grp=nil)
478
+ dup.sep!(dec_sep,grp_sep,grp)
479
+ end
480
+ # This is the mutator version of #sep().
481
+ def sep!(dec_sep,grp_sep=nil,grp=nil)
482
+ set! :dec_sep=>dec_sep, :grp_sep=>grp_sep, :grp=>grp
483
+ end
484
+
485
+ # This defines the grouping of digits (which can also be defined in #sep()
486
+ def grouping(grp=[3],grp_sep=nil)
487
+ dup.grouping!(grp,grp_sep)
488
+ end
489
+ # This is the mutator version of #grouping().
490
+ def grouping!(grp=[3],grp_sep=nil)
491
+ set! :grp_sep=>grp_sep, :grp=>grp
492
+ end
493
+
494
+ # This is a shortcut to return a new default Fmt object
495
+ # and define the separators as with #sep().
496
+ def Fmt.sep(dec_sep,grp_sep=nil,grp=nil)
497
+ Fmt.default.sep(dec_sep,grp_sep,grp)
498
+ end
499
+ # This is a shortcut to return a new default Fmt object
500
+ # and define the grouping as with #grouping().
501
+ def Fmt.grouping(grp=[3],grp_sep=nil)
502
+ Fmt.default.grouping(grp,grp_sep)
503
+ end
504
+
505
+ # Define the formatting mode. There are two fixed parameters:
506
+ # - <tt>mode</tt> (only relevant for output)
507
+ # [<tt>:gen</tt>]
508
+ # (general) chooses automatically the shortes format
509
+ # [<tt>:fix</tt>]
510
+ # (fixed precision) is a simple format with a fixed number of digits
511
+ # after the point
512
+ # [<tt>:sig</tt>]
513
+ # (significance precision) is like :fix but using significant digits
514
+ # [<tt>:sci</tt>]
515
+ # (scientific) is the exponential form 1.234E2
516
+ # - <tt>precision</tt> (number of digits or :exact, only used for output)
517
+ # [<tt>exact</tt>]
518
+ # means that as many digits as necessary to unambiguosly define the
519
+ # value are used; this is the default.
520
+ #
521
+ # Other paramters can be passed in a hash after <tt>precision</tt>
522
+ # - <tt>:round</tt> rounding mode applied to conversions
523
+ # (this is relevant for both input and output). It must be one of:
524
+ # [<tt>:inf</tt>]
525
+ # rounds towards infinite; 1.5 is rounded to 2, -1.5 to -2
526
+ # [<tt>:zero</tt>]
527
+ # rounds towards zero; 1.5 is rounded to 1, -1.5 to 2
528
+ # [<tt>:even</tt>]
529
+ # rounds to the nearest even digit 1.5 rounds to 2, 2.5 to 2
530
+ # - <tt>:approx</tt> approximate mode
531
+ # [<tt>:only_sig</tt>]
532
+ # (the default) treats the value as an approximation and only
533
+ # significant digits (those that cannot take an arbitrary value without
534
+ # changing the specified value) are shown.
535
+ # [<tt>:exact</tt>]
536
+ # the value is interpreted as exact, there's no distinction between
537
+ # significant and insignificant digits.
538
+ # [<tt>:simplify</tt>]
539
+ # the value is simplified, if possible to a simpler (rational) value.
540
+ # - <tt>:show_all_digits</tt> if true, this forces to show digits that
541
+ # would otherwise not be shown in the <tt>:gen</tt> format: trailing
542
+ # zeros of exact types or non-signficative digits of inexact types.
543
+ # - <tt>:nonsignficative_digits</tt> assigns a character to display
544
+ # insignificant digits, # by default
545
+ def mode(mode,precision=nil,options={})
546
+ dup.mode!(mode,precision,options)
547
+ end
548
+ # This is the mutator version of #mode().
549
+ def mode!(mode,precision=nil,options={})
550
+ set! options.merge(:mode=>mode, :ndig=>precision)
551
+ end
552
+
553
+ # Defines the formatting mode like #mode() but using a different
554
+ # order of the first two parameters parameters, which is useful
555
+ # to change the precision only. Refer to #mode().
556
+ def prec(precision,mode=nil, options={})
557
+ dup.prec! precision, mode, options
558
+ end
559
+ # This is the mutator version of #prec().
560
+ def prec!(precision,mode=:gen, options={})
561
+ set! options.merge(:mode=>mode, :ndig=>precision)
562
+ end
563
+
564
+ # This is a shortcut to return a new default Fmt object
565
+ # and define the formatting mode as with #mode()
566
+ def Fmt.mode(mode,ndig=nil,options={})
567
+ Fmt.default.mode(mode,ndig,options)
568
+ end
569
+ # This is a shortcut to return a new default Fmt object
570
+ # and define the formatting mode as with #prec()
571
+ def Fmt.prec(ndig,mode=nil,options={})
572
+ Fmt.default.prec(ndig,mode,options)
573
+ end
574
+
575
+ # Rounding mode used when not specified otherwise
576
+ def Fmt.default_rounding_mode
577
+ @@default_rounding_mode
578
+ end
579
+ # The default rounding can be changed here; it starts with the value :even.
580
+ # See the rounding modes available in the description of method #mode().
581
+ def Fmt.default_rounding_mode=(m)
582
+ @@default_rounding_mode=m
583
+ Fmt.default = Fmt.default.round(m)
584
+ end
585
+
586
+ # This controls the display of the digits that are not necessary
587
+ # to specify the value unambiguosly (e.g. trailing zeros).
588
+ #
589
+ # The true (default) value forces the display of the requested number of digits
590
+ # and false will display only necessary digits.
591
+ def show_all_digits(ad=true)
592
+ dup.show_all_digits! ad
593
+ end
594
+ # This is the mutator version of #show_all_digits().
595
+ def show_all_digits!(ad=true)
596
+ set! :all_digits=>ad
597
+ end
598
+ # This defines the approximate mode (:only_sig, :exact, :simplify)
599
+ # just like the last parameter of #mode()
600
+ def approx_mode(mode)
601
+ dup.approx_mode! mode
602
+ end
603
+ # This is the mutator version of #approx_mode().
604
+ def approx_mode!(mode)
605
+ set! :approx=>mode
606
+ end
607
+ # Defines a character to stand for insignificant digits when
608
+ # a specific number of digits has been requested greater than then
609
+ # number of significant digits (for approximate types).
610
+ def insignificant_digits(ch='#')
611
+ dup.insignificant_digits! ch
612
+ end
613
+ # This is the mutator version of #insignificant_digits().
614
+ def insignificant_digits!(ch='#')
615
+ ch ||= ''
616
+ set! :non_sig=>ch
617
+ end
618
+ # Defines the number of significan digits before the radix separator
619
+ # in scientific notation. A negative value will set all significant digits
620
+ # before the radix separator. The special value <tt>:eng</tt> activates
621
+ # _engineering_ mode, in which the exponents are multiples of 3.
622
+ #
623
+ # For example:
624
+ # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(0) -> 0.1234E0
625
+ # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(3) -> 123.4E-3
626
+ # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(-1) -> 1234.E-4
627
+ # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(:eng) -> 123.4E-3
628
+ def sci_digits(n=-1)
629
+ dup.sci_digits! n
630
+ end
631
+ # This is the mutator version of #sci_digits().
632
+ def sci_digits!(n=-1)
633
+ set! :sci_format=>n
634
+ end
635
+
636
+ # This is a shortcut to return a new default Fmt object
637
+ # and define show_all_digits
638
+ def Fmt.show_all_digits(v=true)
639
+ Fmt.default.show_all_digits(v)
640
+ end
641
+ # This is a shortcut to return a new default Fmt object
642
+ # and define approx_mode
643
+ def Fmt.approx_mode(v)
644
+ Fmt.default.approx_mode(v)
645
+ end
646
+ # This is a shortcut to return a new default Fmt object
647
+ # and define insignificant digits
648
+ def Fmt.insignificant_digits(v='#')
649
+ Fmt.default.insignificant_digits(v)
650
+ end
651
+ # This is a shortcut to return a new default Fmt object
652
+ # and define sci_digits
653
+ def Fmt.sci_digits(v=-1)
654
+ Fmt.default.sci_digits(v)
655
+ end
656
+
657
+ # Controls the display of the sign for positive numbers
658
+ def show_plus(sp=true)
659
+ dup.show_plus! sp
660
+ end
661
+ # This is the mutator version of #show_plus().
662
+ def show_plus!(sp=true)
663
+ set! :show_plus=>sp
664
+ end
665
+
666
+ # This is a shortcut to return a new default Fmt object
667
+ # and define show_plus
668
+ def Fmt.show_plus(v=true)
669
+ Fmt.default.show_plus(v)
670
+ end
671
+
672
+ # Defines the handling and notation for repeating numerals. The parameters
673
+ # can be passed in order or in a hash:
674
+ # [<tt>:begin</tt>] is the beginning delimiter of repeating section (<)
675
+ # [<tt>:end</tt>] is the ending delimiter of repeating section (<)
676
+ # [<tt>:suffix</tt>] is the suffix used to indicate a implicit repeating decimal
677
+ # [<tt>:rep</tt>]
678
+ # if this parameter is greater than zero, on output the repeating section
679
+ # is repeated the indicated number of times followed by the suffix;
680
+ # otherwise the delimited notation is used.
681
+ # [<tt>:read</tt>]
682
+ # (true/false) determines if repeating decimals are
683
+ # recognized on input (true)
684
+ def rep(*params)
685
+ dup.rep!(*params)
686
+ end
687
+ # This is the mutator version of #rep().
688
+ def rep!(*params)
689
+
690
+ params << {} if params.size==0
691
+ if params[0].kind_of?(Hash)
692
+ params = params[0]
693
+ else
694
+ begch,endch,autoch,rep,read = *params
695
+ params = {:begin=>begch,:end=>endch,:suffix=>autoch,:nreps=>rep,:read=>read}
696
+ end
697
+
698
+ set! params
699
+ end
700
+
701
+ # This is a shortcut to return a new default Fmt object
702
+ # and define the repeating decimals mode as with #rep()
703
+ def Fmt.rep(*params)
704
+ Fmt.default.rep(*params)
705
+ end
706
+
707
+ # Sets the justificaton width, mode and fill character
708
+ #
709
+ # The mode accepts these values:
710
+ # [<tt>:right</tt>] (the default) justifies to the right (adds padding at the left)
711
+ # [<tt>:left</tt>] justifies to the left (adds padding to the right)
712
+ # [<tt>:internal</tt>] like :right, but the sign is kept to the left, outside the padding.
713
+ # [<tt>:center</tt>] centers the number in the field
714
+ def width(w,adj=nil,ch=nil)
715
+ dup.width! w,adj,ch
716
+ end
717
+ # This is the mutator version of #width().
718
+ def width!(w,adj=nil,ch=nil)
719
+ set! :width=>w, :adjust=>adj, :fill_char=>ch
720
+ end
721
+ # Defines the justification (as #width()) with the given
722
+ # width, internal mode and filling with zeros.
723
+ #
724
+ # Note that if you also use grouping separators, the filling 0s
725
+ # will not be separated.
726
+ def pad0s(w)
727
+ dup.pad0s! w
728
+ end
729
+ # This is the mutator version of #pad0s().
730
+ def pad0s!(w)
731
+ width! w, :internal, '0'
732
+ end
733
+ # This is a shortcut to create a new Fmt object and define the width
734
+ # parameters as with #widht()
735
+ def Fmt.width(w,adj=nil,ch=nil)
736
+ Fmt.default.width(w,adj,ch)
737
+ end
738
+ # This is a shortcut to create a new Fmt object and define numeric
739
+ # padding as with #pad0s()
740
+ def Fmt.pad0s(w)
741
+ Fmt.default.pad0s(w)
742
+ end
743
+
744
+ # defines the numerical base; the second parameters forces the use
745
+ # of uppercase letters for bases greater than 10.
746
+ def base(b, uppercase=nil)
747
+ dup.base! b, uppercase
748
+ end
749
+ # This is the mutator version of #base().
750
+ def base!(b, uppercase=nil)
751
+ set! :base_radix=>b, :base_uppercase=>uppercase
752
+ end
753
+ # This is a shortcut to create a new Fmt object and define the base
754
+ def Fmt.base(b, uppercase=nil)
755
+ Fmt.default.base(b, uppercase)
756
+ end
757
+ # returns the exponent char used with the specified base
758
+ def get_exp_char(base) # :nodoc:
759
+ base ||= @base_radix
760
+ base<=10 ? 'E' : '^'
761
+ end
762
+
763
+ # returns the base
764
+ def get_base # :nodoc:
765
+ @base_radix
766
+ end
767
+ # returns the digit characters used for a base
768
+ def get_base_digits(b=nil) # :nodoc:
769
+ (b.nil? || b==@base_radix) ? @base_digits : DigitsDef.base(b,!@base_uppercase)
770
+ end
771
+ # returns true if uppercase digits are used
772
+ def get_base_uppercase? # :nodoc:
773
+ @base_uppercase
774
+ end
775
+
776
+ # returns the formatting mode
777
+ def get_mode # :nodoc:
778
+ @mode
779
+ end
780
+ # returns the precision (number of digits)
781
+ def get_ndig # :nodoc:
782
+ @ndig
783
+ end
784
+ # return the show_all_digits state
785
+ def get_all_digits? # :nodoc:
786
+ @all_digits
787
+ end
788
+ # returns the approximate mode
789
+ def get_approx # :nodoc:
790
+ @approx
791
+ end
792
+
793
+ # returns the rounding mode
794
+ def get_round # :nodoc:
795
+ @round
796
+ end
797
+
798
+ # Method used internally to format a neutral numeral
799
+ def nio_write_formatted(neutral) # :nodoc:
800
+ str = ''
801
+ if neutral.special?
802
+ str << neutral.sign
803
+ case neutral.special
804
+ when :inf
805
+ str << @inf_txt
806
+ when :nan
807
+ str << @nan_txt
808
+ end
809
+ else
810
+ zero = get_base_digits(neutral.base).digit_char(0).chr
811
+ neutral = neutral.dup
812
+ round! neutral
813
+ if neutral.zero?
814
+ str << neutral.sign if neutral.sign=='-' # show - if number was <0 before rounding
815
+ str << zero
816
+ if @ndig.kind_of?(Numeric) && @ndig>0 && @mode==:fix
817
+ str << @dec_sep << zero*@ndig
818
+ end
819
+ else
820
+
821
+ neutral.trimLeadZeros
822
+ actual_mode = @mode
823
+ trim_trail_zeros = !@all_digits # false
824
+
825
+ integral_digits = @sci_format
826
+ if integral_digits == :eng
827
+ integral_digits = 1
828
+ while (neutral.dec_pos - integral_digits).modulo(3) != 0
829
+ integral_digits += 1
830
+ end
831
+ elsif integral_digits==:all || integral_digits < 0
832
+ if neutral.inexact? && @non_sig!='' && @ndig.kind_of?(Numeric)
833
+ integral_digits = @ndig
834
+ else
835
+ integral_digits = neutral.digits.length
836
+ end
837
+ end
838
+ exp = neutral.dec_pos - integral_digits
839
+
840
+ case actual_mode
841
+ when :gen # general (automatic)
842
+ # @ndig means significant digits
843
+ actual_mode = :sig
844
+ actual_mode = :sci if use_scientific?(neutral, exp)
845
+ trim_trail_zeros = !@all_digits # true
846
+ end
847
+
848
+ case actual_mode
849
+ when :fix, :sig #, :gen
850
+
851
+ str << neutral.sign if @show_plus || neutral.sign!='+'
852
+
853
+ if @show_base && @base_prefix
854
+ b_prefix = @base_indicators[neutral.base]
855
+ str << b_prefix if b_prefix
856
+ end
857
+
858
+ if @ndig==:exact
859
+ neutral.sign = '+'
860
+ str << neutral.to_RepDec.getS(@rep_n, getRepDecOpt(neutral.base))
861
+ else
862
+ #zero = get_base_digits.digit_char(0).chr
863
+ ns_digits = ''
864
+
865
+ nd = neutral.digits.length
866
+ if actual_mode==:fix
867
+ nd -= neutral.dec_pos
868
+ end
869
+ if neutral.inexact? && @ndig>nd # assert no rep-dec.
870
+ ns_digits = @non_sig*(@ndig-nd)
871
+ end
872
+
873
+ digits = neutral.digits + ns_digits
874
+ if neutral.dec_pos<=0
875
+ str << zero+@dec_sep+zero*(-neutral.dec_pos) + digits
876
+ elsif neutral.dec_pos >= digits.length
877
+ str << group(digits + zero*(neutral.dec_pos-digits.length))
878
+ else
879
+ str << group(digits[0...neutral.dec_pos]) + @dec_sep + digits[neutral.dec_pos..-1]
880
+ end
881
+ end
882
+
883
+ #str = str.chomp(zero).chomp(@dec_sep) if trim_trail_zeros && str.include?(@dec_sep)
884
+ if trim_trail_zeros && str.include?(@dec_sep) && str[-@rep_auto.size..-1]!=@rep_auto
885
+ str.chop! while str[-1]==zero[0]
886
+ str.chomp!(@dec_sep)
887
+ #puts str
888
+ end
889
+
890
+
891
+ when :sci
892
+
893
+ str << neutral.sign if @show_plus || neutral.sign!='+'
894
+
895
+ if @show_base && @base_prefix
896
+ b_prefix = @base_indicators[neutral.base]
897
+ str << b_prefix if b_prefix
898
+ end
899
+
900
+ #zero = get_base_digits.digit_char(0).chr
901
+ if @ndig==:exact
902
+ neutral.sign = '+'
903
+ neutral.dec_pos-=exp
904
+ str << neutral.to_RepDec.getS(@rep_n, getRepDecOpt(neutral.base))
905
+ else
906
+ ns_digits = ''
907
+
908
+ nd = neutral.digits.length
909
+ if actual_mode==:fix
910
+ nd -= neutral.dec_pos
911
+ end
912
+ if neutral.inexact? && @ndig>nd # assert no rep-dec.
913
+ ns_digits = @non_sig*(@ndig-nd)
914
+ end
915
+
916
+ digits = neutral.digits + ns_digits
917
+ str << ((integral_digits<1) ? zero : digits[0...integral_digits])
918
+ str << @dec_sep
919
+ str << digits[integral_digits...@ndig]
920
+ pad_right =(@ndig+1-str.length)
921
+ str << zero*pad_right if pad_right>0 && !neutral.inexact? # maybe we didn't have enought digits
922
+ end
923
+
924
+ #str = str.chomp(zero).chomp(@dec_sep) if trim_trail_zeros && str.include?(@dec_sep)
925
+ if trim_trail_zeros && str.include?(@dec_sep) && str[-@rep_auto.size..-1]!=@rep_auto
926
+ str.chop! while str[-1]==zero[0]
927
+ str.chomp!(@dec_sep)
928
+ #puts str
929
+ end
930
+
931
+ str << get_exp_char(neutral.base)
932
+ str << exp.to_s
933
+
934
+ end
935
+
936
+ end
937
+ end
938
+
939
+ if @show_base && !@base_prefix
940
+ b_prefix = @base_indicators[neutral.base]
941
+ str << b_prefix if b_prefix
942
+ end
943
+
944
+
945
+ if @width>0 && @fill_char!=''
946
+ l = @width - str.length
947
+ if l>0
948
+ case @adjust
949
+ when :internal
950
+ sign = ''
951
+ if str[0,1]=='+' || str[0,1]=='-'
952
+ sign = str[0,1]
953
+ str = str[1...str.length]
954
+ end
955
+ str = sign + @fill_char*l + str
956
+ when :center
957
+ str = @fill_char*(l/2) + str + @fill_char*(l-l/2)
958
+ when :right
959
+ str = @fill_char*l + str
960
+ when :left
961
+ str = str + @fill_char*l
962
+ end
963
+ end
964
+ end
965
+
966
+ return str
967
+ end
968
+
969
+ # round a neutral numeral according to the format options
970
+ def round!(neutral) # :nodoc:
971
+ neutral.round! @ndig, @mode, @round
972
+ end
973
+
974
+ @@sci_fmt = nil
975
+
976
+ def nio_read_formatted(txt) # :nodoc:
977
+ txt = txt.dup
978
+ num = nil
979
+
980
+ base = nil
981
+
982
+ base ||= get_base
983
+
984
+ zero = get_base_digits(base).digit_char(0).chr
985
+ txt.tr!(@non_sig,zero) # we don't simply remove it because it may be before the radix point
986
+
987
+ exp = 0
988
+ x_char = get_exp_char(base)
989
+
990
+ exp_i = txt.index(x_char)
991
+ exp_i = txt.index(x_char.downcase) if exp_i===nil
992
+ if exp_i!=nil
993
+ exp = txt[exp_i+1...txt.length].to_i
994
+ txt = txt[0...exp_i]
995
+ end
996
+
997
+
998
+ opt = getRepDecOpt(base)
999
+ if @rep_in
1000
+ #raise InvalidFormat,"Invalid numerical base" if base!=10
1001
+ rd = RepDec.new # get_base not necessary: setS sets it from options
1002
+ rd.setS txt, opt
1003
+ num = rd.to_NeutralNum(opt.digits)
1004
+ else
1005
+ # to do: use RepDec.parse; then build NeutralNum directly
1006
+ opt.set_delim '',''
1007
+ opt.set_suffix ''
1008
+ rd = RepDec.new # get_base not necessary: setS sets it from options
1009
+ rd.setS txt, opt
1010
+ num = rd.to_NeutralNum(opt.digits)
1011
+ end
1012
+ num.rounding = get_round
1013
+ num.dec_pos += exp
1014
+ return num
1015
+ end
1016
+
1017
+
1018
+ @@fmts = {
1019
+ :def=>Fmt.new.freeze
1020
+ }
1021
+ # Returns the current default format.
1022
+ def self.default
1023
+ d = self[:def]
1024
+ if block_given?
1025
+ d = d.dup
1026
+ yield d
1027
+ end
1028
+ d
1029
+ end
1030
+ # Defines the current default format.
1031
+ def self.default=(fmt)
1032
+ self[:def] = fmt
1033
+ end
1034
+ # Assigns a format to a name in the formats repository.
1035
+ def self.[]=(tag,fmt_def)
1036
+ @@fmts[tag.to_sym]=fmt_def.freeze
1037
+ end
1038
+ # Retrieves a named format from the repository.
1039
+ def self.[](tag)
1040
+ @@fmts[tag.to_sym]
1041
+ end
1042
+
1043
+ protected
1044
+
1045
+ @@valid_properties = nil
1046
+ ALIAS_PROPERTIES = {
1047
+ :show_all_digits=>:all_digits,
1048
+ :rounding_mode=>:round,
1049
+ :approx_mode=>:approx,
1050
+ :sci_digits=>:sci_format,
1051
+ :non_signitificative_digits=>:non_sig,
1052
+ :begin=>:rep_begin,
1053
+ :end=>:rep_end,
1054
+ :suffix=>:rep_auto,
1055
+ :nreps=>:rep_n,
1056
+ :read=>:rep_in
1057
+ }
1058
+ def set!(properties={}) # :nodoc:
1059
+
1060
+
1061
+ @@valid_properties ||= instance_variables.collect{|v| v[1..-1].to_sym}
1062
+
1063
+
1064
+ properties.each do |k,v|
1065
+ al = ALIAS_PROPERTIES[k]
1066
+ if al
1067
+ properties[al] = v
1068
+ properties.delete k
1069
+ elsif !@@valid_properties.include?(k)
1070
+ raise InvalidOption, "Invalid option: #{k}"
1071
+ end
1072
+ end
1073
+
1074
+
1075
+ if properties[:grp_sep].nil? && !properties[:dec_sep].nil? && properties[:dec_sep]!=@dec_sep && properties[:dec_sep]==@grp_sep
1076
+ properties[:grp_sep] = properties[:dec_sep]=='.' ? ',' : '.'
1077
+ end
1078
+
1079
+ if properties[:all_digits].nil? && (properties[:ndig] || properties[:mode])
1080
+ ndig = properties[:ndig] || @ndig
1081
+ mode = properties[:mode] || @mode
1082
+ properties[:all_digits] = ndig!=:exact && mode!=:gen
1083
+ end
1084
+
1085
+ if !properties[:all_digits].nil? && properties[:non_sig].nil?
1086
+ properties[:non_sig] = '' unless properties[:all_digits]
1087
+ elsif !properties[:non_sig].nil? && properties[:all_digits].nil?
1088
+ properties[:all_digits] = true if properties[:non_sig]!=''
1089
+ end
1090
+
1091
+ if !properties[:base_radix].nil? || !properties[:base_uppercase].nil?
1092
+ base = properties[:base_radix] || @base_radix
1093
+ uppercase = properties[:base_uppercase] || @base_uppercase
1094
+ properties[:base_digits] = DigitsDef.base(base, !uppercase)
1095
+ end
1096
+
1097
+
1098
+ properties.each do |k,v|
1099
+ instance_variable_set "@#{k}", v unless v.nil?
1100
+ end
1101
+
1102
+ self
1103
+ end
1104
+
1105
+ def set(properties={}) # :nodoc:
1106
+ self.dup.set!(properties)
1107
+ end
1108
+
1109
+ def use_scientific?(neutral,exp) # :nodoc:
1110
+ nd = @ndig.kind_of?(Numeric) ? @ndig : [neutral.digits.length,10].max
1111
+ if @@sci_fmt==:hp
1112
+ puts " #{nd} ndpos=#{neutral.dec_pos} ndlen=#{neutral.digits.length}"
1113
+ neutral.dec_pos>nd || ([neutral.digits.length,nd].min-neutral.dec_pos)>nd
1114
+ else
1115
+ exp<-4 || exp>=nd
1116
+ end
1117
+ end
1118
+
1119
+ def getRepDecOpt(base=nil) # :nodoc:
1120
+ rd_opt = RepDec::Opt.new
1121
+ rd_opt.begin_rep = @rep_begin
1122
+ rd_opt.end_rep = @rep_end
1123
+ rd_opt.auto_rep = @rep_auto
1124
+ rd_opt.dec_sep = @dec_sep
1125
+ rd_opt.grp_sep = @grp_sep
1126
+ rd_opt.grp = @grp
1127
+ rd_opt.inf_txt = @inf_txt
1128
+ rd_opt.nan_txt = @nan_txt
1129
+ rd_opt.set_digits(get_base_digits(base))
1130
+ # if base && (base != get_base_digits.radix)
1131
+ # rd_opt.set_digits(get_base_digits(base))
1132
+ # else
1133
+ # rd_opt.set_digits get_base_digits
1134
+ # end
1135
+ return rd_opt
1136
+ end
1137
+
1138
+ def group(digits) # :nodoc:
1139
+ RepDec.group_digits(digits, getRepDecOpt)
1140
+ end
1141
+
1142
+ end
1143
+
1144
+ # This is a mix-in module to add formatting capabilities no numerical classes.
1145
+ # A class that includes this module should provide the methods
1146
+ # nio_write_neutral(fmt):: an instance method to write the value to
1147
+ # a neutral numeral. The format is passed so that
1148
+ # the base, for example, is available.
1149
+ # nio_read_neutral(neutral):: a class method to create a value from a neutral
1150
+ # numeral.
1151
+ module Formattable
1152
+
1153
+ # This is the method available in all formattable objects
1154
+ # to format the value into a text string according
1155
+ # to the optional format passed.
1156
+ def nio_write(fmt=Fmt.default)
1157
+ neutral = nio_write_neutral(fmt)
1158
+ fmt.nio_write_formatted(neutral)
1159
+ end
1160
+
1161
+ module ClassMethods
1162
+ # This is the method available in all formattable clases
1163
+ # to read a formatted value from a text string into
1164
+ # a value the class, according to the optional format passed.
1165
+ def nio_read(txt,fmt=Fmt.default)
1166
+ neutral = fmt.nio_read_formatted(txt)
1167
+ nio_read_neutral neutral
1168
+ end
1169
+ end
1170
+
1171
+ # Round a formattable object according to the rounding mode and
1172
+ # precision of a format.
1173
+ def nio_round(fmt=Fmt.default)
1174
+ neutral = nio_write_neutral(fmt)
1175
+ fmt.round! neutral
1176
+ self.class.nio_read_neutral neutral
1177
+ end
1178
+
1179
+ def self.append_features(mod) # :nodoc:
1180
+ super
1181
+ mod.extend ClassMethods
1182
+ end
1183
+
1184
+ end
1185
+
1186
+ Fmt[:comma] = Fmt.sep(',','.')
1187
+ Fmt[:comma_th] = Fmt.sep(',','.',[3])
1188
+ Fmt[:dot] = Fmt.sep('.',',')
1189
+ Fmt[:dot_th] = Fmt.sep('.',',',[3])
1190
+ Fmt[:code] = Fmt.new.prec(20) # don't use :exact to avoid repeating numerals
1191
+
1192
+ class Fmt
1193
+ # Intermediate conversion format for simplified conversion
1194
+ CONV_FMT = Fmt.prec(:exact).rep('<','>','...',0).approx_mode(:simplify)
1195
+ # Intermediate conversion format for exact conversion
1196
+ CONV_FMT_STRICT = Fmt.prec(:exact).rep('<','>','...',0).approx_mode(:exact)
1197
+ # Numerical conversion: converts the quantity +x+ to an object
1198
+ # of class +type+.
1199
+ #
1200
+ # The third parameter is the kind of conversion:
1201
+ # [<tt>:approx</tt>]
1202
+ # Tries to find an approximate simpler value if possible for inexact
1203
+ # numeric types. This is the default. This is slower in general and
1204
+ # may take some seconds in some cases.
1205
+ # [<tt>:exact</tt>]
1206
+ # Performs a conversion as exact as possible.
1207
+ # The third parameter is true for approximate
1208
+ # conversion (inexact values are simplified if possible) and false
1209
+ # for conversions as exact as possible.
1210
+ def Fmt.convert(x, type, mode=:approx)
1211
+ fmt = mode==:approx ? CONV_FMT : CONV_FMT_STRICT
1212
+ # return x.prec(type)
1213
+ if !(x.is_a?(type))
1214
+ # return type.nio_read(x.nio_write(fmt),fmt)
1215
+
1216
+ x = x.nio_write_neutral(fmt)
1217
+ x = type.nio_read_neutral(x)
1218
+
1219
+ end
1220
+ x
1221
+ end
1222
+ end
1223
+
1224
+ module_function
1225
+
1226
+ def nio_float_to_bigdecimal(x,prec) # :nodoc:
1227
+ if prec.nil?
1228
+ x = Nio.convert(x,BigDecimal,:approx)
1229
+ elsif prec==:exact
1230
+ x = Nio.convert(x,BigDecimal,:exact)
1231
+ else
1232
+ x = BigDecimal(x.nio_write(Nio::Fmt.new.prec(prec,:sig)))
1233
+ end
1234
+ x
1235
+ end
1236
+
1237
+
1238
+ module Clinger # :nodoc: all
1239
+ module_function
1240
+
1241
+ def algM(f,e,round_mode,eb=10,beta=Float::RADIX,n=Float::MANT_DIG,min_e=Float::MIN_EXP-Float::MANT_DIG,max_e=Float::MAX_EXP-Float::MANT_DIG)
1242
+
1243
+ if e<0
1244
+ u,v,k = f,eb**(-e),0
1245
+ else
1246
+ u,v,k = f*(eb**e),1,0
1247
+ end
1248
+
1249
+ loop do
1250
+ x = u.div(v)
1251
+ # overflow if k>=max_e
1252
+ if (x>=beta**(n-1) && x<beta**n) || k==min_e || k==max_e
1253
+ return ratio_float(u,v,k,round_mode,beta,n)
1254
+ elsif x<beta**(n-1)
1255
+ u *= beta
1256
+ k -= 1
1257
+ elsif x>=beta**n
1258
+ v *= beta
1259
+ k += 1
1260
+ end
1261
+ end
1262
+
1263
+ end
1264
+
1265
+ def ratio_float(u,v,k,round_mode,beta=Float::RADIX,n=Float::MANT_DIG)
1266
+ q = u.div v
1267
+ r = u-q*v
1268
+ v_r = v-r
1269
+ z = Math.ldexp(q,k)
1270
+ if r<v_r
1271
+ z
1272
+ elsif r>v_r
1273
+ nextfloat z
1274
+ elsif (round_mode==:even && q.even?) || (round_mode==:zero)
1275
+ z
1276
+ else
1277
+ nextfloat z
1278
+ end
1279
+ end
1280
+
1281
+ def nextfloat(x)
1282
+ f,e = Math.frexp(x)
1283
+ e = Float::MIN_EXP if f==0
1284
+ e = [Float::MIN_EXP,e].max
1285
+ dx = Math.ldexp(1,e-Float::MANT_DIG) #Math.ldexp(Math.ldexp(1.0,-Float::MANT_DIG),e)
1286
+ x + dx
1287
+ end
1288
+
1289
+ def prevfloat(x)
1290
+ f,e = Math.frexp(x)
1291
+ e = Float::MIN_EXP if f==0
1292
+ e = [Float::MIN_EXP,e].max
1293
+ dx = Math.ldexp(1,e-Float::MANT_DIG) #Math.ldexp(Math.ldexp(1.0,-Float::MANT_DIG),e)
1294
+ if e==Float::MIN_EXP || f!=0.5 #0.5==Math.ldexp(2**(bits-1),-Float::MANT_DIG)
1295
+ x - dx
1296
+ else
1297
+ x - dx/2 # x - Math.ldexp(Math.ldexp(1.0,-Float::MANT_DIG),e-1)
1298
+ end
1299
+ end
1300
+
1301
+ end
1302
+
1303
+ module BurgerDybvig # :nodoc: all
1304
+ module_function
1305
+
1306
+ def float_to_digits(v,f,e,round_mode,min_e,p,b,_B)
1307
+
1308
+ case round_mode
1309
+ when :even
1310
+ roundl = roundh = f.even?
1311
+ when :inf
1312
+ roundl = true
1313
+ roundh = false
1314
+ when :zero
1315
+ roundl = false
1316
+ roundh = true
1317
+ else
1318
+ # here we don't assume any rounding in the floating point numbers
1319
+ # the result is valid for any rounding
1320
+ roundl = false
1321
+ roundh = false
1322
+ end
1323
+
1324
+ if e >= 0
1325
+ if f != exptt(b,p-1)
1326
+ be = exptt(b,e)
1327
+ r,s,m_p,m_m,k = scale(f*be*2,2,be,be,0,_B,roundl ,roundh,v)
1328
+ else
1329
+ be = exptt(b,e)
1330
+ be1 = be*b
1331
+ r,s,m_p,m_m,k = scale(f*be1*2,b*2,be1,be,0,_B,roundl ,roundh,v)
1332
+ end
1333
+ else
1334
+ if e==min_e or f != exptt(b,p-1)
1335
+ r,s,m_p,m_m,k = scale(f*2,exptt(b,-e)*2,1,1,0,_B,roundl ,roundh,v)
1336
+ else
1337
+ r,s,m_p,m_m,k = scale(f*b*2,exptt(b,1-e)*2,b,1,0,_B,roundl ,roundh,v)
1338
+ end
1339
+ end
1340
+ [k]+generate(r,s,m_p,m_m,_B,roundl ,roundh)
1341
+ end
1342
+
1343
+ def scale(r,s,m_p,m_m,k,_B,low_ok ,high_ok,v)
1344
+ return scale2(r,s,m_p,m_m,k,_B,low_ok ,high_ok) if v==0
1345
+ est = (logB(_B,v)-1E-10).ceil.to_i
1346
+ if est>=0
1347
+ fixup(r,s*exptt(_B,est),m_p,m_m,est,_B,low_ok,high_ok)
1348
+ else
1349
+ sc = exptt(_B,-est)
1350
+ fixup(r*sc,s,m_p*sc,m_m*sc,est,_B,low_ok,high_ok)
1351
+ end
1352
+ end
1353
+
1354
+ def fixup(r,s,m_p,m_m,k,_B,low_ok,high_ok)
1355
+ if (high_ok ? (r+m_p >= s) : (r+m_p > s)) # too low?
1356
+ [r,s*_B,m_p,m_m,k+1]
1357
+ else
1358
+ [r,s,m_p,m_m,k]
1359
+ end
1360
+ end
1361
+
1362
+ def scale2(r,s,m_p,m_m,k,_B,low_ok ,high_ok)
1363
+ loop do
1364
+ if (high_ok ? (r+m_p >= s) : (r+m_p > s)) # k is too low
1365
+ s *= _B
1366
+ k += 1
1367
+ elsif (high_ok ? ((r+m_p)*_B<s) : ((r+m_p)*_B<=s)) # k is too high
1368
+ r *= _B
1369
+ m_p *= _B
1370
+ m_m *= _B
1371
+ k -= 1
1372
+ else
1373
+ break
1374
+ end
1375
+ end
1376
+ [r,s,m_p,m_m,k]
1377
+ end
1378
+
1379
+ def generate(r,s,m_p,m_m,_B,low_ok ,high_ok)
1380
+ list = []
1381
+ loop do
1382
+ d,r = (r*_B).divmod(s)
1383
+ m_p *= _B
1384
+ m_m *= _B
1385
+ tc1 = low_ok ? (r<=m_m) : (r<m_m)
1386
+ tc2 = high_ok ? (r+m_p >= s) : (r+m_p > s)
1387
+
1388
+ if not tc1
1389
+ if not tc2
1390
+ list << d
1391
+ else
1392
+ list << d+1
1393
+ break
1394
+ end
1395
+ else
1396
+ if not tc2
1397
+ list << d
1398
+ break
1399
+ else
1400
+ if r*2 < s
1401
+ list << d
1402
+ break
1403
+ else
1404
+ list << d+1
1405
+ break
1406
+ end
1407
+ end
1408
+ end
1409
+
1410
+ end
1411
+ list
1412
+ end
1413
+
1414
+ $exptt_table = Array.new(326)
1415
+ (0...326).each{|i| $exptt_table[i]=10**i}
1416
+ def exptt(_B, k)
1417
+ if _B==10 && k>=0 && k<326
1418
+ $exptt_table[k]
1419
+ else
1420
+ _B**k
1421
+ end
1422
+ end
1423
+
1424
+ $logB_table = Array.new(37)
1425
+ (2...37).each{|b| $logB_table[b]=1.0/Math.log(b)}
1426
+ def logB(_B, x)
1427
+ if _B>=2 && _B<37
1428
+ Math.log(x)*$logB_table[_B]
1429
+ else
1430
+ Math.log(x)/Math.log(_B)
1431
+ end
1432
+ end
1433
+
1434
+ def float_to_digits_max(v,f,e,round_mode,min_e,p,b,_B)
1435
+
1436
+ case round_mode
1437
+ when :even
1438
+ roundl = roundh = f.even?
1439
+ when :inf
1440
+ roundl = true
1441
+ roundh = false
1442
+ when :zero
1443
+ roundl = false
1444
+ roundh = true
1445
+ else
1446
+ # here we don't assume any rounding in the floating point numbers
1447
+ # the result is valid for any rounding
1448
+ roundl = false
1449
+ roundh = false
1450
+ end
1451
+
1452
+ if e >= 0
1453
+ if f != exptt(b,p-1)
1454
+ be = exptt(b,e)
1455
+ r,s,m_p,m_m,k = scale(f*be*2,2,be,be,0,_B,roundl ,roundh,v)
1456
+ else
1457
+ be = exptt(b,e)
1458
+ be1 = be*b
1459
+ r,s,m_p,m_m,k = scale(f*be1*2,b*2,be1,be,0,_B,roundl ,roundh,v)
1460
+ end
1461
+ else
1462
+ if e==min_e or f != exptt(b,p-1)
1463
+ r,s,m_p,m_m,k = scale(f*2,exptt(b,-e)*2,1,1,0,_B,roundl ,roundh,v)
1464
+ else
1465
+ r,s,m_p,m_m,k = scale(f*b*2,exptt(b,1-e)*2,b,1,0,_B,roundl ,roundh,v)
1466
+ end
1467
+ end
1468
+ [k]+generate_max(r,s,m_p,m_m,_B,roundl ,roundh)
1469
+ end
1470
+
1471
+ def generate_max(r,s,m_p,m_m,_B,low_ok ,high_ok)
1472
+ list = [false]
1473
+ loop do
1474
+ d,r = (r*_B).divmod(s)
1475
+ m_p *= _B
1476
+ m_m *= _B
1477
+
1478
+ list << d
1479
+
1480
+ tc1 = low_ok ? (r<=m_m) : (r<m_m)
1481
+ tc2 = high_ok ? (r+m_p >= s) : (r+m_p > s)
1482
+
1483
+ if tc1 && tc2
1484
+ list[0] = true if r*2 >= s
1485
+ break
1486
+ end
1487
+ end
1488
+ list
1489
+ end
1490
+
1491
+ end
1492
+
1493
+ end
1494
+
1495
+ class Float
1496
+ include Nio::Formattable
1497
+ def self.nio_read_neutral(neutral)
1498
+ x = nil
1499
+
1500
+ honor_rounding = true
1501
+
1502
+ if neutral.special?
1503
+ case neutral.special
1504
+ when :nan
1505
+ x = 0.0/0.0
1506
+ when :inf
1507
+ x = (neutral.sign=='-' ? -1.0 : +1.0)/0.0
1508
+ end
1509
+ elsif neutral.rep_pos<neutral.digits.length
1510
+
1511
+ x,y = neutral.to_RepDec.getQ
1512
+ x = Float(x)/y
1513
+
1514
+ else
1515
+ nd = neutral.base==10 ? Float::DIG : ((Float::MANT_DIG-1)*Math.log(2)/Math.log(neutral.base)).floor
1516
+ k = neutral.dec_pos-neutral.digits.length
1517
+ if !honor_rounding && (neutral.digits.length<=nd && k.abs<=15)
1518
+ x = neutral.digits.to_i(neutral.base).to_f
1519
+ if k<0
1520
+ x /= Float(neutral.base**-k)
1521
+ else
1522
+ x *= Float(neutral.base**k)
1523
+ end
1524
+ x = -x if neutral.sign=='-'
1525
+ elsif !honor_rounding && (k>0 && (k+neutral.digits.length < 2*nd))
1526
+ j = k-neutral.digits.length
1527
+ x = neutral.digits.to_i(neutral.base).to_f * Float(neutral.base**(j))
1528
+ x *= Float(neutral.base**(k-j))
1529
+ x = -x if neutral.sign=='-'
1530
+ elsif neutral.base.modulo(Float::RADIX)==0
1531
+
1532
+ f = neutral.digits.to_i(neutral.base)
1533
+ e = neutral.dec_pos-neutral.digits.length
1534
+
1535
+ rounding = neutral.rounding
1536
+
1537
+ x = Nio::Clinger::algM(f,e,rounding,neutral.base,Float::RADIX,Float::MANT_DIG,Float::MIN_EXP-Float::MANT_DIG,Float::MAX_EXP-Float::MANT_DIG)
1538
+ x = -x if neutral.sign=='-'
1539
+
1540
+ else
1541
+
1542
+ f = neutral.digits.to_i(neutral.base)
1543
+ e = neutral.dec_pos-neutral.digits.length
1544
+
1545
+ rounding = neutral.rounding
1546
+
1547
+ x = Nio::Clinger::algM(f,e,rounding,neutral.base,Float::RADIX,Float::MANT_DIG,Float::MIN_EXP-Float::MANT_DIG,Float::MAX_EXP-Float::MANT_DIG)
1548
+ x = -x if neutral.sign=='-'
1549
+
1550
+ end
1551
+ end
1552
+
1553
+ return x
1554
+ end
1555
+ def nio_write_neutral(fmt)
1556
+ neutral = Nio::NeutralNum.new
1557
+ x = self
1558
+
1559
+ if x.nan?
1560
+ neutral.set_special(:nan)
1561
+ elsif x.infinite?
1562
+ neutral.set_special(:inf, x<0 ? '-' : '+')
1563
+ else
1564
+ converted = false
1565
+ if fmt.get_ndig==:exact && fmt.get_approx==:simplify
1566
+
1567
+ if x!=0
1568
+ q = x.nio_r(Nio::Tolerance.decimals(Float::DIG,:sig))
1569
+ if q!=0
1570
+ neutral = q.nio_write_neutral(fmt)
1571
+ converted = true if neutral.digits.length<=Float::DIG
1572
+ end
1573
+ end
1574
+
1575
+ elsif fmt.get_approx==:exact
1576
+ neutral = x.nio_xr.nio_write_neutral(fmt)
1577
+ converted = true
1578
+ end
1579
+ if !converted
1580
+ if fmt.get_base==10 && false
1581
+ txt = format "%.*e",Float::DECIMAL_DIG-1,x # note that spec. e output precision+1 significant digits
1582
+
1583
+ sign = '+'
1584
+ if txt[0,1]=='-'
1585
+ sign = '-'
1586
+ txt = txt[1...txt.length]
1587
+ end
1588
+ exp = 0
1589
+ x_char = fmt.get_exp_char(fmt.get_base)
1590
+
1591
+ exp_i = txt.index(x_char)
1592
+ exp_i = txt.index(x_char.downcase) if exp_i===nil
1593
+ if exp_i!=nil
1594
+ exp = txt[exp_i+1...txt.length].to_i
1595
+ txt = txt[0...exp_i]
1596
+ end
1597
+
1598
+ dec_pos = txt.index '.'
1599
+ if dec_pos==nil
1600
+ dec_pos = txt.length
1601
+ else
1602
+ txt[dec_pos]=''
1603
+ end
1604
+ dec_pos += exp
1605
+ neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits(10), true, fmt.get_round
1606
+
1607
+ converted = true
1608
+ end
1609
+ end
1610
+ if !converted
1611
+
1612
+ sign = x<0 ? '-' : '+'
1613
+ x = -x if sign=='-'
1614
+ f,e = Math.frexp(x)
1615
+ if e < Float::MIN_EXP
1616
+ # denormalized number
1617
+ f = Math.ldexp(f,e-Float::MIN_EXP+Float::MANT_DIG)
1618
+ e = Float::MIN_EXP-Float::MANT_DIG
1619
+ else
1620
+ # normalized number
1621
+ f = Math.ldexp(f,Float::MANT_DIG)
1622
+ e -= Float::MANT_DIG
1623
+ end
1624
+ f = f.to_i
1625
+ inexact = true
1626
+
1627
+ rounding = fmt.get_round
1628
+
1629
+ if fmt.get_all_digits?
1630
+ # use as many digits as possible
1631
+ dec_pos,r,*digits = Nio::BurgerDybvig::float_to_digits_max(x,f,e,rounding,Float::MIN_EXP-Float::MANT_DIG,Float::MANT_DIG,Float::RADIX,fmt.get_base)
1632
+ inexact = :roundup if r
1633
+ else
1634
+ # use as few digits as possible
1635
+ dec_pos,*digits = Nio::BurgerDybvig::float_to_digits(x,f,e,rounding,Float::MIN_EXP-Float::MANT_DIG,Float::MANT_DIG,Float::RADIX,fmt.get_base)
1636
+ end
1637
+ txt = ''
1638
+ digits.each{|d| txt << fmt.get_base_digits.digit_char(d)}
1639
+ neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, inexact, fmt.get_round
1640
+
1641
+ end
1642
+ end
1643
+
1644
+ return neutral
1645
+ end
1646
+ end
1647
+
1648
+ class Numeric
1649
+ unless method_defined?(:even?)
1650
+ def even?
1651
+ self.modulo(2)==0
1652
+ end
1653
+ end
1654
+ unless method_defined?(:odd?)
1655
+ def odd?
1656
+ self.modulo(2)!=0
1657
+ end
1658
+ end
1659
+ end
1660
+
1661
+ class Integer
1662
+ include Nio::Formattable
1663
+ def self.nio_read_neutral(neutral)
1664
+ x = nil
1665
+
1666
+ if neutral.special?
1667
+ raise Nio::InvalidFormat,"Invalid integer numeral"
1668
+ elsif neutral.rep_pos<neutral.digits.length
1669
+ return Rational.nio_read_neutral(neutral).to_i
1670
+ else
1671
+ digits = neutral.digits
1672
+
1673
+ if neutral.dec_pos <= 0
1674
+ digits = '0'
1675
+ elsif neutral.dec_pos <= digits.length
1676
+ digits = digits[0...neutral.dec_pos]
1677
+ else
1678
+ digits = digits + '0'*(neutral.dec_pos-digits.length)
1679
+ end
1680
+
1681
+ x = digits.to_i(neutral.base)
1682
+ # this was formely needed because we didn't adust the digits
1683
+ # if neutral.dec_pos != neutral.digits.length
1684
+ # # with rational included, negative powers of ten are rational numbers
1685
+ # x = (x*((neutral.base)**(neutral.dec_pos-neutral.digits.length))).to_i
1686
+ # end
1687
+ x = -x if neutral.sign=='-'
1688
+ end
1689
+
1690
+ return x
1691
+ end
1692
+ def nio_write_neutral(fmt)
1693
+ neutral = Nio::NeutralNum.new
1694
+ x = self
1695
+
1696
+ sign = x<0 ? '-' : '+'
1697
+ txt = x.abs.to_s(fmt.get_base)
1698
+ dec_pos = rep_pos = txt.length
1699
+ neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, false ,fmt.get_round
1700
+
1701
+ return neutral
1702
+ end
1703
+ end
1704
+
1705
+ class Rational
1706
+ include Nio::Formattable
1707
+ def self.nio_read_neutral(neutral)
1708
+ x = nil
1709
+
1710
+ if neutral.special?
1711
+ case neutral.special
1712
+ when :nan
1713
+ x = Rational(0,0)
1714
+ when :inf
1715
+ x = Rational((neutral.sign=='-' ? -1 : +1),0)
1716
+ end
1717
+ else
1718
+ x = Rational(*neutral.to_RepDec.getQ)
1719
+ end
1720
+
1721
+ return x
1722
+ end
1723
+ def nio_write_neutral(fmt)
1724
+ neutral = Nio::NeutralNum.new
1725
+ x = self
1726
+
1727
+ if x.denominator==0
1728
+ if x.numerator>0
1729
+ neutral.set_special(:inf)
1730
+ elsif x.numerator<0
1731
+ neutral.set_special(:inf,'-')
1732
+ else
1733
+ neutral.set_special(:nan)
1734
+ end
1735
+ else
1736
+ if fmt.get_base==10
1737
+ rd = Nio::RepDec.new.setQ(x.numerator,x.denominator)
1738
+ else
1739
+ opt = Nio::RepDec::DEF_OPT.dup.set_digits(fmt.get_base_digits)
1740
+ rd = Nio::RepDec.new.setQ(x.numerator,x.denominator, opt)
1741
+ end
1742
+ neutral = rd.to_NeutralNum(fmt.get_base_digits)
1743
+ neutral.rounding = fmt.get_round
1744
+ end
1745
+
1746
+ return neutral
1747
+ end
1748
+ end
1749
+
1750
+ if defined? BigDecimal
1751
+ class BigDecimal
1752
+ include Nio::Formattable
1753
+ def self.nio_read_neutral(neutral)
1754
+ x = nil
1755
+
1756
+ if neutral.special?
1757
+ case neutral.special
1758
+ when :nan
1759
+ x = BigDecimal('NaN') # BigDecimal("0")/0
1760
+ when :inf
1761
+ x = BigDecimal(neutral.sign=='-' ? '-1.0' : '+1.0')/0
1762
+ end
1763
+ elsif neutral.rep_pos<neutral.digits.length
1764
+
1765
+ x,y = neutral.to_RepDec.getQ
1766
+ x = BigDecimal(x.to_s)/y
1767
+
1768
+ else
1769
+ if neutral.base==10
1770
+ #x = BigDecimal(neutral.digits)
1771
+ #x *= BigDecimal("1E#{(neutral.dec_pos-neutral.digits.length)}")
1772
+ #x = -x if neutral.sign=='-'
1773
+ str = neutral.sign
1774
+ str += neutral.digits
1775
+ str += "E#{(neutral.dec_pos-neutral.digits.length)}"
1776
+ x = BigDecimal(str)
1777
+ else
1778
+ x = BigDecimal(neutral.digits.to_i(neutral.base).to_s)
1779
+ x *= BigDecimal(neutral.base.to_s)**(neutral.dec_pos-neutral.digits.length)
1780
+ x = -x if neutral.sign=='-'
1781
+ end
1782
+ end
1783
+
1784
+ return x
1785
+ end
1786
+ def nio_write_neutral(fmt)
1787
+ neutral = Nio::NeutralNum.new
1788
+ x = self
1789
+
1790
+ if x.nan?
1791
+ neutral.set_special(:nan)
1792
+ elsif x.infinite?
1793
+ neutral.set_special(:inf, x<0 ? '-' : '+')
1794
+ else
1795
+ converted = false
1796
+ if fmt.get_ndig==:exact && fmt.get_approx==:simplify
1797
+
1798
+ prc = [x.precs[0],20].max
1799
+ neutral = x.nio_r(Nio::BigTolerance.decimals(prc,:sig)).nio_write_neutral(fmt)
1800
+ converted = true if neutral.digits.length<prc
1801
+
1802
+ elsif fmt.get_approx==:exact && fmt.get_base!=10
1803
+ neutral = x.nio_xr.nio_write_neutral(fmt)
1804
+ converted = true
1805
+ end
1806
+ if !converted
1807
+ if fmt.get_base==10
1808
+ txt = x.to_s
1809
+
1810
+ sign = '+'
1811
+ if txt[0,1]=='-'
1812
+ sign = '-'
1813
+ txt = txt[1...txt.length]
1814
+ end
1815
+ exp = 0
1816
+ x_char = fmt.get_exp_char(fmt.get_base)
1817
+
1818
+ exp_i = txt.index(x_char)
1819
+ exp_i = txt.index(x_char.downcase) if exp_i===nil
1820
+ if exp_i!=nil
1821
+ exp = txt[exp_i+1...txt.length].to_i
1822
+ txt = txt[0...exp_i]
1823
+ end
1824
+
1825
+ dec_pos = txt.index '.'
1826
+ if dec_pos==nil
1827
+ dec_pos = txt.length
1828
+ else
1829
+ txt[dec_pos]=''
1830
+ end
1831
+ dec_pos += exp
1832
+ neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits(10), true, fmt.get_round
1833
+
1834
+ end
1835
+ end
1836
+ if !converted
1837
+
1838
+ min_prec = 24
1839
+ min_exp = -1000
1840
+ s,f,b,e = x.split
1841
+ e -= f.size
1842
+ sign = s<0 ? '-' : '+'
1843
+ x = -x if sign=='-'
1844
+ f_i = f.to_i
1845
+ prc = [x.precs[0],min_prec].max
1846
+ f_i *= 10**(prc-f.size)
1847
+ e -= (prc-f.size)
1848
+
1849
+ inexact = true
1850
+
1851
+ rounding = fmt.get_round
1852
+
1853
+ if fmt.get_all_digits?
1854
+ # use as many digits as possible
1855
+ dec_pos,r,*digits = Nio::BurgerDybvig::float_to_digits_max(x,f_i,e,rounding,[e,min_exp].min,prc,b,fmt.get_base)
1856
+ inexact = :roundup if r
1857
+ else
1858
+ # use as few digits as possible
1859
+ dec_pos,*digits = Nio::BurgerDybvig::float_to_digits(x,f_i,e,rounding,[e,min_exp].min,prc,b,fmt.get_base)
1860
+ end
1861
+ txt = ''
1862
+ digits.each{|d| txt << fmt.get_base_digits.digit_char(d)}
1863
+ neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, inexact, fmt.get_round
1864
+
1865
+ end
1866
+ end
1867
+
1868
+ return neutral
1869
+ end
1870
+ end
1871
+ end
1872
+