flt 1.2.1 → 1.3.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.
@@ -595,6 +595,7 @@ class Num < Numeric
595
595
  @clamp = options[:clamp ] unless options[:clamp ].nil?
596
596
  @exact = options[:exact ] unless options[:exact ].nil?
597
597
  @angle = options[:angle ] unless options[:angle ].nil?
598
+ @precision += options[:extra_precision] unless options[:extra_precision].nil?
598
599
  update_precision
599
600
  end
600
601
  end
@@ -676,6 +677,36 @@ class Num < Numeric
676
677
  _convert(x)._neg(self)
677
678
  end
678
679
 
680
+ # Power. See DecNum#power()
681
+ def power(x,y,modulo=nil)
682
+ _convert(x).power(y,modulo,self)
683
+ end
684
+
685
+ # Returns the base 10 logarithm
686
+ def log10(x)
687
+ _convert(x).log10(self)
688
+ end
689
+
690
+ # Returns the base 2 logarithm
691
+ def log2(x)
692
+ _convert(x).log10(self)
693
+ end
694
+
695
+ # Exponential function: e**x
696
+ def exp(x)
697
+ _convert(x).exp(self)
698
+ end
699
+
700
+ # Returns the natural (base e) logarithm
701
+ def ln(x)
702
+ _convert(x).ln(self)
703
+ end
704
+
705
+ # Ruby-style log function: arbitrary base logarithm which defaults to natural logarithm
706
+ def log(x, base=nil)
707
+ _convert(x).log(base, self)
708
+ end
709
+
679
710
  # Converts a number to a string
680
711
  def to_string(x, eng=false)
681
712
  _convert(x)._fix(self).to_s(eng, self)
@@ -1176,8 +1207,8 @@ class Num < Numeric
1176
1207
  private :define_context
1177
1208
 
1178
1209
  # The current context (thread-local).
1179
- # If arguments are passed they are interpreted as in Num.define_context() to change
1180
- # the current context.
1210
+ # If arguments are passed they are interpreted as in Num.define_context() and an altered copy
1211
+ # of the current context is returned.
1181
1212
  # If a block is given, this method is a synonym for Num.local_context().
1182
1213
  def self.context(*args, &blk)
1183
1214
  if blk
@@ -1189,11 +1220,12 @@ class Num < Numeric
1189
1220
  self._context = ctxt = self::DefaultContext.dup if ctxt.nil?
1190
1221
  ctxt
1191
1222
  else
1192
- # change the current context
1193
- # TODO: consider doing self._context = ... here
1194
- # so we would have DecNum.context = c that assigns a duplicate of c
1195
- # and DecNum.context c to set alias c
1196
- self.context = define_context(*args)
1223
+ # Return a modified copy of the current context
1224
+ if args.first.kind_of?(ContextBase)
1225
+ self.define_context(*args)
1226
+ else
1227
+ self.define_context(self.context, *args)
1228
+ end
1197
1229
  end
1198
1230
  end
1199
1231
 
@@ -1202,6 +1234,11 @@ class Num < Numeric
1202
1234
  self._context = c.dup
1203
1235
  end
1204
1236
 
1237
+ # Modify the current context, e.g. DecNum.set_context(:precision=>10)
1238
+ def self.set_context(*args)
1239
+ self.context = define_context(*args)
1240
+ end
1241
+
1205
1242
  # Defines a scope with a local context. A context can be passed which will be
1206
1243
  # set a the current context for the scope; also a hash can be passed with
1207
1244
  # options to apply to the local scope.
@@ -1262,6 +1299,11 @@ class Num < Numeric
1262
1299
  new [+1, nil, :nan]
1263
1300
  end
1264
1301
 
1302
+ # One half: 1/2
1303
+ def one_half
1304
+ new '0.5'
1305
+ end
1306
+
1265
1307
  def int_radix_power(n)
1266
1308
  self.radix**n
1267
1309
  end
@@ -2280,6 +2322,164 @@ class Num < Numeric
2280
2322
 
2281
2323
  end
2282
2324
 
2325
+ # Naive implementation of exponential and logarithm functions; should be replaced
2326
+ # by something more efficient in specific Num classes.
2327
+
2328
+ # Exponential function
2329
+ def exp(context=nil)
2330
+ context = num_class.define_context(context)
2331
+
2332
+ # exp(NaN) = NaN
2333
+ ans = _check_nans(context)
2334
+ return ans if ans
2335
+
2336
+ # exp(-Infinity) = 0
2337
+ return num_class.zero if self.infinite? && (self.sign == -1)
2338
+
2339
+ # exp(0) = 1
2340
+ return Num(1) if self.zero?
2341
+
2342
+ # exp(Infinity) = Infinity
2343
+ return Num(self) if self.infinite?
2344
+
2345
+ # the result is now guaranteed to be inexact (the true
2346
+ # mathematical result is transcendental). There's no need to
2347
+ # raise Rounded and Inexact here---they'll always be raised as
2348
+ # a result of the call to _fix.
2349
+ return context.exception(Inexact, 'Inexact exp') if context.exact?
2350
+ p = context.precision
2351
+ adj = self.adjusted_exponent
2352
+
2353
+ if self.sign == +1 and adj > _number_of_digits((context.emax+1)*3)
2354
+ # overflow
2355
+ ans = Num(+1, 1, context.emax+1)
2356
+ elsif self.sign == -1 and adj > _number_of_digits((-context.etiny+1)*3)
2357
+ # underflow to 0
2358
+ ans = Num(+1, 1, context.etiny-1)
2359
+ elsif self.sign == +1 and adj < -p
2360
+ # p+1 digits; final round will raise correct flags
2361
+ ans = Num(+1, num_clas.int_radix_power(p)+1, -p)
2362
+ elsif self.sign == -1 and adj < -p-1
2363
+ # p+1 digits; final round will raise correct flags
2364
+ ans = Num(+1, num_clas.int_radix_power(p+1)-1, -p-1)
2365
+ else
2366
+ # general case
2367
+ x_sign = self.sign
2368
+ x = self.copy_sign(+1)
2369
+ i, lasts, s, fact, num = 0, 0, 1, 1, 1
2370
+ elim = [context.emax, -context.emin, 10000].max
2371
+ xprec = num_class.radix==10 ? 3 : 4
2372
+ num_class.local_context(context, :extra_precision=>xprec, :rounding=>:half_even, :elimit=>elim) do
2373
+ while s != lasts
2374
+ lasts = s
2375
+ i += 1
2376
+ fact *= i
2377
+ num *= x
2378
+ s += num / fact
2379
+ end
2380
+ s = num_class.Num(1)/s if x_sign<0
2381
+ end
2382
+ ans = s
2383
+ end
2384
+
2385
+ # at this stage, ans should round correctly with *any*
2386
+ # rounding mode, not just with ROUND_HALF_EVEN
2387
+ num_class.context(context, :rounding=>:half_even) do |local_context|
2388
+ ans = ans._fix(local_context)
2389
+ context.flags = local_context.flags
2390
+ end
2391
+
2392
+ return ans
2393
+ end
2394
+
2395
+ # Returns the natural (base e) logarithm
2396
+ def ln(context=nil)
2397
+ context = num_class.define_context(context)
2398
+
2399
+ # ln(NaN) = NaN
2400
+ ans = _check_nans(context)
2401
+ return ans if ans
2402
+
2403
+ # ln(0.0) == -Infinity
2404
+ return num_class.infinity(-1) if self.zero?
2405
+
2406
+ # ln(Infinity) = Infinity
2407
+ return num_class.infinity if self.infinite? && self.sign == +1
2408
+
2409
+ # ln(1.0) == 0.0
2410
+ return num_class.zero if self == Num(1)
2411
+
2412
+ # ln(negative) raises InvalidOperation
2413
+ return context.exception(InvalidOperation, 'ln of a negative value') if self.sign==-1
2414
+
2415
+ # result is irrational, so necessarily inexact
2416
+ return context.exception(Inexact, 'Inexact exp') if context.exact?
2417
+
2418
+ elim = [context.emax, -context.emin, 10000].max
2419
+ xprec = num_class.radix==10 ? 3 : 4
2420
+ num_class.local_context(context, :extra_precision=>xprec, :rounding=>:half_even, :elimit=>elim) do
2421
+
2422
+ one = num_class.Num(1)
2423
+
2424
+ x = self
2425
+ if (expo = x.adjusted_exponent)<-1 || expo>=2
2426
+ x = x.scaleb(-expo)
2427
+ else
2428
+ expo = nil
2429
+ end
2430
+
2431
+ x = (x-one)/(x+one)
2432
+ x2 = x*x
2433
+ ans = x
2434
+ d = ans
2435
+ i = one
2436
+ last_ans = nil
2437
+ while ans != last_ans
2438
+ last_ans = ans
2439
+ x = x2*x
2440
+ i += 2
2441
+ d = x/i
2442
+ ans += d
2443
+ end
2444
+ ans *= 2
2445
+ if expo
2446
+ ans += num_class.Num(num_class.radix).ln*expo
2447
+ end
2448
+ end
2449
+
2450
+ num_class.context(context, :rounding=>:half_even) do |local_context|
2451
+ ans = ans._fix(local_context)
2452
+ context.flags = local_context.flags
2453
+ end
2454
+ return ans
2455
+ end
2456
+
2457
+ # Ruby-style logarithm of arbitrary base, e (natural base) by default
2458
+ def log(b=nil, context=nil)
2459
+ if b.nil?
2460
+ self.ln(context)
2461
+ elsif b==10
2462
+ self.log10(context)
2463
+ elsif b==2
2464
+ self.log2(context)
2465
+ else
2466
+ context = num_class.define_context(context)
2467
+ +num_class.context(:extra_precision=>3){self.ln(context)/num_class[b].ln(context)}
2468
+ end
2469
+ end
2470
+
2471
+ # Returns the base 10 logarithm
2472
+ def log10(context=nil)
2473
+ context = num_class.define_context(context)
2474
+ num_class.context(:extra_precision=>3){self.ln/num_class.Num(10).ln}
2475
+ end
2476
+
2477
+ # Returns the base 2 logarithm
2478
+ def log2(context=nil)
2479
+ context = num_class.define_context(context)
2480
+ num_class.context(context, :extra_precision=>3){self.ln()/num_class.Num(2).ln}
2481
+ end
2482
+
2283
2483
  # Convert to other numerical type.
2284
2484
  def convert_to(type, context=nil)
2285
2485
  context = define_context(context)
@@ -2349,14 +2549,14 @@ class Num < Numeric
2349
2549
  # The ulp here is context.maximum_finite - context.maximum_finite.next_minus
2350
2550
  return Num(+1, 1, context.etop)
2351
2551
  elsif self.zero? || self.adjusted_exponent <= context.emin
2352
- # This is the ulp value for self.abs <= context.minimum_normal*DecNum.context
2353
- # Here we use it for self.abs < context.minimum_normal*DecNum.context;
2552
+ # This is the ulp value for self.abs <= context.minimum_normal*num_class.context
2553
+ # Here we use it for self.abs < context.minimum_normal*num_class.context;
2354
2554
  # because of the simple exponent check; the remaining cases are handled below.
2355
2555
  return context.minimum_nonzero
2356
2556
  else
2357
2557
  # The next can compute the ulp value for the values that
2358
2558
  # self.abs > context.minimum_normal && self.abs <= context.maximum_finite
2359
- # The cases self.abs < context.minimum_normal*DecNum.context have been handled above.
2559
+ # The cases self.abs < context.minimum_normal*num_class.context have been handled above.
2360
2560
 
2361
2561
  # assert self.normal? && self.abs>context.minimum_nonzero
2362
2562
  norm = self.normalize
@@ -2452,7 +2652,7 @@ class Num < Numeric
2452
2652
  reduce.split == other.reduce.split
2453
2653
  end
2454
2654
 
2455
- # Compares like <=> but returns a DecNum value.
2655
+ # Compares like <=> but returns a Num value.
2456
2656
  def compare(other, context=nil)
2457
2657
 
2458
2658
  other = _convert(other)
@@ -2475,7 +2675,7 @@ class Num < Numeric
2475
2675
  end
2476
2676
  end
2477
2677
 
2478
- # Synonym for DecNum#adjusted_exponent()
2678
+ # Synonym for Num#adjusted_exponent()
2479
2679
  def scientific_exponent
2480
2680
  adjusted_exponent
2481
2681
  end
@@ -2718,7 +2918,7 @@ class Num < Numeric
2718
2918
  # digit to be rounded (exponent == -places)
2719
2919
  # * :precision or :significan_digits is the number of digits
2720
2920
  # * :power 10^exponent, value of the digit to be rounded,
2721
- # should be passed as a type convertible to DecNum.
2921
+ # should be passed as a type convertible to Num.
2722
2922
  # * :index 0-based index of the digit to be rounded
2723
2923
  # * :rindex right 0-based index of the digit to be rounded
2724
2924
  #
@@ -2743,7 +2943,7 @@ class Num < Numeric
2743
2943
  elsif v=(opt[:exponent])
2744
2944
  prec = adjusted_exponent + 1 - v
2745
2945
  elsif v=(opt[:power])
2746
- prec = adjusted_exponent + 1 - DecNum(v).adjusted_exponent
2946
+ prec = adjusted_exponent + 1 - num_class.Num(v).adjusted_exponent
2747
2947
  elsif v=(opt[:index])
2748
2948
  prec = i+1
2749
2949
  elsif v=(opt[:rindex])
@@ -2869,13 +3069,31 @@ class Num < Numeric
2869
3069
  format(context, options.merge(:eng=>eng))
2870
3070
  end
2871
3071
 
2872
- # Raises to the power of x.
3072
+ # Raises to the power of x, to modulo if given.
2873
3073
  #
2874
- # If self is negative then other
3074
+ # With two arguments, compute self**other. If self is negative then other
2875
3075
  # must be integral. The result will be inexact unless other is
2876
3076
  # integral and the result is finite and can be expressed exactly
2877
3077
  # in 'precision' digits.
2878
- def power(other, context=nil)
3078
+ #
3079
+ # With three arguments, compute (self**other) % modulo. For the
3080
+ # three argument form, the following restrictions on the
3081
+ # arguments hold:
3082
+ #
3083
+ # - all three arguments must be integral
3084
+ # - other must be nonnegative
3085
+ # - at least one of self or other must be nonzero
3086
+ # - modulo must be nonzero and have at most 'precision' digits
3087
+ #
3088
+ # The result of a.power(b, modulo) is identical to the result
3089
+ # that would be obtained by computing (a**b) % modulo with
3090
+ # unbounded precision, but may be computed more efficiently. It is
3091
+ # always exact.
3092
+ def power(other, modulo=nil, context=nil)
3093
+ if context.nil? && (modulo.kind_of?(ContextBase) || modulo.is_a?(Hash))
3094
+ context = modulo
3095
+ modulo = nil
3096
+ end
2879
3097
 
2880
3098
  context = num_class.define_context(context)
2881
3099
  other = _convert(other)
@@ -3045,6 +3263,9 @@ class Num < Numeric
3045
3263
  end
3046
3264
  context.exception Underflow if ans.adjusted_exponent < context.emin
3047
3265
  end
3266
+
3267
+ ans = ans % modulo if modulo
3268
+
3048
3269
  # unlike exp, ln and log10, the power function respects the
3049
3270
  # rounding mode; no need to use ROUND_HALF_EVEN here
3050
3271
  ans._fix(context)
@@ -3264,7 +3485,7 @@ class Num < Numeric
3264
3485
 
3265
3486
  if number_of_digits > max_payload_len
3266
3487
  payload = payload.to_s[-max_payload_len..-1].to_i
3267
- return DecNum([@sign, payload, @exp])
3488
+ return num_class.Num([@sign, payload, @exp])
3268
3489
  end
3269
3490
  end
3270
3491
  Num(self)
@@ -3421,7 +3642,7 @@ class Num < Numeric
3421
3642
  leftdigits = dec_pos
3422
3643
  end
3423
3644
 
3424
- # TODO: DRY (this code is duplicated in DecNum#format)
3645
+ # TODO: DRY (this code is duplicated in num_class#format)
3425
3646
  if exp<=0 && leftdigits>-6
3426
3647
  dotplace = leftdigits
3427
3648
  elsif !eng
@@ -4122,7 +4343,15 @@ class Num < Numeric
4122
4343
  #
4123
4344
  # If the base does not correspond to one of the predefined classes (DecNum, BinNum), a new class
4124
4345
  # is dynamically generated.
4125
- def [](base)
4346
+ #
4347
+ # The [] operator can also be applied to classes derived from Num to act as a constructor
4348
+ # (short hand for .new):
4349
+ # Flt::Num[10]['0.1'] # same as FLt::DecNum['0.1'] or Flt.DecNum('0.1') or Flt::DecNum.new('0.1')
4350
+ def [](*args)
4351
+ return self.Num(*args) if self!=Num # && self.ancestors.include?(Num)
4352
+ raise RuntimeError, "Invalid number of arguments (#{args.size}) for Num.[]; 1 expected." unless args.size==1
4353
+ base = args.first
4354
+
4126
4355
  case base
4127
4356
  when 10
4128
4357
  DecNum
@@ -0,0 +1,746 @@
1
+ require 'flt/dec_num'
2
+
3
+ module Flt
4
+
5
+ # Trigonometry functions. The angular units used by these functions can be specified
6
+ # with the +angle+ attribute of the context. The accepted values are:
7
+ # * :rad for radians
8
+ # * :deg for degrees
9
+ # * :grad for gradians
10
+ #
11
+ # These functions are injected in Context objects.
12
+ module Trigonometry
13
+
14
+ # Cosine of an angle given in the units specified by the context +angle+ attribute.
15
+ def cos(x)
16
+ cos_base(num_class[x])
17
+ end
18
+
19
+ # Sine of an angle given in the units specified by the context +angle+ attribute.
20
+ def sin(x)
21
+ sin_base(num_class[x])
22
+ end
23
+
24
+ # Tangent of an angle given in the units specified by the context +angle+ attribute.
25
+ def tan(x)
26
+ tan_base(num_class[x])
27
+ end
28
+
29
+ # Arc-tangent. The result is in the units specified by the context +angle+ attribute.
30
+ # If the angular units are radians the result is in [-pi/2, pi/2]; it is in [-90,90] in degrees.
31
+ def atan(x)
32
+ atan_base(num_class[x])
33
+ end
34
+
35
+ # Arc-tangent with two arguments (principal value of the argument of the complex number x+i*y).
36
+ # The result is in the units specified by the context +angle+ attribute.
37
+ # If the angular units are radians the result is in [-pi, pi]; it is in [-180,180] in degrees.
38
+ def atan2(y, x)
39
+ atan2_base(num_class[y], num_class[x])
40
+ end
41
+
42
+ # Arc-sine. The result is in the units specified by the context +angle+ attribute.
43
+ # If the angular units are radians the result is in [-pi/2, pi/2]; it is in [-90,90] in degrees.
44
+ def asin(x)
45
+ asin_base(num_class[x])
46
+ end
47
+
48
+ # Arc-cosine. The result is in the units specified by the context +angle+ attribute.
49
+ # If the angular units are radians the result is in [-pi/2, pi/2]; it is in [-90,90] in degrees.
50
+ def acos(x)
51
+ acos_base(num_class[x])
52
+ end
53
+
54
+ # Length of the hypotenuse of a right-angle triangle (modulus or absolute value of the complex x+i*y).
55
+ def hypot(x, y)
56
+ hypot_base(num_class[x], num_class[y])
57
+ end
58
+
59
+ # Pi
60
+ def pi(round_digits=nil)
61
+ round_digits ||= self.precision
62
+ if Trigonometry.pi_digits < round_digits
63
+ # provisional implementation (very slow)
64
+ lasts = 0
65
+ t, s, n, na, d, da = Trigonometry.pi_cache
66
+ num_class.context(self) do |local_context|
67
+ local_context.precision = round_digits + 6
68
+ tol = Rational(1,num_class.int_radix_power(local_context.precision+1))
69
+ while (s-lasts)>tol
70
+ lasts = s
71
+ n, na = n+na, na+8
72
+ d, da = d+da, da+32
73
+ t = (t * n) / d
74
+ s += t
75
+ end
76
+ Trigonometry.pi_value = num_class[s]
77
+ Trigonometry.pi_digits = round_digits
78
+ Trigonometry.pi_cache = [t, s, n, na, d, da]
79
+ end
80
+ end
81
+ num_class.context(self, :precision=>round_digits){+Trigonometry.pi_value}
82
+ end
83
+
84
+ def e(digits=nil)
85
+ num_class.context(self) do |local_context|
86
+ local_context.precision = digits if digits
87
+ num_class.Num(1).exp
88
+ end
89
+ end
90
+
91
+ def half
92
+ @half ||= num_class.one_half
93
+ end
94
+
95
+ # Hyperbolic sine
96
+ def sinh(x)
97
+ sinh_base(num_class[x])
98
+ end
99
+
100
+ # Hyperbolic cosine
101
+ def cosh(x)
102
+ cosh_base(num_class[x])
103
+ end
104
+
105
+ # Hyperbolic tangent
106
+ def tanh(x)
107
+ tanh_base(num_class[x])
108
+ end
109
+
110
+ # Hyperbolic arcsine
111
+ def asinh(x)
112
+ asinh_base(num_class[x])
113
+ end
114
+
115
+ # Hyperbolic arccosine
116
+ def acosh(x)
117
+ acosh_base(num_class[x])
118
+ end
119
+
120
+ # Hyperbolic arctangent
121
+ def atanh(x)
122
+ atanh_base(num_class[x])
123
+ end
124
+
125
+ protected
126
+
127
+ @pi_value = nil
128
+ @pi_digits = 0
129
+ @pi_cache = [Rational(3), Rational(3), 1, 0, 0, 24]
130
+ class <<self
131
+ attr_accessor :pi_value, :pi_digits, :pi_cache
132
+ end
133
+
134
+ def cos_base(x)
135
+ x = x.copy_sign(+1) # note that abs rounds; copy_sign does not.
136
+ rev_sign = false
137
+ s = nil
138
+ num_class.context(self) do |local_context|
139
+ local_context.precision += 3 # extra digits for intermediate steps
140
+ x,k,pi_2 = local_context.reduce_angle2(x,2)
141
+ rev_sign = true if k>1
142
+ if k % 2 == 0
143
+ x = pi_2 - x
144
+ else
145
+ rev_sign = !rev_sign
146
+ end
147
+ x = local_context.to_rad(x)
148
+ i, lasts, fact, num = 1, 0, 1, num_class[x]
149
+ s = num
150
+ x2 = -x*x
151
+ while s != lasts
152
+ lasts = s
153
+ i += 2
154
+ fact *= i * (i-1)
155
+ num *= x2
156
+ s += num / fact
157
+ end
158
+ end
159
+ return rev_sign ? minus(s) : plus(s)
160
+ end
161
+
162
+ def sin_base(x)
163
+ sign = x.sign
164
+ s = nil
165
+ num_class.context(self) do |local_context|
166
+ local_context.precision += 3 # extra digits for intermediate steps
167
+ x = x.copy_sign(+1) if sign<0
168
+ x,k,pi_2 = local_context.reduce_angle2(x,2)
169
+ sign = -sign if k>1
170
+ x = pi_2 - x if k % 2 == 1
171
+ x = local_context.to_rad(x)
172
+ i, lasts, fact, num = 1, 0, 1, num_class[x]
173
+ s = num
174
+ x2 = -x*x
175
+ while s != lasts
176
+ lasts = s
177
+ i += 2
178
+ fact *= i * (i-1)
179
+ num *= x2
180
+ s += num / fact
181
+ end
182
+ end
183
+ return plus(s).copy_sign(sign)
184
+ end
185
+
186
+ def tan_base(x)
187
+ plus(num_class.context(self) do |local_context|
188
+ local_context.precision += 2 # extra digits for intermediate steps
189
+ s,c = local_context.sin(x), local_context.cos(x)
190
+ s/c
191
+ end)
192
+ end
193
+
194
+ def atan_base(x)
195
+ s = nil
196
+ conversion = true
197
+ extra_prec = num_class.radix==2 ? 4 : 2
198
+ num_class.context(self) do |local_context|
199
+ local_context.precision += extra_prec
200
+ if x == 0
201
+ return num_class.zero
202
+ elsif x.abs > 1
203
+ if x.infinite?
204
+ s = local_context.quarter_cycle.copy_sign(x)
205
+ conversion = false
206
+ break
207
+ else
208
+ # c = (quarter_cycle).copy_sign(x)
209
+ c = (half*local_context.pi).copy_sign(x)
210
+ x = 1 / x
211
+ end
212
+ end
213
+ local_context.precision += extra_prec
214
+ x_squared = x ** 2
215
+ if x_squared.zero? || x_squared.subnormal?
216
+ s = x
217
+ s = c - s if c && c!=0
218
+ break
219
+ end
220
+ y = x_squared / (1 + x_squared)
221
+ y_over_x = y / x
222
+ i = num_class.zero; lasts = 0; s = y_over_x; coeff = 1; num = y_over_x
223
+ while s != lasts
224
+ lasts = s
225
+ i += 2
226
+ coeff *= i / (i + 1)
227
+ num *= y
228
+ s += coeff * num
229
+ end
230
+ if c && c!= 0
231
+ s = c - s
232
+ end
233
+ end
234
+ return conversion ? rad_to(s) : plus(s)
235
+ end
236
+
237
+ def atan2_base(y, x)
238
+ abs_y = y.abs
239
+ abs_x = x.abs
240
+ y_is_real = !x.infinite?
241
+
242
+ if x != 0
243
+ if y_is_real
244
+ a = y!=0 ? atan(y / x) : num_class.zero
245
+ a += half_cycle.copy_sign(y) if x < 0
246
+ return a
247
+ elsif abs_y == abs_x
248
+ one = num_class[1]
249
+ x = one.copy_sign(x)
250
+ y = one.copy_sign(y)
251
+ return half_cycle * (2 - x) / (4 * y)
252
+ end
253
+ end
254
+
255
+ if y != 0
256
+ return atan(num_class.infinity(y.sign))
257
+ elsif x < 0
258
+ return half_cycle.copy_sign(x)
259
+ else
260
+ return num_class.zero
261
+ end
262
+ end
263
+
264
+ def asin_base(x)
265
+ x = +x
266
+ return self.exception(Num::InvalidOperation, 'asin needs -1 <= x <= 1') if x.abs > 1
267
+
268
+ if x == -1
269
+ return -quarter_cycle
270
+ elsif x == 0
271
+ return num_class.zero
272
+ elsif x == 1
273
+ return quarter_cycle
274
+ end
275
+
276
+ num_class.context(self) do |local_context|
277
+ local_context.precision += 3
278
+ x = x/(1-x*x).sqrt
279
+ x = local_context.atan(x)
280
+ end
281
+ +x
282
+ end
283
+
284
+ def acos_base(x)
285
+
286
+ return self.exception(Num::InvalidOperation, 'acos needs -1 <= x <= 2') if x.abs > 1
287
+
288
+ if x == -1
289
+ return half_cycle
290
+ elsif x == 0
291
+ return quarter_cycle
292
+ elsif x == 1
293
+ return num_class.zero
294
+ end
295
+
296
+ required_precision = self.precision
297
+
298
+ if x < half
299
+ num_class.context(self, :precision=>required_precision+2) do
300
+ x = x/(1-x*x).sqrt
301
+ x = num_class.context.quarter_cycle - num_class.context.atan(x)
302
+ end
303
+ else
304
+ # valid for x>=0
305
+ num_class.context(self, :precision=>required_precision+3) do
306
+
307
+ # x = (1-x*x).sqrt # x*x may require double precision if x*x is near 1
308
+ x = (1-num_class.context(self, :precision=>required_precision*2){x*x}).sqrt
309
+
310
+ x = num_class.context.asin(x)
311
+ end
312
+ end
313
+ +x
314
+
315
+ end
316
+
317
+ def hypot_base(x, y)
318
+ +num_class.context(self) do |local_context|
319
+ local_context.precision += 3
320
+ (x*x + y*y).sqrt
321
+ end
322
+ end
323
+
324
+ def sinh_base(x)
325
+ sign = x.sign
326
+ s = nil
327
+ num_class.context(self) do |local_context|
328
+ local_context.precision += 3 # extra digits for intermediate steps
329
+ x = x.copy_sign(+1) if sign<0
330
+ if x > 1
331
+ s = half*(x.exp - (-x).exp)
332
+ else
333
+ i, lasts, fact, num = 1, 0, 1, num_class[x]
334
+ s = num
335
+ x2 = x*x
336
+ while s != lasts
337
+ lasts = s
338
+ i += 2
339
+ fact *= i * (i-1)
340
+ num *= x2
341
+ s += num / fact
342
+ end
343
+ end
344
+ end
345
+ return plus(s).copy_sign(sign)
346
+ end
347
+
348
+ def cosh_base(x)
349
+ s = nil
350
+ num_class.context(self) do |local_context|
351
+ local_context.precision += 3 # extra digits for intermediate steps
352
+ x = x.copy_sign(+1)
353
+ s = half*(x.exp + (-x).exp)
354
+ end
355
+ return plus(s)
356
+ end
357
+
358
+ def tanh_base(x)
359
+ s = nil
360
+ num_class.context(self) do |local_context|
361
+ local_context.precision += 3 # extra digits for intermediate steps
362
+ s = sinh_base(x)/cosh_base(x)
363
+ end
364
+ return plus(s)
365
+ end
366
+
367
+ def asinh_base(x)
368
+ sign = x.sign
369
+ x = x.copy_sign(+1)
370
+ s = nil
371
+
372
+ num_class.context(self) do |local_context|
373
+ x_squared = x ** 2
374
+ if x_squared.zero? || x_squared.subnormal?
375
+ s = x
376
+ else
377
+ # TODO: more accurate formula for small x: if x<...
378
+ if x.adjusted_exponent >= local_context.precision
379
+ s = local_context.ln(x+x)
380
+ else
381
+ s = local_context.ln(x + local_context.sqrt(x_squared + 1))
382
+ end
383
+ end
384
+ end
385
+ return plus(s).copy_sign(sign)
386
+ end
387
+
388
+ def acosh_base(x)
389
+
390
+ return self.exception(Num::InvalidOperation, 'acosh needs x >= 1') if x < 1
391
+
392
+ x = x.copy_sign(+1)
393
+ s = nil
394
+
395
+ num_class.context(self) do |local_context|
396
+ if x == 1
397
+ s = num_class.zero
398
+ else
399
+ if x.adjusted_exponent >= local_context.precision
400
+ s = x+x
401
+ else
402
+ s = x + local_context.sqrt((x+1)*(x-1))
403
+ end
404
+ s = local_context.ln(s)
405
+ end
406
+ end
407
+ return plus(s)
408
+ end
409
+
410
+ def atanh_base(x)
411
+ sign = x.sign
412
+ x = x.copy_sign(+1)
413
+ s = nil
414
+
415
+ return self.exception(Num::InvalidOperation, 'asinh needs -1 <= x <= 1') if x > 1
416
+
417
+ num_class.context(self) do |local_context|
418
+ if x.adjusted_exponent <= -local_context.precision
419
+ s = x
420
+ else
421
+ s = (1 + x) / (1 - x)
422
+ s = half*local_context.ln(s)
423
+ end
424
+ end
425
+ return plus(s).copy_sign(sign)
426
+ end
427
+
428
+ def pi2(decimals=nil)
429
+ num_class.context(self, :precision=>decimals) do |local_context|
430
+ local_context.pi*2
431
+ end
432
+ end
433
+
434
+ def invpi(decimals=nil)
435
+ num_class.context(self, :precision=>decimals) do |local_context|
436
+ num_class[1]/local_context.pi
437
+ end
438
+ end
439
+
440
+ def inv2pi(decimals=nil)
441
+ num_class.context(self, :precision=>decimals) do |local_context|
442
+ num_class.Num(1)/local_context.pi2
443
+ end
444
+ end
445
+
446
+ # class <<self
447
+ # private
448
+
449
+ def modtwopi(x)
450
+ return plus(num_class.context(self, :precision=>self.precision*3){x.modulo(one_cycle)})
451
+ end
452
+
453
+ # Reduce angle to [0,2Pi)
454
+ def reduce_angle(a)
455
+ modtwopi(a)
456
+ end
457
+
458
+ # Reduce angle to [0,Pi/k0) (result is not rounded to precision)
459
+ def reduce_angle2(a,k0=nil) # divisor of pi or nil for pi*2
460
+ # we could reduce first to pi*2 to avoid the mod k0 operation
461
+ k,r,divisor = num_class.context do
462
+ num_class.context.precision *= 3
463
+ m = k0.nil? ? one_cycle : half_cycle/k0
464
+ a.divmod(m)+[m]
465
+ end
466
+ [r, k.modulo(k0*2).to_i, divisor]
467
+ end
468
+
469
+ def one_cycle
470
+ case self.angle
471
+ when :rad
472
+ pi2
473
+ when :deg
474
+ num_class.Num(360)
475
+ when :grad
476
+ num_class.Num(400)
477
+ end
478
+ end
479
+
480
+ def half_cycle
481
+ case self.angle
482
+ when :rad
483
+ pi(num_class.context.precision)
484
+ when :deg
485
+ num_class.Num(180)
486
+ when :grad
487
+ num_class.Num(200)
488
+ end
489
+ end
490
+
491
+ def quarter_cycle
492
+ case self.angle
493
+ when :rad
494
+ half*pi(num_class.context.precision)
495
+ when :deg
496
+ num_class.Num(90)
497
+ when :grad
498
+ num_class.Num(100)
499
+ end
500
+ end
501
+
502
+ def to_rad(x)
503
+ case self.angle
504
+ when :rad
505
+ plus(x)
506
+ else
507
+ plus(num_class.context(self, :extra_precision=>3){|lc| x*lc.pi/half_cycle})
508
+ end
509
+ end
510
+
511
+ def to_deg(x)
512
+ case self.angle
513
+ when :deg
514
+ plus(x)
515
+ else
516
+ plus(num_class.context(self, :extra_precision=>3){x*num_class[180]/half_cycle})
517
+ end
518
+ end
519
+
520
+ def to_grad(x)
521
+ case self.angle
522
+ when :deg
523
+ plus(x)
524
+ else
525
+ plus(num_class.context(self, :extra_precision=>3){x*num_class[200]/half_cycle})
526
+ end
527
+ end
528
+
529
+ def to_angle(angular_units, x)
530
+ return plus(x) if angular_units == self.angle
531
+ case angular_units
532
+ when :rad
533
+ to_rad(x)
534
+ when :deg
535
+ to_deg(x)
536
+ when :grad
537
+ to_grad(x)
538
+ end
539
+ end
540
+
541
+ def rad_to(x)
542
+ case self.angle
543
+ when :rad
544
+ plus(x)
545
+ else
546
+ plus(num_class.context(self, :extra_precision=>3){|lc| x*half_cycle/lc.pi})
547
+ end
548
+ end
549
+
550
+ def deg_to(x)
551
+ case self.angle
552
+ when :deg
553
+ plus(x)
554
+ else
555
+ plus(num_class.context(self, :extra_precision=>3){x*half_cycle/num_class[180]})
556
+ end
557
+ end
558
+
559
+ def grad_to(x)
560
+ case self.angle
561
+ when :grad
562
+ plus(x)
563
+ else
564
+ plus(num_class.context(self, :extra_precision=>3){x*half_cycle/num_class[200]})
565
+ end
566
+ end
567
+
568
+ def angle_to(x, angular_units)
569
+ return plus(x) if angular_units == self.angle
570
+ case angular_units
571
+ when :rad
572
+ rad_to(x)
573
+ when :deg
574
+ deg_to(x)
575
+ when :grad
576
+ grad_to(x)
577
+ end
578
+ end
579
+
580
+ #end
581
+
582
+ module Support
583
+ module_function
584
+ def iarccot(x, unity)
585
+ xpow = unity / x
586
+ n = 1
587
+ sign = 1
588
+ sum = 0
589
+ loop do
590
+ term = xpow / n
591
+ break if term == 0
592
+ sum += sign * (xpow/n)
593
+ xpow /= x*x
594
+ n += 2
595
+ sign = -sign
596
+ end
597
+ sum
598
+ end
599
+ end
600
+
601
+ end # Trigonometry
602
+
603
+ Num::ContextBase.class_eval{include Trigonometry}
604
+
605
+ class DecNum
606
+
607
+ module Trigonometry
608
+
609
+ include Flt::Trigonometry::Support
610
+
611
+ # Pi
612
+ @pi_cache = nil # truncated pi digits as a string
613
+ @pi_cache_digits = 0
614
+ PI_MARGIN = 10
615
+ class <<self
616
+ attr_accessor :pi_cache, :pi_cache_digits
617
+ end
618
+
619
+ def pi(round_digits=nil)
620
+ round_digits ||= self.precision
621
+ digits = round_digits
622
+ if Trigonometry.pi_cache_digits <= digits # we need at least one more truncated digit
623
+ continue = true
624
+ while continue
625
+ margin = PI_MARGIN # margin to reduce recomputing with more digits to avoid ending in 0 or 5
626
+ digits += margin + 1
627
+ fudge = 10
628
+ unity = 10**(digits+fudge)
629
+ v = 4*(4*iarccot(5, unity) - iarccot(239, unity))
630
+ v = v.to_s[0,digits]
631
+ # if the last digit is 0 or 5 the truncated value may not be good for rounding
632
+ loop do
633
+ #last_digit = v%10
634
+ last_digit = v[-1,1].to_i
635
+ continue = (last_digit==5 || last_digit==0)
636
+ if continue && margin>0
637
+ # if we have margin we back-up one digit
638
+ margin -= 1
639
+ v = v[0...-1]
640
+ else
641
+ break
642
+ end
643
+ end
644
+ end
645
+ Trigonometry.pi_cache_digits = digits + margin - PI_MARGIN # @pi_cache.size
646
+ Trigonometry.pi_cache = v # DecNum(+1, v, 1-digits) # cache truncated value
647
+ end
648
+ # Now we avoid rounding too much because it is slow
649
+ l = round_digits + 1
650
+ while (l<Num[16]::Trigonometry.pi_cache_digits) && [0,5].include?(Trigonometry.pi_cache[l-1,1].to_i)
651
+ l += 1
652
+ end
653
+ v = Trigonometry.pi_cache[0,l]
654
+ num_class.context(self, :precision=>round_digits){+num_class.Num(+1,v.to_i,1-l)}
655
+ end
656
+
657
+ end # DecNum::Trigonometry
658
+
659
+ DecNum::Context.class_eval{include DecNum::Trigonometry}
660
+
661
+ end # DecNum
662
+
663
+ Num[16].class_eval do
664
+
665
+ module Num[16]::Trigonometry
666
+
667
+ extend Flt::Trigonometry::Support
668
+
669
+ # Pi
670
+ @pi_cache = nil # truncated pi digits as a string
671
+ @pi_cache_digits = 0
672
+ PI_MARGIN = 10
673
+ class <<self
674
+ attr_accessor :pi_cache, :pi_cache_digits
675
+ end
676
+
677
+ # truncated hex digits for rounding hexadecimally at round_digits
678
+ def self.pi_hex_digits(round_digits=nil)
679
+ round_digits ||= self.precision
680
+ digits = round_digits
681
+ if Num[16]::Trigonometry.pi_cache_digits <= digits # we need at least one more truncated digit
682
+ continue = true
683
+ while continue
684
+ margin = PI_MARGIN # margin to reduce recomputing with more digits to avoid ending in 0 or 5
685
+ digits += margin + 1
686
+ fudge = 16
687
+ unity = 16**(digits+fudge)
688
+ v = 4*(4*iarccot(5, unity) - iarccot(239, unity))
689
+ v = v.to_s(16)[0,digits]
690
+ # if the last digit is 0 or 8 the truncated value may not be good for rounding
691
+ loop do
692
+ #last_digit = v%16
693
+ last_digit = v[-1,1].to_i(16)
694
+ continue = (last_digit==8 || last_digit==0)
695
+ if continue && margin>0
696
+ # if we have margin we back-up one digit
697
+ margin -= 1
698
+ v = v[0...-1]
699
+ else
700
+ break
701
+ end
702
+ end
703
+ end
704
+ Num[16]::Trigonometry.pi_cache_digits = digits + margin - PI_MARGIN # @pi_cache.size
705
+ Num[16]::Trigonometry.pi_cache = v # DecNum(+1, v, 1-digits) # cache truncated value
706
+ end
707
+ # Now we avoid rounding too much because it is slow
708
+ l = round_digits + 1
709
+ while (l<Num[16]::Trigonometry.pi_cache_digits) && [0,8].include?(Num[16]::Trigonometry.pi_cache[l-1,1].to_i(16))
710
+ l += 1
711
+ end
712
+ Num[16]::Trigonometry.pi_cache[0,l]
713
+ end
714
+
715
+ def pi(round_digits=nil)
716
+ v = Num[16]::Trigonometry.pi_hex_digits(round_digits)
717
+ l = v.size
718
+ num_class.context(self, :precision=>round_digits){+num_class.Num(+1,v.to_i(16),1-l)}
719
+ end
720
+
721
+ end # Num[16]::Trigonometry
722
+
723
+ Num[16]::Context.class_eval{include Num[16]::Trigonometry}
724
+
725
+ end # Num[16]
726
+
727
+ class BinNum
728
+ module Trigonometry
729
+ def pi(round_digits=nil)
730
+ round_digits ||= self.precision
731
+ nhexd = (round_digits+3)/4 + 1
732
+ v = Num[16]::Trigonometry.pi_hex_digits(nhexd)
733
+ l = v.size
734
+ v = v.to_i(16)
735
+ e = (1-l)*4
736
+ # add trailing 01 for rounding (there always be some non null digit beyond the rounding point)
737
+ v <<= 2
738
+ v |= 1
739
+ e -= 2
740
+ num_class.context(self, :precision=>round_digits){+num_class.Num(+1,v,e)}
741
+ end
742
+ end
743
+ BinNum::Context.class_eval{include BinNum::Trigonometry}
744
+ end
745
+
746
+ end # Flt