ruby-calc 0.1.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.
@@ -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