nio 0.2.0

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