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/flttol.rb ADDED
@@ -0,0 +1,654 @@
1
+ # Floating point tolerance
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
+ # Author:: Javier Goizueta (mailto:javier@goizueta.info)
12
+ # Copyright:: Copyright (c) 2002-2004 Javier Goizueta
13
+ # License:: Distributes under the GPL license
14
+ #
15
+ # This module provides a numeric tolerance class for Float and BigDecimal.
16
+
17
+ require 'bigdecimal'
18
+ require 'bigdecimal/math' if ::VERSION>='1.8.1'
19
+ require 'nio/tools'
20
+
21
+
22
+ class Float
23
+ unless const_defined?(:RADIX) # old Ruby versions didn't have this
24
+ # Base of the Float representation
25
+ RADIX = 2
26
+
27
+ x = 1.0
28
+ _bits_ = 0
29
+ begin
30
+ _bits_ += 1
31
+ x /= 2
32
+ end while 1!=x+1
33
+
34
+ # Number of RADIX-base digits of precision in a Float
35
+ MANT_DIG = _bits_
36
+ # Number of decimal digits that can be stored in a Float and recovered
37
+ DIG = ((MANT_DIG-1)*Math.log(RADIX)/Math.log(10)).floor
38
+ # Smallest value that added to 1.0 produces something different from 1.0
39
+ EPSILON = Math.ldexp(*Math.frexp(1).collect{|e| e.kind_of?(Integer) ? e-(MANT_DIG-1) : e})
40
+ end
41
+ # Decimal precision required to represent a Float and be able to recover its value
42
+ DECIMAL_DIG = (MANT_DIG*Math.log(RADIX)/Math.log(10)).ceil+1
43
+ end
44
+
45
+ # :stopdoc:
46
+ # A problem has been detected with Float#to_i() in some Ruby versiones
47
+ # (it has been found in Ruby 1.8.4 compiled for x86_64_linux|)
48
+ # This problem makes to_i produce an incorrect sign on some cases.
49
+ # Here we try to detect the problem and apply a quick patch,
50
+ # although this will slow down the method.
51
+ if 4.611686018427388e+018.to_i < 0
52
+ class Float
53
+ alias _to_i to_i
54
+ def to_i
55
+ neg = (self < 0)
56
+ i = _to_i
57
+ i_neg = (i < 0)
58
+ i = -i if neg != i_neg
59
+ i
60
+ end
61
+ end
62
+ end
63
+ # :startdoc:
64
+
65
+
66
+ # This module contains some constructor-like module functions
67
+ # to help with the creation of tolerances and big-decimals.
68
+ #
69
+ # =BigDec
70
+ # BigDec(x) -> a BigDecimal
71
+ # BigDec(x,precision) -> a BigDecimal
72
+ # BigDec(x,:exact) -> a BigDecimal
73
+ # This is a shortcut to define a BigDecimal without using quotes
74
+ # and a general conversion to BigDecimal method.
75
+ #
76
+ # The second parameter can be :exact to try for an exact conversion
77
+ #
78
+ # Conversions from Float have issues that should be understood; :exact
79
+ # conversion will use the exact internal value of the Float, and when
80
+ # no precision is specified, a value as simple as possible expressed as
81
+ # a fraction will be used.
82
+ #
83
+ # =Tol
84
+ # Tol(x) -> a Tolerance
85
+ # This module function will convert its argument to a Noi::Tolerance
86
+ # or a Noi::BigTolerance depending on its argument;
87
+ #
88
+ # Values of type Tolerance,Float,Integer (for Tolerance) or
89
+ # BigTolerance,BigDecimal (for BigTolerance) are accepted.
90
+ #
91
+ # =BigTol
92
+ # BigTol(x) -> a BigTolerance
93
+ # This module function will convert its argument to a Noi::BigTolerance
94
+ #
95
+ # Values of type BigTolerance or Numeric are accepted.
96
+ module Nio
97
+
98
+ # This class represents floating point tolerances for Float numbers
99
+ # and allows comparison within the specified tolerance.
100
+ class Tolerance
101
+ include StateEquivalent
102
+
103
+ # The numeric class this tolerance applies to.
104
+ def num_class
105
+ Float
106
+ end
107
+
108
+ # The tolerance mode is either :abs (absolute) :rel (relative) or :sig (significant).
109
+ # The last parameter is a flag to specify decimal mode for the :sig mode
110
+ def initialize(t=0.0, mode=:abs, decmode=false)
111
+ set t, mode, decmode
112
+ end
113
+
114
+
115
+ #This initializes a Tolerance with a given number of decimals
116
+ def decimals(d, mode=:abs, rounded=true)
117
+
118
+ @mode = mode
119
+ @decimal_mode = true
120
+ @d = (d<=0 || d>Float::DIG) ? Float::DIG : d
121
+ @t = 10**(-@d)
122
+ @t *= 0.5 if rounded
123
+
124
+ self
125
+ end
126
+
127
+ #This initializes a Tolerance with a number of significant decimal digits
128
+ def sig_decimals(d, rounded=true)
129
+ decimals d, :sig, rounded
130
+ end
131
+
132
+ #Initialize with a multiple of the internal floating-point precision.
133
+ def epsilon(times_epsilon=1, mode=:sig)
134
+ set Float::EPSILON*times_epsilon, mode
135
+ end
136
+
137
+ # As #epsilon but using a somewhat bigger (about twice) precision that
138
+ # assures associative multiplication.
139
+ def big_epsilon(n=1, mode=:sig)
140
+ t = Math.ldexp(0.5*n,3-Float::MANT_DIG) # n*(2*Float::EPSILON/(1-0.5*Float::EPSILON)**2)
141
+ set t, mode
142
+ end
143
+
144
+ # Initialize with a relative fraction
145
+ def fraction(f)
146
+ set f, :rel
147
+ end
148
+ # Initialize with a percentage
149
+ def percent(x)
150
+ fraction x/100.0
151
+ end
152
+ # Initialize with a per-mille value
153
+ def permille(x)
154
+ fraction x/1000.0
155
+ end
156
+
157
+
158
+ #Shortcut notation for get_value
159
+ def [](x)
160
+ return x.nil? ? @t : get_value(x)
161
+ end
162
+ #Return tolerance relative to a magnitude
163
+ def get_value(x)
164
+ rel(x)
165
+ end
166
+ #Essential equality within tolerance
167
+ def equals?(x,y)
168
+
169
+ case @mode
170
+ when :sig
171
+
172
+ if @decimal_mode
173
+ begin
174
+ x_exp = Math.log10(x.abs)
175
+ #x_exp = x_exp.finite? ? x_exp.ceil : 0
176
+ x_exp = x_exp.finite? ? x_exp.floor+1 : 0
177
+ rescue
178
+ x_exp = 0
179
+ end
180
+ begin
181
+ y_exp = Math.log10(y.abs)
182
+ #y_exp = y_exp.finite? ? y_exp.ceil : 0
183
+ y_exp = y_exp.finite? ? y_exp.floor+1 : 0
184
+ rescue
185
+ y_exp = 0
186
+ end
187
+ (y-x).abs <= @t*(10**([x_exp,y_exp].min-@@dec_ref_exp))
188
+ else
189
+ z,x_exp = Math.frexp(x)
190
+ z,y_exp = Math.frexp(y)
191
+ (y-x).abs <= Math.ldexp(@t,[x_exp,y_exp].min-@@ref_exp) # (y-x).abs <= @t*(2**([x_exp,y_exp].min-@@ref_exp))
192
+ end
193
+
194
+ when :rel
195
+
196
+ (y-x).abs <= @t*([x.abs,y.abs].min) #reference value is 1
197
+
198
+ when :abs
199
+ (x-y).abs<@t
200
+ end
201
+
202
+ end
203
+ #Approximate equality within tolerance
204
+ def aprx_equals?(x,y)
205
+
206
+ case @mode
207
+ when :sig
208
+
209
+ if @decimal_mode
210
+ begin
211
+ x_exp = Math.log10(x.abs)
212
+ #x_exp = x_exp.finite? ? x_exp.ceil : 0
213
+ x_exp = x_exp.finite? ? x_exp.floor+1 : 0
214
+ rescue
215
+ x_exp = 0
216
+ end
217
+ begin
218
+ y_exp = Math.log10(y.abs)
219
+ #y_exp = y_exp.finite? ? y_exp.ceil : 0
220
+ y_exp = y_exp.finite? ? y_exp.floor+1 : 0
221
+ rescue
222
+ y_exp = 0
223
+ end
224
+ (y-x).abs <= @t*(10**([x_exp,y_exp].max-@@dec_ref_exp))
225
+ else
226
+ z,x_exp = Math.frexp(x)
227
+ z,y_exp = Math.frexp(y)
228
+ (y-x).abs <= Math.ldexp(@t,[x_exp,y_exp].max-@@ref_exp) # (y-x).abs <= @t*(2**([x_exp,y_exp].max-@@ref_exp))
229
+ end
230
+
231
+ when :rel
232
+
233
+ (y-x).abs <= @t*([x.abs,y.abs].max) #reference value is 1
234
+
235
+ when :abs
236
+ (x-y).abs<=@t
237
+ end
238
+
239
+ end
240
+ #Comparison within tolerance
241
+ def greater_than?(x,y)
242
+ less_than?(y,x)
243
+ end
244
+ #Comparison within tolerance
245
+ def less_than?(x,y)
246
+
247
+ case @mode
248
+ when :sig
249
+
250
+ if @decimal_mode
251
+ begin
252
+ x_exp = Math.log10(x.abs)
253
+ #x_exp = x_exp.finite? ? x_exp.ceil : 0
254
+ x_exp = x_exp.finite? ? x_exp.floor+1 : 0
255
+ rescue
256
+ x_exp = 0
257
+ end
258
+ begin
259
+ y_exp = Math.log10(y.abs)
260
+ #y_exp = y_exp.finite? ? y_exp.ceil : 0
261
+ y_exp = y_exp.finite? ? y_exp.floor+1 : 0
262
+ rescue
263
+ y_exp = 0
264
+ end
265
+ y-x > @t*(10**([x_exp,y_exp].max-@@dec_ref_exp))
266
+ else
267
+ z,x_exp = Math.frexp(x)
268
+ z,y_exp = Math.frexp(y)
269
+ y-x > Math.ldexp(@t,[x_exp,y_exp].max-@@ref_exp) # y-x > @t*(2**([x_exp,y_exp].max-@@ref_exp))
270
+ end
271
+
272
+ when :rel
273
+
274
+ y-x > @t*([x.abs,y.abs].max) #reference value is 1
275
+
276
+ when :abs
277
+ x-y<@t
278
+ end
279
+
280
+ end
281
+ #Comparison within tolerance
282
+ def zero?(x,compared_with=nil)
283
+ compared_with.nil? ? x.abs<@t : x.abs<rel(compared_with)
284
+ end
285
+
286
+
287
+ # Returns true if the argument is approximately an integer
288
+ def apprx_i?(x)
289
+ equals?(x,x.round)
290
+ end
291
+ # If the argument is close to an integer it rounds it
292
+ # and returns it as an object of the specified class (by default, Integer)
293
+ def apprx_i(x,result=Integer)
294
+ r = x.round
295
+ return equals?(x,r) ? r.prec(result) : x
296
+ end
297
+
298
+
299
+ # Returns the magnitude of the tolerance
300
+ def magnitude
301
+ @t
302
+ end
303
+ # Returns the number of decimal digits of the tolerance
304
+ def num_decimals
305
+ @d
306
+ end
307
+ # Returns true for decimal-mode tolerance
308
+ def decimal?
309
+ @decimal_mode
310
+ end
311
+ # Returns the mode (:abs, :rel, :sig) of the tolerance
312
+ def mode
313
+ @mode
314
+ end
315
+
316
+
317
+ private
318
+
319
+ def set(t=0.0, mode=:abs, decmode=false)
320
+
321
+ @t = t==0 ? Float::EPSILON : t.abs
322
+ @t = 0.5 if @t > 0.5
323
+ @mode = mode
324
+ @t = Float::EPSILON if @mode!=:abs && @t<Float::EPSILON
325
+ @decimal_mode = decmode
326
+ @d = @t==0 ? 0 : (-Math.log10(2*@t).floor).to_i
327
+
328
+ self
329
+ end
330
+
331
+ @@ref_exp = 1 # Math.frexp(1)[1] => tol. relative to [1,2)
332
+
333
+ @@dec_ref_exp = 0 # tol. relative to [0.1,1)
334
+
335
+ def rel(x)
336
+ r = @t
337
+ case @mode
338
+ when :sig
339
+ if @decimal_mode
340
+ d = x==0 ? 0 : (Math.log10(x.abs).floor+1).to_i
341
+ r = @t*(10**(d-@@dec_ref_exp))
342
+ else
343
+ x,exp = Math.frexp(x)
344
+ r = Math.ldexp(@t,exp-@@ref_exp)
345
+ end
346
+ when :rel
347
+ r = @t*x.abs
348
+ end
349
+ r
350
+ end
351
+
352
+ end
353
+
354
+ def Tolerance.decimals(d=0, mode=:abs,rounded=true)
355
+ Tolerance.new.decimals(d,mode,rounded)
356
+ end
357
+ def Tolerance.sig_decimals(d=0, mode=:abs,rounded=true)
358
+ Tolerance.new.sig_decimals(d,rounded)
359
+ end
360
+ def Tolerance.epsilon(n=1, mode=:sig)
361
+ Tolerance.new.epsilon(n, mode)
362
+ end
363
+ def Tolerance.big_epsilon(n=1, mode=:sig)
364
+ Tolerance.new.big_epsilon(n, mode)
365
+ end
366
+ def Tolerance.fraction(f)
367
+ Tolerance.new.fraction(f)
368
+ end
369
+ def Tolerance.percent(p)
370
+ Tolerance.new.percent(p)
371
+ end
372
+ def Tolerance.permille(p)
373
+ Tolerance.new.permille(p)
374
+ end
375
+
376
+ # This class represents floating point tolerances for BigDecimal numbers
377
+ # and allows comparison within the specified tolerance.
378
+ class BigTolerance
379
+ include StateEquivalent
380
+ module BgMth # :nodoc:
381
+ extend BigMath if ::RUBY_VERSION>='1.8.1'
382
+ end
383
+
384
+ # The numeric class this tolerance applies to.
385
+ def num_class
386
+ BigDecimal
387
+ end
388
+
389
+ #The tolerance mode is either :abs (absolute) :rel (relative) or :sig
390
+ def initialize(t=BigDecimal('0'), mode=:abs, decmode=false)
391
+ set t, mode, decmode
392
+ end
393
+
394
+
395
+ #This initializes a BigTolerance with a given number of decimals
396
+ def decimals(d, mode=:abs, rounded=true)
397
+
398
+ @mode = mode
399
+ @decimal_mode = true
400
+ @d = d==0 ? 16 : d
401
+ if rounded
402
+ @t = BigDecimal("0.5E#{-d}") # HALF*(BigDecimal(10)**(-d))
403
+ else
404
+ @t = BigDecimal("1E#{-d}") # BigDecimal(10)**(-d)
405
+ end
406
+ @ref_exp = BigDecimal('0.1').exponent # reference for significative mode: [0.1,1)
407
+
408
+ self
409
+ end
410
+
411
+ #This initializes a BigTolerance with a number of significative decimal digits
412
+ def sig_decimals(d, rounded=true)
413
+ decimals d, :sig, rounded
414
+ end
415
+
416
+ def fraction(f)
417
+ set f, :rel
418
+ end
419
+ def percent(x)
420
+ fraction x*BigDecimal('0.01')
421
+ end
422
+ def permille(x)
423
+ fraction x*BigDecimal('0.001')
424
+ end
425
+
426
+
427
+ #Shortcut notation for get_value
428
+ def [](x)
429
+ return x.nil? ? @t : get_value(x)
430
+ end
431
+ #Return tolerance relative to a magnitude
432
+ def get_value(x)
433
+ rel(x)
434
+ end
435
+ #Essential equality within tolerance
436
+ def equals?(x,y)
437
+
438
+ case @mode
439
+ when :sig
440
+
441
+ x_exp = x.exponent
442
+ y_exp = y.exponent
443
+ (y-x).abs <= @t*BigDecimal("1E#{[x_exp,y_exp].min-@ref_exp}")
444
+
445
+ when :rel
446
+
447
+ (y-x).abs <= @t*([x.abs,y.abs].min) #reference value is 1
448
+
449
+ when :abs
450
+ (x-y).abs<@t
451
+ end
452
+
453
+ end
454
+ #Approximate equality within tolerance
455
+ def aprx_equals?(x,y)
456
+
457
+ case @mode
458
+ when :sig
459
+
460
+ x_exp = x.exponent
461
+ y_exp = y.exponent
462
+ (y-x).abs <= @t*BigDecimal("1E#{[x_exp,y_exp].max-@ref_exp}")
463
+
464
+ when :rel
465
+
466
+ (y-x).abs <= @t*([x.abs,y.abs].max) #reference value is 1
467
+
468
+ when :abs
469
+ (x-y).abs<=@t
470
+ end
471
+
472
+ end
473
+ #Comparison within tolerance
474
+ def greater_than?(x,y)
475
+ less_than?(y,x)
476
+ end
477
+ #Comparison within tolerance
478
+ def less_than?(x,y)
479
+
480
+ case @mode
481
+ when :sig
482
+
483
+ x_exp = x.exponent
484
+ y_exp = y.exponent
485
+ y-x > @t*BigDecimal("1E#{[x_exp,y_exp].max-@ref_exp}")
486
+
487
+ when :rel
488
+
489
+ y-x > @t*([x.abs,y.abs].max) #reference value is 1
490
+
491
+ when :abs
492
+ x-y<@t
493
+ end
494
+
495
+ end
496
+ #Comparison within tolerance
497
+ def zero?(x,compared_with=nil)
498
+ compared_with.nil? ? x.abs<@t : x.abs<rel(compared_with)
499
+ end
500
+
501
+
502
+ # Returns true if the argument is approximately an integer
503
+ def apprx_i?(x)
504
+ equals?(x,x.round)
505
+ end
506
+ # If the argument is close to an integer it rounds it
507
+ # and returns it as an object of the specified class (by default, Integer)
508
+ def apprx_i(x,result=Integer)
509
+ r = x.round
510
+ return equals?(x,r) ? r.prec(result) : x
511
+ end
512
+
513
+
514
+ # Returns the magnitude of the tolerance
515
+ def magnitude
516
+ @t
517
+ end
518
+ # Returns the number of decimal digits of the tolerance
519
+ def num_decimals
520
+ @d
521
+ end
522
+ # Returns true for decimal-mode tolerance
523
+ def decimal?
524
+ @decimal_mode
525
+ end
526
+ # Returns the mode (:abs, :rel, :sig) of the tolerance
527
+ def mode
528
+ @mode
529
+ end
530
+
531
+
532
+ private
533
+
534
+ HALF = BigDecimal('0.5')
535
+
536
+ def set(t=BigDecimal('0'), mode=:abs, decmode=false)
537
+
538
+ @t = t
539
+ @t = HALF if @t > HALF
540
+ raise TypeError,"El valor de tolerancia debe ser de tipo BigDecimal" if @t.class!=BigDecimal
541
+ @mode = mode
542
+ @decimal_mode = decmode
543
+ @d = @t.zero? ? 0 : -(@t*2).exponent+1
544
+ @ref_exp = BigDecimal('1').exponent # reference for significative mode: [1,10)
545
+
546
+ self
547
+ end
548
+
549
+ def rel(x)
550
+ r = @t
551
+ case @mode
552
+ when :sig
553
+ d = x==0 ? 0 : x.exponent
554
+ r = @t*BigDecimal("1E#{d-@ref_exp}")
555
+ when :rel
556
+ r = @t*x.abs
557
+ end
558
+ r
559
+ end
560
+
561
+ end
562
+
563
+ def BigTolerance.decimals(d=0, mode=:abs)
564
+ BigTolerance.new.decimals(d,mode)
565
+ end
566
+ def BigTolerance.sig_decimals(d=0, mode=:abs)
567
+ BigTolerance.new.sig_decimals(d)
568
+ end
569
+ def BigTolerance.fraction(f)
570
+ BigTolerance.new.fraction(f)
571
+ end
572
+ def BigTolerance.percent(p)
573
+ BigTolerance.new.percent(p)
574
+ end
575
+ def BigTolerance.permille(p)
576
+ BigTolerance.new.permille(p)
577
+ end
578
+
579
+ module_function
580
+
581
+ # Tol(x) -> a Tolerance
582
+ # This module function will convert its argument to a Noi::Tolerance
583
+ # or a Noi::BigTolerance depending on its argument;
584
+ #
585
+ # Values of type Tolerance,Float,Integer (for Tolerance) or
586
+ # BigTolerance,BigDecimal (for BigTolerance) are accepted.
587
+ def Tol(x) # :doc:
588
+ case x
589
+ when Tolerance
590
+ x
591
+ when BigTolerance
592
+ x
593
+ when BigDecimal
594
+ BigTolerance.new(x)
595
+ when Float
596
+ Tolerance.new(x)
597
+ when Integer
598
+ Tolerance.sig_decimals(x)
599
+ else # e.g. Rational
600
+ x
601
+ end
602
+ end
603
+
604
+ # BigTol(x) -> a BigTolerance
605
+ # This module function will convert its argument to a Noi::BigTolerance
606
+ #
607
+ # Values of type BigTolerance or Numeric are accepted.
608
+ def BigTol(x) # :doc:
609
+ case x
610
+ when BigTolerance
611
+ x
612
+ when Integer
613
+ BigTolerance.sig_decimals(x)
614
+ when Rational
615
+ x
616
+ else
617
+ BigTolerance.new(BigDec(x))
618
+ end
619
+ end
620
+
621
+ # BigDec(x) -> a BigDecimal
622
+ # BigDec(x,precision) -> a BigDecimal
623
+ # BigDec(x,:exact) -> a BigDecimal
624
+ # This is a shortcut to define a BigDecimal without using quotes
625
+ # and a general conversion to BigDecimal method.
626
+ #
627
+ # The second parameter can be :exact to try for an exact conversion
628
+ #
629
+ # Conversions from Float have issues that should be understood; :exact
630
+ # conversion will use the exact internal value of the Float, and when
631
+ # no precision is specified, a value as simple as possible expressed as
632
+ # a fraction will be used.
633
+ def BigDec(x,prec=nil) # :doc:
634
+ if x.respond_to?(:to_str)
635
+ x = BigDecimal(x.to_str, prec||0)
636
+ else
637
+ case x
638
+ when Integer
639
+ x = BigDecimal(x.to_s)
640
+ when Rational
641
+ if prec && prec!=:exact
642
+ x = BigDecimal.new(x.numerator.to_s).div(x.denominator,prec)
643
+ else
644
+ x = BigDecimal.new(x.numerator.to_s)/BigDecimal.new(x.denominator.to_s)
645
+ end
646
+ when BigDecimal
647
+ when Float
648
+ x = nio_float_to_bigdecimal(x,prec)
649
+ end
650
+ end
651
+ x
652
+ end
653
+
654
+ end