nio 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+