ruby-decimal 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -7,7 +7,7 @@
7
7
  * New functions implemented:
8
8
  exp(), ln(), log10(), power().
9
9
 
10
-
10
+ == 0.2.1 2009-06-23
11
11
 
12
12
  * Bug fixes:
13
13
  - Decimal#inspect was always producing debug information; now it uses $DEBUG
@@ -22,3 +22,10 @@
22
22
  - setting context exponent limits with elimit
23
23
  - require decimal/shortcut to use D for Decimal
24
24
 
25
+ == 0.2.2 2009-07-21
26
+
27
+ * Bug fixes:
28
+ - Decimal#normal? was incorrect for special values and zero.
29
+ - After a local context, the global context was set to a copy of the original
30
+ context, so any previously stored reference to it was now unbound.
31
+ - Context#normalize was incorrect.
data/README.txt CHANGED
@@ -120,6 +120,14 @@ Decimal::ExtendedContext in the following examples:
120
120
 
121
121
  Decimal.context = Decimal::ExtendedContext
122
122
 
123
+ Most decimal operations can be executed by using either Context or Decimal methods:
124
+
125
+ puts Decimal.context.exp(1) -> 2.71828183
126
+ puts Decimal(1).exp -> 2.71828183
127
+
128
+ If using Context methods, values are automatically converted as if the Decimal() constructor
129
+ was used.
130
+
123
131
  ==Rounding
124
132
 
125
133
  Results are normally rounded using the precision (number of significant digits)
@@ -241,14 +249,22 @@ Or with a generic method:
241
249
  puts Decimal('1.1').convert_to(Integer) -> 1
242
250
  puts Decimal('1.1').convert_to(Rational) -> 11/10
243
251
 
244
- Conversion is also possible to Float:
252
+ Thera are also GDAS style conversion operations:
253
+
254
+ puts Decimal('1.1').to_integral_value -> 1
255
+
256
+ And conversion is also possible to Float:
245
257
  puts Decimal('1.1').to_f -> 1.1
246
258
  puts Decimal('1.1').convert_to(Float) -> 1.1
247
259
  puts Float(Decimal('1.1')) -> 1.1
248
260
 
249
- And with GDAS style operations:
261
+ Types with predefined bidirectional conversion (Integer and Rational)
262
+ can be operated with Decimal on either side of an operator, and the result will be a Decimal.
263
+ For Float there is no predefined bidirectional conversion (see below how to define it)
264
+ and the result of an operation between Decimal and Float will be of type Float.
250
265
 
251
- puts Decimal('1.1').to_integral_value -> 1
266
+ puts (Decimal(1.1) + 2.0).class -> Float
267
+ puts (2.0 + Decimal(1.1)).class -> Float
252
268
 
253
269
  The conversion system is extensible. For example, we can include BigDecimal into it
254
270
  by defining suitable conversion procedures:
@@ -548,7 +548,7 @@ class Decimal
548
548
  # normalizes so that the coefficient has precision digits
549
549
  # (this is not the old GDA normalize function)
550
550
  def normalize(x)
551
- _convert(x).reduce(self)
551
+ _convert(x).normalize(self)
552
552
  end
553
553
 
554
554
  # Adjusted exponent of x returned as a Decimal value.
@@ -799,20 +799,22 @@ class Decimal
799
799
  # of the particular rounding rules used.
800
800
  def strict_epsilon(sign=+1)
801
801
  return exception(InvalidOperation, "Exact context strict epsilon") if exact?
802
- coeff = 1
803
- exp = 1-precision
804
802
  # assume radix is even (Decimal.radix%2 == 0)
805
803
  case rounding
806
804
  when :down, :floor
805
+ # largest epsilon: 0.0...10 (precision digits shown to the right of the decimal point)
807
806
  exp = 1-precision
808
807
  coeff = 1
809
808
  when :half_even, :half_down #, :up # :up # :down, :half_down, :up05, :floor
809
+ # next largest: 0.0...050...1 (+precision-1 additional digits here)
810
810
  exp = 1-2*precision
811
811
  coeff = 1 + Decimal.int_radix_power(precision)/2
812
812
  when :half_up
813
+ # next largest: 0.0...05 (precision digits shown to the right of the decimal point)
813
814
  exp = 1-2*precision
814
815
  coeff = Decimal.int_radix_power(precision)/2
815
816
  when :up, :ceiling, :up05
817
+ # smallest epsilon
816
818
  return minimum_nonzero(sign)
817
819
  end
818
820
  return Decimal(sign, coeff, exp)
@@ -1004,16 +1006,20 @@ class Decimal
1004
1006
  local_context(*args, &blk)
1005
1007
  elsif args.empty?
1006
1008
  # return the current context
1007
- Thread.current['Decimal.context'] ||= DefaultContext.dup
1009
+ self._context = DefaultContext.dup if _context.nil?
1010
+ _context
1008
1011
  else
1009
1012
  # change the current context
1013
+ # TODO: consider doing _context = ... here
1014
+ # so we would have Decimal.context = c that assigns a duplicate of c
1015
+ # and Decimal.context c to set alias c
1010
1016
  Decimal.context = define_context(*args)
1011
1017
  end
1012
1018
  end
1013
1019
 
1014
1020
  # Change the current context (thread-local).
1015
1021
  def Decimal.context=(c)
1016
- Thread.current['Decimal.context'] = c.dup
1022
+ self._context = c.dup
1017
1023
  end
1018
1024
 
1019
1025
  # Defines a scope with a local context. A context can be passed which will be
@@ -1021,9 +1027,9 @@ class Decimal
1021
1027
  # options to apply to the local scope.
1022
1028
  # Changes done to the current context are reversed when the scope is exited.
1023
1029
  def Decimal.local_context(*args)
1024
- keep = context.dup
1025
- Decimal.context = define_context(*args)
1026
- result = yield Decimal.context
1030
+ keep = Decimal.context # use this so _context is initialized if necessary
1031
+ Decimal.context = define_context(*args) # this dups the assigned context
1032
+ result = yield _context
1027
1033
  # TODO: consider the convenience of copying the flags from Decimal.context to keep
1028
1034
  # This way a local context does not affect the settings of the previous context,
1029
1035
  # but flags are transferred.
@@ -1031,10 +1037,21 @@ class Decimal
1031
1037
  # keep.flags = Decimal.context.flags
1032
1038
  # Another alternative to consider: logically or the flags:
1033
1039
  # keep.flags ||= Decimal.context.flags # (this requires implementing || in Flags)
1034
- Decimal.context = keep
1040
+ self._context = keep
1035
1041
  result
1036
1042
  end
1037
1043
 
1044
+ class <<self
1045
+ # This is the thread-local context storage low level interface
1046
+ protected
1047
+ def _context #:nodoc:
1048
+ Thread.current['Decimal.context']
1049
+ end
1050
+ def _context=(c) #:nodoc:
1051
+ Thread.current['Decimal.context'] = c
1052
+ end
1053
+ end
1054
+
1038
1055
  # A decimal number with value zero and the specified sign
1039
1056
  def Decimal.zero(sign=+1)
1040
1057
  Decimal.new([sign, 0, 0])
@@ -1301,7 +1318,7 @@ class Decimal
1301
1318
 
1302
1319
  # Returns whether the number is normal
1303
1320
  def normal?(context=nil)
1304
- return true if special? || zero?
1321
+ return false if special? || zero?
1305
1322
  context = Decimal.define_context(context)
1306
1323
  (context.emin <= self.adjusted_exponent) && (self.adjusted_exponent <= context.emax)
1307
1324
  end
@@ -1334,6 +1351,8 @@ class Decimal
1334
1351
  case other
1335
1352
  when *Decimal.context.coercible_types_or_decimal
1336
1353
  [Decimal(other),self]
1354
+ when Float
1355
+ [other, self.to_f]
1337
1356
  else
1338
1357
  super
1339
1358
  end
@@ -2229,7 +2248,7 @@ class Decimal
2229
2248
  end
2230
2249
  end
2231
2250
  else
2232
- if defined? other.coerce
2251
+ if !self.nan? && defined? other.coerce
2233
2252
  x, y = other.coerce(self)
2234
2253
  x <=> y
2235
2254
  else
@@ -2535,7 +2554,12 @@ class Decimal
2535
2554
  prec = adjusted_exponent + 1
2536
2555
  as_int = true
2537
2556
  end
2538
- result = plus(:rounding=>r, :precision=>prec)
2557
+ dg = number_of_digits-prec
2558
+ changed = _round(r, dg)
2559
+ coeff = Decimal.int_div_radix_power(@coeff, dg)
2560
+ exp = @exp + dg
2561
+ coeff += 1 if changed==1
2562
+ result = Decimal(@sign, coeff, exp)
2539
2563
  return as_int ? result.to_i : result
2540
2564
  end
2541
2565
 
@@ -3530,9 +3554,10 @@ class Decimal
3530
3554
  if ROUND_ARITHMETIC
3531
3555
  (@coeff % Decimal.int_radix_power(i))==0 ? 0 : -1
3532
3556
  else
3557
+ return 0 if i==0
3533
3558
  d = @coeff.to_s
3534
- p = d.size - i
3535
- d[p..-1].match(/\A0+\Z/) ? 0 : -1
3559
+ tail = d[-i..-1]
3560
+ (tail.nil? || tail.match(/\A0*\Z/)) ? 0 : -1
3536
3561
  end
3537
3562
  end
3538
3563
 
@@ -3551,9 +3576,19 @@ class Decimal
3551
3576
  _round_half_up(i)
3552
3577
  end
3553
3578
  else
3579
+ return 0 if i==0
3554
3580
  d = @coeff.to_s
3555
3581
  p = d.size - i
3556
- d[p..-1].match(/^5d*$/) ? -1 : _round_half_up(i)
3582
+ rdig = d[p,1]
3583
+ if '6789'.include?(rdig)
3584
+ 1
3585
+ elsif '1234'.include?(rdig)
3586
+ -1
3587
+ elsif rdig=='5'
3588
+ d[p+1..-1].match(/^0*$/) ? -1 : +1
3589
+ else # rdig=='0'
3590
+ d[p..-1].match(/^0*$/) ? 0 : -1
3591
+ end
3557
3592
  end
3558
3593
 
3559
3594
  end
@@ -3568,6 +3603,7 @@ class Decimal
3568
3603
  (@coeff % m)==0 ? 0 : -1
3569
3604
  end
3570
3605
  else
3606
+ return 0 if i==0
3571
3607
  d = @coeff.to_s
3572
3608
  p = d.size - i
3573
3609
  if '56789'.include?(d[p,1])
@@ -3589,6 +3625,7 @@ class Decimal
3589
3625
  _round_half_up(i)
3590
3626
  end
3591
3627
  else
3628
+ return 0 if i==0
3592
3629
  d = @coeff.to_s
3593
3630
  p = d.size - i
3594
3631
 
@@ -3679,7 +3716,7 @@ class Decimal
3679
3716
  end
3680
3717
  # adj == -1, 0.1 <= self < 1
3681
3718
  return e + (10**-e - c).to_s.length - 1
3682
- end
3719
+ end
3683
3720
 
3684
3721
  module AuxiliarFunctions #:nodoc:
3685
3722
 
@@ -2,7 +2,7 @@ module DecimalSupport
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 2
5
- TINY = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -93,7 +93,7 @@ class TestDefineConversions < Test::Unit::TestCase
93
93
  assert_equal Decimal('7.1'), Decimal('7')+0.1
94
94
  end
95
95
  assert_equal Decimal('11'), Decimal(11.0)
96
- assert Decimal(11.0).is_a?(Decimal)
96
+ assert_instance_of Decimal, Decimal(11.0)
97
97
 
98
98
  Decimal.context.define_conversion_from(Float) do |x, context|
99
99
  Decimal.context(context, :exact=>true) do
@@ -109,4 +109,33 @@ class TestDefineConversions < Test::Unit::TestCase
109
109
 
110
110
  end
111
111
 
112
+ def test_conversion_types
113
+
114
+ assert_instance_of Decimal, Decimal(1)+3
115
+ assert_instance_of Decimal, 3+Decimal(1)
116
+ assert_instance_of Decimal, Rational(1,5)+Decimal(1)
117
+ assert_instance_of Decimal, Decimal(1)+Rational(1,5)
118
+ assert_instance_of Float, Decimal(1)+3.0
119
+ assert_instance_of Float, 3.0+Decimal(1)
120
+
121
+ Decimal.context.define_conversion_from(Float) do |x, context|
122
+ s,e = Math.frexp(x)
123
+ significand = Math.ldexp(s, Float::MANT_DIG).to_i
124
+ exponent = e - Float::MANT_DIG
125
+ # the number is (as a Rational) significand * exponent**Float::RADIX
126
+ Decimal(significand*(Float::RADIX**exponent ))
127
+ end
128
+
129
+ assert_instance_of Decimal, Decimal(1)+3.0
130
+ assert_instance_of Decimal, 3.0+Decimal(1)
131
+
132
+ Decimal.context.define_conversion_from(BigDecimal) do |x, context|
133
+ Decimal(x.to_s) # or use x.split etc.
134
+ end
135
+
136
+ assert_instance_of Decimal, Decimal(1)+BigDecimal('3')
137
+ assert_instance_of Decimal, BigDecimal('3')+Decimal(1)
138
+
139
+ end
140
+
112
141
  end
data/test/test_round.rb CHANGED
@@ -43,6 +43,11 @@ class TestRound < Test::Unit::TestCase
43
43
  assert_equal 100, Decimal('100.99999').truncate
44
44
  assert_equal(-100, Decimal('-100.99999').truncate)
45
45
 
46
+ assert_equal(10, Decimal('9.99999').round(:rounding=>:half_up))
47
+ assert_equal(1, Decimal('0.999999').round(:rounding=>:half_up))
48
+ assert_equal(0, Decimal('0.0999999').round(:rounding=>:half_up))
49
+ assert_equal(1, Decimal('0.0999999').round(:rounding=>:up))
50
+
46
51
  end
47
52
 
48
53
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-decimal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Goizueta
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-06-23 00:00:00 +02:00
12
+ date: 2009-07-21 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency