ruby-calc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,214 @@
1
+ require "calc/version"
2
+ require "calc/calc"
3
+ require "calc/numeric"
4
+ require "calc/q"
5
+ require "calc/c"
6
+
7
+ module Calc
8
+ # builtins implemented as instance methods on Calc::Q or Calc::C
9
+ BUILTINS1 = %i(
10
+ abs acos acosh acot acoth acsc acsch agd appr arg asec asech asin asinh
11
+ atan atan2 atanh bernoulli bit bround btrunc catalan ceil cfappr cfsim char
12
+ cmp comb conj cos cosh cot coth csc csch den digit digits estr euler exp
13
+ fact factor fcnt fib floor frac frem gcd gcdrem gd highbit hypot ilog
14
+ ilog10 ilog2 im int inverse iroot iseven isimag isint ismult isodd isprime
15
+ isqrt isreal isrel issq jacobi lcm lcmfact lfactor ln log lowbit ltol meq
16
+ minv mmin mne mod near nextcand nextprime norm num perm pfact pix places
17
+ pmod popcnt power prevcand prevprime ptest quo quomod re root round scale
18
+ sec sech sgn sin sinh sqrt tan tanh trunc xor
19
+ ).freeze
20
+
21
+ # builtins implemented as module methods on Calc
22
+ BUILTINS2 = %i(
23
+ avg config freebernoulli freeeuler hean hnrmod max min pi polar ssq sum
24
+ version
25
+ ).freeze
26
+
27
+ ALL_BUILTINS = BUILTINS1 + BUILTINS2
28
+
29
+ # module versions of instance builtins; implemented by turning the first
30
+ # argument into the right class and calling the instance method
31
+ class << self
32
+ BUILTINS1.each do |f|
33
+ define_method f do |*args|
34
+ x = args.shift
35
+ if x.is_a?(Calc::Q) || x.is_a?(Calc::C)
36
+ x.__send__(f, *args)
37
+ elsif x.is_a?(Complex)
38
+ Calc::C(x).__send__(f, *args)
39
+ else
40
+ Calc::Q(x).__send__(f, *args)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.Q(*args) # rubocop:disable Style/MethodName
47
+ Q.new(*args)
48
+ end
49
+
50
+ def self.C(*args) # rubocop:disable Style/MethodName
51
+ C.new(*args)
52
+ end
53
+
54
+ # Average (arithmetic mean)
55
+ #
56
+ # Any number of numeric arguments can be provided. Returns the sum of all
57
+ # values divided by the number of values. If no values are provided, returns
58
+ # nil.
59
+ #
60
+ # @return [Calc::Q,Calc::C]
61
+ # @example
62
+ # Calc.avg(1, 2, 3) #=> Calc::Q(2)
63
+ # Calc.avg(4, Calc::C(2, 2)) #=> Calc::C(3+1i)
64
+ def self.avg(*args)
65
+ args.flatten!
66
+ return nil if args.none?
67
+ args.map { |n| to_calc_x(n) }.inject(:+) / args.size
68
+ end
69
+
70
+ # Harmonic mean
71
+ #
72
+ # Returns zero if any of the provded values is zero. Returns nil if no
73
+ # values are provided. Otherwise returns the harmonic mean of the given
74
+ # values.
75
+ #
76
+ # @return [Calc::Q,Calc::C]
77
+ # Calc.hmean(1, 2, 4) #=> Calc::Q(12/7)
78
+ # Calc.hmean(2, Complex(0, 2)) #=> Calc::C(2+2i)
79
+ def self.hmean(*args)
80
+ args.flatten!
81
+ return nil if args.none?
82
+ return Q::ZERO if args.detect(&:zero?)
83
+ args.size / args.map { |n| to_calc_x(n) }.map(&:inverse).inject(:+)
84
+ end
85
+
86
+ # Maximum from provided values.
87
+ #
88
+ # Each argument must be convertable to Calc::Q. If no values, returns nil.
89
+ #
90
+ # @return [Calc::Q]
91
+ # @example
92
+ # Calc.max(5, 3, 7, 2, 9) #=> Calc::Q(9)
93
+ def self.max(*args)
94
+ args.compact.map { |n| Calc::Q(n) }.max
95
+ end
96
+
97
+ # Minimum from provided values
98
+ #
99
+ # Each argument must be convertable to Calc::Q. If no values, returns nil.
100
+ #
101
+ # @return [Calc::Q]
102
+ # @example
103
+ # Calc.min(5, 3, 7, 2, 9) #=> Calc::Q(2)
104
+ def self.min(*args)
105
+ args.compact.map { |n| Calc::Q(n) }.min
106
+ end
107
+
108
+ # Evaluate a polynomial
109
+ #
110
+ # First case:
111
+ # poly(a_0, a_1, ..., a_n, x)
112
+ # returns:
113
+ # a_n + (a_n-1 + ... + (a_1 + a_0 * x) * x ..) * x
114
+ # In particular:
115
+ # poly(a, x) -> a
116
+ # poly(a, b, x) -> b + a * x
117
+ # poly(a, b, c, x) -> c + (b + a * x) * x
118
+ # or a*x**2 + b*x + c
119
+ #
120
+ # In the second case, the first parameter is an array of coefficients, ie:
121
+ # poly([a_0, a_1, ... a_n], x)
122
+ # returns:
123
+ # a_0 + (a_n-1 + (a_2 + ... a_n * x) * x)
124
+ # Note that the order of coeffecients is reverse of the first case.
125
+ #
126
+ # If one or more elements of clist is another array, and there is more than
127
+ # one argument (x, y, ...) the coefficient corresponding to such an element
128
+ # is the value of the poly for that list and the next argument in x, y, ...
129
+ # For example:
130
+ # poly([[a, b, c], [d, e], f], x, y)
131
+ # Returns:
132
+ # (a + b * y + c * y^2) + (d + e * y) * x + f * x^2
133
+ #
134
+ # For more explanation and examples on how the nested arrays works, see
135
+ # "help poly" bearning in mind that a calc list is equivament to a ruby
136
+ # array.
137
+ #
138
+ # @return [Calc::Numeric]
139
+ # @example
140
+ # # 2 * 7**2 + 3 * 7 + 5
141
+ # Calc.poly(2, 3, 5, 7) #=> Calc::Q(124)
142
+ def self.poly(*args)
143
+ raise ArgumentError, "Need at least one argument for poly" if args.none?
144
+ if args.first.respond_to?(:each)
145
+ # second case
146
+ clist = args.shift
147
+ evalpoly(clist, args.flatten, 0)
148
+ else
149
+ # first case
150
+ x = to_calc_x(args.pop)
151
+ return x if args.none?
152
+ args.reverse.each_with_index.map { |coeff, i| to_calc_x(coeff) * x**i }.reduce(:+)
153
+ end
154
+ end
155
+
156
+ # evalpoly and evp are modelled on functions of the same name in libcalc,
157
+ # which we can't use because they use VALUE and LIST types. because the
158
+ # libcalc versions use doubly linked lists, the ruby versions has to pass
159
+ # around an index to the coeffecients array instead.
160
+ def self.evalpoly(clist, lp, x)
161
+ return nil if clist.none?
162
+ if lp[x].nil?
163
+ if clist.first.respond_to?(:each)
164
+ evalpoly(clist.first, lp, x + 1)
165
+ else
166
+ to_calc_x(clist.first)
167
+ end
168
+ else
169
+ evp(clist, lp, x)
170
+ end
171
+ end
172
+ private_class_method :evalpoly
173
+
174
+ def self.evp(clist, lp, x)
175
+ clist.reverse.reduce(Q::ZERO) do |vres, v|
176
+ (vres * lp[x]) + if v.respond_to?(:each)
177
+ evalpoly(v, lp, x + 1) || Q::ZERO
178
+ else
179
+ to_calc_x(v)
180
+ end
181
+ end
182
+ end
183
+ private_class_method :evp
184
+
185
+ # Returns the sum of squares.
186
+ #
187
+ # Nil values are ignored. If any argument is am array, it contributes
188
+ # the sum of squares of its contents recursively.
189
+ #
190
+ # @return [Calc::C,Calc::Q]
191
+ # @raise [ArgumentError] if any argument can't be converted to a Calc class
192
+ # @example
193
+ # Calc.ssq(1, 2, 3) #=> Calc::Q(14)
194
+ # Calc.ssq(1+2i, 3-4i, 5) #=> Calc::C(15-20i)
195
+ def self.ssq(*args)
196
+ args.flatten.map { |term| to_calc_x(term)**2 }.inject(:+)
197
+ end
198
+
199
+ def self.sum(*args)
200
+ args.flatten.map { |t| to_calc_x(t) }.compact.inject(:+)
201
+ end
202
+
203
+ # returns a Calc::Q or Calc::C object, converting if necessary
204
+ def self.to_calc_x(n)
205
+ if n.is_a?(Calc::Q) || n.is_a?(Calc::C)
206
+ n
207
+ elsif n.is_a?(Complex)
208
+ Calc::C(n)
209
+ else
210
+ Calc::Q(n)
211
+ end
212
+ end
213
+ private_class_method :to_calc_x
214
+ end
@@ -0,0 +1,371 @@
1
+ module Calc
2
+ class C
3
+ # Returns the absolute value of a complex number. For purely real or
4
+ # purely imaginary values, returns the absolute value of the non-zero
5
+ # part. Otherwise returns the absolute part of its complex form within
6
+ # the specified accuracy.
7
+ #
8
+ # @param eps [Calc::Q] (optional) calculation accuracy
9
+ # @return [Calc::Q]
10
+ # @example
11
+ # Calc::C(-1).abs #=> Calc::Q(1)
12
+ # Calc::C(3,-4).abs #=> Calc::Q(5)
13
+ # Calc::C(4,5).abs("1e-5") #=> Calc::Q(6.40312)
14
+ def abs(*args)
15
+ # see absvalue() in value.c
16
+ re.hypot(im, *args)
17
+ end
18
+ alias magnitude abs
19
+
20
+ # Approximate numbers of multiples of a specific number.
21
+ #
22
+ # c.appr(y,z) is equivalent to c.re.appr(y,z) + c.im.appr(y,z) * Calc::C(0,1)
23
+ def appr(*args)
24
+ q1 = re.appr(*args)
25
+ q2 = im.appr(*args)
26
+ if q2.zero?
27
+ q1
28
+ else
29
+ C.new(q1, q2)
30
+ end
31
+ end
32
+
33
+ # Returns the argument (the angle or phase) of a complex number in radians.
34
+ #
35
+ # @param eps [Calc::Q] (optional) calculation accuracy
36
+ # @return [Calc::Q]
37
+ # @example
38
+ # Calc::C(1,0).arg #=> 0
39
+ # Calc::C(-1,0).arg #=> -pi
40
+ # Calc::C(1,1).arg #=> Calc::Q(0.78539816339744830962)
41
+ def arg(*args)
42
+ # see f_arg() in func.c
43
+ im.atan2(re, *args)
44
+ end
45
+ alias angle arg
46
+ alias phase arg
47
+
48
+ # Round real and imaginary parts to the specified number of binary digits
49
+ #
50
+ # @return [Calc::C,Calc::Q]
51
+ # @param places [Integer] number of binary places to round to (default 0)
52
+ # @param rns [Integer] rounding flags (default Calc.config(:round))
53
+ # @example
54
+ # Calc::C("7/32","-7/32").bround(3) #=> Calc::C(0.25-0.25i)
55
+ def bround(*args)
56
+ q1 = re.bround(*args)
57
+ q2 = im.bround(*args)
58
+ if q2.zero?
59
+ q1
60
+ else
61
+ C.new(q1, q2)
62
+ end
63
+ end
64
+
65
+ # Complex conjugate
66
+ #
67
+ # Returns the complex conjugate of self (same real part and same imaginary
68
+ # part but with opposite sign)
69
+ #
70
+ # @return [Calc::C]
71
+ # @example
72
+ # Calc::C(3,3).conj #=> Calc::C(3-3i)
73
+ def conj
74
+ C.new(re, -im)
75
+ end
76
+ alias conjugate conj
77
+
78
+ # Trigonometric cotangent
79
+ #
80
+ # @param eps [Calc::Q] (optional) calculation accuracy
81
+ # @return [Calc::C]
82
+ # @example
83
+ # Calc::C(2,3).cot #=> Calc::C(~-0.00373971037633695666-~0.99675779656935831046i)
84
+ def cot(*args)
85
+ # see f_cot() in func.c
86
+ cos(*args) / sin(*args)
87
+ end
88
+
89
+ # Hyperbolic cotangent
90
+ #
91
+ # @param eps [Calc::Q] (optional) calculation accuracy
92
+ # @return [Calc::C]
93
+ # @example
94
+ # Calc::C(2,3).coth #=> Calc::C(~1.03574663776499539611+~0.01060478347033710175i)
95
+ def coth(*args)
96
+ # see f_coth() in func.c
97
+ cosh(*args) / sinh(*args)
98
+ end
99
+
100
+ # Trigonometric cosecant
101
+ #
102
+ # @param eps [Calc::Q] (optional) calculation accuracy
103
+ # @return [Calc::C]
104
+ # @example
105
+ # Calc::C(2,3).csc #=> Calc::C(~0.09047320975320743981+~0.04120098628857412646i)
106
+ def csc(*args)
107
+ # see f_csc() in func.c
108
+ sin(*args).inverse
109
+ end
110
+
111
+ # Hyperbolic cosecant
112
+ #
113
+ # @param eps [Calc::Q] (optional) calculation accuracy
114
+ # @return [Calc::C]
115
+ # @example
116
+ # Calc::C(2,3).csch #=> Calc::C(~-0.27254866146294019951-~0.04030057885689152187i)
117
+ def csch(*args)
118
+ # see f_csch() in func.c
119
+ sinh(*args).inverse
120
+ end
121
+
122
+ # Denominator of a complex number
123
+ #
124
+ # The denominator is the lowest common denominator of the real and
125
+ # imaginary parts
126
+ #
127
+ # @return [Calc::Q]
128
+ # @example
129
+ # Calc::C("1/2", "2/3").denominator #=> Calc::Q(6)
130
+ def denominator
131
+ re.den.lcm(im.den)
132
+ end
133
+
134
+ # Returns a string which if evaluated creates a new object with the original value
135
+ #
136
+ # @return [String]
137
+ # @example
138
+ # Calc::C(0.5,-2).estr #=> "Calc::C(Calc::Q(1,2),-2)"
139
+ def estr
140
+ s = self.class.name
141
+ s << "("
142
+ s << (re.int? ? re.to_s : re.estr)
143
+ s << "," + (im.int? ? im.to_s : im.estr) unless im.zero?
144
+ s << ")"
145
+ s
146
+ end
147
+
148
+ # Returns true if self has integer real part and zero imaginary part
149
+ #
150
+ # @return [Boolean]
151
+ # @example
152
+ # Calc::C(1,1).int? #=> false
153
+ # Calc::C(1,0).int? #=> true
154
+ def int?
155
+ im.zero? ? re.int? : false
156
+ end
157
+
158
+ alias imaginary im
159
+
160
+ def iseven
161
+ even? ? Q::ONE : Q::ZERO
162
+ end
163
+
164
+ # Returns 1 if the number is imaginary (zero real part and non-zero
165
+ # imaginary part) otherwise returns 0. See also [imag?].
166
+ #
167
+ # @return [Calc::Q]
168
+ # @example
169
+ # Calc::C(0,1).isimag #=> Calc::Q(1)
170
+ # Calc::C(1,1).isimag #=> Calc::Q(0)
171
+ def isimag
172
+ imag? ? Q::ONE : Q::ZERO
173
+ end
174
+
175
+ def isodd
176
+ odd? ? Q::ONE : Q::ZERO
177
+ end
178
+
179
+ # Returns 1 if the number has zero imaginary part, otherwise returns 0.
180
+ # See also [real?].
181
+ #
182
+ # @return [Calc::Q]
183
+ # @example
184
+ # Calc::C(1,1).isreal #=> Calc::Q(0)
185
+ # Calc::C(1,0).isreal #=> Calc::Q(1)
186
+ def isreal
187
+ real? ? Q::ONE : Q::ZERO
188
+ end
189
+
190
+ # Computes the remainder for an integer quotient
191
+ #
192
+ # Result is equivalent to applying the mod function separately to the real
193
+ # and imaginary parts.
194
+ #
195
+ # @param y [Integer]
196
+ # @param r [Integer] (optional) rounding mode (see "help mod")
197
+ # @return [Calc::C]
198
+ # @example
199
+ # Calc::C(0, 11).mod(5) #=> Calc::C(1i)
200
+ def mod(*args)
201
+ q1 = re.mod(*args)
202
+ q2 = im.mod(*args)
203
+ if q2.zero?
204
+ q1
205
+ else
206
+ Calc::C(q1, q2)
207
+ end
208
+ end
209
+
210
+ # Numerator of a complex number
211
+ #
212
+ # @return [Calc::C]
213
+ # @example
214
+ # Calc::C("1/2", "2/3").numerator #=> Calc::C(3+4i)
215
+ def numerator
216
+ C.new(re.num * denominator / re.den, im.num * denominator / im.den)
217
+ end
218
+
219
+ # Returns the real part of the value as a rational
220
+ #
221
+ # If this number is non-imaginary, equivalent to re.rationalize(eps).
222
+ #
223
+ # Note that this method exists for ruby Numeric compatibility. Libcalc has
224
+ # an alternative approximation method with different semantics, see `appr`.
225
+ #
226
+ # @param eps [Float,Rational]
227
+ # @return [Calc::Q]
228
+ # @example
229
+ # Calc::C("1/3", 0).rationalize #=> Calc::Q(1/3)
230
+ # Calc::C(1, 2).rationalize # RangeError
231
+ def rationalize(eps = nil)
232
+ raise RangeError, "Can't convert #{ self } into Rational" if im.nonzero?
233
+ re.rationalize(eps)
234
+ end
235
+
236
+ # Round real and imaginary parts to the specified number of decimal digits
237
+ #
238
+ # @return [Calc::C,Calc::Q]
239
+ # @param places [Integer] number of decimal places to round to (default 0)
240
+ # @param rns [Integer] rounding flags (default Calc.config(:round))
241
+ # Calc::C("7/32","-7/32").round(3) #=> Calc::C(0.218-0.219i)
242
+ def round(*args)
243
+ q1 = re.round(*args)
244
+ q2 = im.round(*args)
245
+ if q2.zero?
246
+ q1
247
+ else
248
+ C.new(q1, q2)
249
+ end
250
+ end
251
+
252
+ # Trigonometric secant
253
+ #
254
+ # @param eps [Calc::Q] (optional) calculation accuracy
255
+ # @return [Calc::C]
256
+ # @example
257
+ # Calc::C(2,3).sec #=> Calc::C(~-0.04167496441114427005+~0.09061113719623759653i)
258
+ def sec(*args)
259
+ # see f_sec() in func.c
260
+ cos(*args).inverse
261
+ end
262
+
263
+ # Hyperbolic secant
264
+ #
265
+ # @param eps [Calc::Q] (optional) calculation accuracy
266
+ # @return [Calc::C]
267
+ # @example
268
+ # Calc::C(2,3).sech #=> Calc::C(~-0.26351297515838930964-~0.03621163655876852087i)
269
+ def sech(*args)
270
+ # see f_sech() in func.c
271
+ cosh(*args).inverse
272
+ end
273
+
274
+ # Trigonometric tangent
275
+ #
276
+ # @param eps [Calc::Q] (optional) calculation accuracy
277
+ # @return [Calc::C]
278
+ # @example
279
+ # Calc::C(1,2).tan #=> Calc::C(~-0.00376402564150424829+~1.00323862735360980145i)
280
+ def tan(*args)
281
+ # see f_tan() in func.c
282
+ sin(*args) / cos(*args)
283
+ end
284
+
285
+ # Hyperbolic tangent
286
+ #
287
+ # @param eps [Calc::Q] (optional) calculation accuracy
288
+ # @return [Calc::C]
289
+ # @example
290
+ # Calc::C(1,2).tanh #=> Calc::C(~0.96538587902213312428-~0.00988437503832249372i)
291
+ def tanh(*args)
292
+ # see f_tanh() in func.c
293
+ sinh(*args) / cosh(*args)
294
+ end
295
+
296
+ # Converts a Calc::C object into a ruby Complex object
297
+ #
298
+ # @return [Complex]
299
+ # @example
300
+ # Calc::C(2, 3).to_c #=> ((2/1)+(3/1)*i)
301
+ def to_c
302
+ Complex(re.to_r, im.to_r)
303
+ end
304
+
305
+ # Convert a complex number with zero imaginary part into a ruby Float
306
+ #
307
+ # @return [Float]
308
+ # @raise [RangeError] if imaginary part is non-zero
309
+ # @example
310
+ # Calc::C("2/3", 0).to_f #=> 0.6666666666666666
311
+ def to_f
312
+ raise RangeError, "can't convert #{ self } into Float" if im.nonzero?
313
+ re.to_f
314
+ end
315
+
316
+ # Convert a wholly real number to an integer.
317
+ #
318
+ # Note that the return value is a ruby Fixnum or Bignum. If you want to
319
+ # convert to an integer but have the result be a `Calc::Q` object, use
320
+ # `trunc` or `round`.
321
+ #
322
+ # @return [Fixnum,Bugnum]
323
+ # @raise [RangeError] if imaginary part is non-zero
324
+ # @example
325
+ # Calc::C(2, 0).to_i #=> 2
326
+ # Calc::C(2, 2).to_i # RangeError
327
+ def to_i
328
+ raise RangeError, "can't convert #{ self } into Integer" if im.nonzero?
329
+ re.to_i
330
+ end
331
+
332
+ # Convert a complex number with zero imaginary part into a ruby Rational
333
+ #
334
+ # @return [Rational]
335
+ # @raise [RangeError] if imaginary part is non-zero
336
+ # @example
337
+ # Calc::C("2/3", 0).to_r #=> (2/3)
338
+ def to_r
339
+ raise RangeError, "can't convert #{ self } into Rational" if im.nonzero?
340
+ re.to_r
341
+ end
342
+
343
+ def to_s(*args)
344
+ if im.zero?
345
+ re.to_s(*args)
346
+ elsif re.zero?
347
+ imag_part(im, *args)
348
+ elsif im > 0
349
+ re.to_s(*args) + "+" + imag_part(im, *args)
350
+ else
351
+ re.to_s(*args) + "-" + imag_part(im.abs, *args)
352
+ end
353
+ end
354
+
355
+ def inspect
356
+ "Calc::C(#{ self })"
357
+ end
358
+
359
+ # aliases for compatibility with ruby Complex
360
+ alias integer? int?
361
+
362
+ private
363
+
364
+ # for formatting imaginary parts; if a fraction, put the "i" after the
365
+ # denominator (eg 2i/3). otherwise it goes at the end (eg 0.5i).
366
+ def imag_part(number, *args)
367
+ string = number.to_s(*args)
368
+ string.insert(string.index("/") || -1, "i")
369
+ end
370
+ end
371
+ end