fibonaccia 1.0.2

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,573 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @internal_comment
3
+ #
4
+ # Copyright © 2015 Ken Coar
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ # :nocov:
20
+ if (RUBY_VERSION =~ %r!\b1.9\b!)
21
+ Encoding.default_external = Encoding::UTF_8
22
+ Encoding.default_internal = Encoding::UTF_8
23
+ end
24
+ # :nocov:
25
+
26
+ require('rubygems')
27
+ require('bundler')
28
+ Bundler.setup
29
+
30
+ require('fibonaccia/module-doc')
31
+ require('fibonaccia/version')
32
+ require('fibonaccia/exceptions')
33
+ require('bigdecimal')
34
+ require('bigdecimal/math')
35
+
36
+ module Fibonaccia
37
+
38
+ include BigMath
39
+
40
+ # @api private
41
+ #
42
+ # The number of digits of precision we want for our <tt>BigDecimal</tt> operations.
43
+ #
44
+ BDPrecision = BigDecimal::double_fig
45
+
46
+ unless (Fibonaccia.const_defined?('PHI_Float'))
47
+ # @api private
48
+ #
49
+ # Phi (φ), the golden ratio. φ can be simply expressed by a
50
+ # formula, but it's an irrational number, meaning that the default
51
+ # precision is implementation-specific.
52
+ #
53
+ # Provide a <tt>Float</tt> value which uses the default precision.
54
+ #
55
+ # @note
56
+ # Use <tt>Fibonaccia.PHI</tt> or <tt>Fibonaccia.PHI(false)</tt>
57
+ # to access this value.
58
+ #
59
+ # @see PHI
60
+ #
61
+ PHI_Float = (1.0 + Math.sqrt(5)) / 2.0
62
+
63
+ # @!macro PHIconst
64
+ #
65
+ # Default value of φ as a <tt>Float</tt>.
66
+ #
67
+ # Referencing <tt>PHI</tt> as a constant
68
+ # (<tt>Fibonaccia::PHI</tt>) is equivalent to:
69
+ #
70
+ # Fibonaccia.PHI(false)
71
+ #
72
+ # Use {Fibonaccia.PHI}<tt>(true)</tt> to obtain the
73
+ # <tt>BigDecimal</tt> representation.
74
+ #
75
+
76
+ # @macro PHIconst
77
+ PHI = PHI_Float
78
+
79
+ # @api private
80
+ #
81
+ # Provide a value for φ using an arbitrarily large precision.
82
+ #
83
+ # @note
84
+ # Use <tt>Fibonaccia.PHI(true)</tt> to access this value.
85
+ #
86
+ # @see PHI
87
+ #
88
+ PHI_BigDecimal = (1.0 + BigDecimal.new(5).sqrt(BDPrecision)) / 2.0
89
+ end
90
+
91
+ #
92
+ # This bit of jiggery-pokery exists because we don't want to expose
93
+ # the *real* definition of PHI (which is PHI_Float) in the
94
+ # documentation. So we wrap a fake one in a never-successful
95
+ # conditional, and Yard uses that.
96
+ #
97
+ # :nocov:
98
+ if (false)
99
+ # @macro PHIconst
100
+ PHI = calculated_constant
101
+ end
102
+ # :nocov:
103
+
104
+ unless (::Fibonaccia.const_defined?('B_Float'))
105
+ # @!macro SpiralFactors
106
+ # Constant used to construct a Golden Spiral. See the
107
+ # {https://en.wikipedia.org/wiki/Golden_spiral Wikipedia article}
108
+ # for details of its definition and use.
109
+
110
+ # @api private
111
+ #
112
+ # @macro SpiralFactors
113
+ #
114
+ # This constant is a <tt>Float</tt> value. For greater precision,
115
+ # use {B_BigDecimal}.
116
+ #
117
+ B_Float = (2.0 * Math.log(PHI_Float)) / Math::PI
118
+
119
+ # @api private
120
+ #
121
+ # @macro SpiralFactors
122
+ #
123
+ # This constant is a <tt>BigDecimal</tt> value. If you don't need
124
+ # arbitrarily great precision, use {B_BigDecimal} instead.
125
+ #
126
+ B_BigDecimal = ((2.0 * BigMath.log(PHI_BigDecimal, BDPrecision)) / BigMath.PI(BDPrecision))
127
+
128
+ # (see B_Float)
129
+ C_Float = Math.exp(B_Float)
130
+
131
+ # (see B_BigDecimal)
132
+ C_BigDecimal = BigMath.exp(B_BigDecimal, BDPrecision)
133
+ end
134
+
135
+ #
136
+ # Since this module is *not* intended for use as a mix-in, all the
137
+ # methods and 'private' data are kept in the eigenclass.
138
+ #
139
+ class << self
140
+
141
+ include BigMath
142
+
143
+ unless (const_defined?('SERIES'))
144
+ #
145
+ # First three terms of the Fibonacci sequence, which is our seed
146
+ # and our minimum internal series.
147
+ #
148
+ SEED = [ 0, 1, 1 ].freeze
149
+
150
+ # @api private
151
+ #
152
+ # Minimum number of terms in the series -- the seed values.
153
+ #
154
+ MIN_TERMS = SEED.count
155
+
156
+ # @api private
157
+ #
158
+ # Array of Fibonacci numbers to however many terms have been
159
+ # evolved. Defined as a constant because the underlying array
160
+ # structure is mutable even for constants, and it's pre-seeded
161
+ # with the first three terms.
162
+ #
163
+ SERIES = SEED.dup
164
+ end
165
+
166
+ include Enumerable
167
+
168
+ # @private
169
+ #
170
+ # Method invoked when a scope does an <b><tt>include Fibonaccia</tt></b>. We
171
+ # simply emit a warning message and do nothing else.
172
+ #
173
+ # @param [Module,Class] klass
174
+ # Object in whose scope the <tt>include</tt> appeared.
175
+ #
176
+ # @return [void]
177
+ #
178
+ def included(klass)
179
+ warn("#warning: #{self.name} " +
180
+ 'is not intended for use as a mix-in, but it does no harm')
181
+ return nil
182
+ end # def included
183
+
184
+ #
185
+ # Constant Phi (φ), the golden ratio.
186
+ #
187
+ # φ can be simply expressed by a formula, but it's an irrational
188
+ # number, meaning that the default precision is
189
+ # implementation-specific. {PHI} allows you to access the value
190
+ # either at the implementation precision, or the
191
+ # <tt>BigDecimal</tt> extended precision.
192
+ #
193
+ # @!macro PHIdoc
194
+ # @param [Boolean] extended
195
+ #
196
+ # @return [Float]
197
+ # when <tt>extended</tt> is false (or at least not a true value).
198
+ # @return [BigDecimal]
199
+ # when <tt>extended</tt> is true.
200
+ #
201
+ # @example Using conventional 'constant' semantics
202
+ # irb> Fibonaccia::PHI
203
+ # => 1.618033988749895
204
+ # irb> Fibonaccia::PHI(false)
205
+ # => 1.618033988749895
206
+ # irb> Fibonaccia::PHI(true)
207
+ # => #<BigDecimal:198e990,'0.1618033988 7498948482 0458683433 33333335E1',54(72)>
208
+ #
209
+ # @example Using module method semantics
210
+ # irb> Fibonaccia.PHI
211
+ # => 1.618033988749895
212
+ # irb> Fibonaccia.PHI(false)
213
+ # => 1.618033988749895
214
+ # irb> Fibonaccia.PHI(true)
215
+ # => #<BigDecimal:198e990,'0.1618033988 7498948482 0458683433 33333335E1',54(72)>
216
+ #
217
+ # @macro PHIdoc
218
+ #
219
+ def PHI(extended=false)
220
+ result = (extended \
221
+ ? PHI_BigDecimal \
222
+ : PHI_Float)
223
+ return result
224
+ end # def PHI
225
+
226
+ # @internal_comment
227
+ #
228
+ # I can't get Yard to process the phi as a method name, so it's
229
+ # just undocumented. Meh.
230
+ #
231
+
232
+ # @api private
233
+ #
234
+ # Alias name (probably not universally useable) for the {PHI} method.
235
+ #
236
+ # @macro PHIdoc
237
+ #
238
+ define_method(:'φ', self.instance_method(:PHI))
239
+
240
+ # @api private
241
+ #
242
+ # We return a <i>copy</i> so our actual array can't be accidentally
243
+ # polluted by whatever the caller may do to what we give it.
244
+ #
245
+
246
+ #
247
+ # Copy of the internal series.
248
+ #
249
+ # Return a <tt>dup</tt> of the internal series, to however many
250
+ # terms it has grown.
251
+ #
252
+ # @note
253
+ # Since this is a duplicate of the module-internal array, it can
254
+ # have a significant impact on memory usage if the series has
255
+ # been extended to any great length.
256
+ #
257
+ # @see reset
258
+ # @see shrink
259
+ # @see terms=
260
+ #
261
+ # @example
262
+ # irb> require('fibonaccia')
263
+ # irb> Fibonaccia.series
264
+ # => [0, 1, 1]
265
+ # irb> Fibonaccia[10]
266
+ # => 55
267
+ # irb> Fibonaccia.series
268
+ # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
269
+ #
270
+ # @return [Array<Integer>]
271
+ # Returns the list of Fibonacci numbers as far as they've been
272
+ # calculated by the module.
273
+ #
274
+ def series
275
+ return SERIES.dup
276
+ end # def series
277
+
278
+ # @api private
279
+ #
280
+ # This method is called to extend the {SERIES} array if necessary.
281
+ #
282
+ # @param [Integer] nterms
283
+ # If the value of this parameter is greater than the number of
284
+ # terms in the {SERIES} array, new terms are calculated until
285
+ # the series is long enough.
286
+ #
287
+ # @return [void]
288
+ #
289
+ def extend_series(nterms)
290
+ nterms = [ 0, nterms.to_i ].max
291
+ n = [ 0, nterms - self.terms ].max
292
+ n.times do
293
+ SERIES << (SERIES[-2] + SERIES[-1])
294
+ end
295
+ return nil
296
+ end # def extend_series
297
+ protected(:extend_series)
298
+
299
+ #
300
+ # Extend the internal series by the specified number of terms.
301
+ #
302
+ # @param [Integer] nterms
303
+ # Number of terms by which to grow the internal series.
304
+ # @return [Integer]
305
+ # the number of terms in the series after the operation.
306
+ #
307
+ # @raise [Fibonaccia::NotPositiveInteger]
308
+ # if the argument isn't an integer greater than or equal to zero.
309
+ #
310
+ def grow(nterms)
311
+ unless (nterms.kind_of?(Integer) && (nterms >= 0))
312
+ msg = 'argument must be a non-negative integer'
313
+ raise(Fibonaccia::NotPositiveInteger, msg)
314
+ end
315
+ self.extend_series(self.terms + nterms)
316
+ return self.terms
317
+ end # def grow
318
+
319
+ #
320
+ # Shrink the internal series by the specified number of terms.
321
+ #
322
+ # @!macro minsize
323
+ # @note
324
+ # The series <b><i>cannot</i></b> be shrunk to fewer than the
325
+ # {SEED} elements.
326
+ #
327
+ # @macro minsize
328
+ # @param [Integer] nterms
329
+ # Number of terms by which to shrink the internal series.
330
+ # @return [Integer]
331
+ # the number of terms in the series after the operation.
332
+ #
333
+ # @raise [Fibonaccia::NotPositiveInteger]
334
+ # if the argument isn't an integer greater than or equal to zero.
335
+ #
336
+ def shrink(nterms)
337
+ unless (nterms.kind_of?(Integer) && (nterms >= 0))
338
+ msg = 'argument must be a non-negative integer'
339
+ raise(Fibonaccia::NotPositiveInteger, msg)
340
+ end
341
+ nterms = [ MIN_TERMS, self.terms - nterms ].max
342
+ SERIES.replace(SERIES.take(nterms))
343
+ return self.terms
344
+ end # def shrink
345
+
346
+ #
347
+ # The number of terms in the internal series.
348
+ #
349
+ # Similar to the #count method provided by the <tt>Enumerable</tt>
350
+ # mix-in, but a more direct approach -- and complementary to
351
+ # the {terms=} method.
352
+ #
353
+ # @return [Integer]
354
+ # number of terms in the internal series.
355
+ #
356
+ def terms
357
+ result = SERIES.count
358
+ return result
359
+ end # def terms
360
+
361
+ #
362
+ # Set the internal series to a specific number of terms.
363
+ #
364
+ # @macro minsize
365
+ # @param [Integer] nterms
366
+ # Number of terms to which the series should be grown or shrunk.
367
+ # @return [Integer]
368
+ # the number of terms in the series after the operation.
369
+ #
370
+ # @raise [Fibonaccia::NotPositiveInteger]
371
+ # if the argument isn't an integer greater than or equal to zero.
372
+ #
373
+ def terms=(nterms)
374
+ unless (nterms.kind_of?(Integer) && (nterms >= 0))
375
+ msg = 'argument must be a non-negative integer'
376
+ raise(Fibonaccia::NotPositiveInteger, msg)
377
+ end
378
+ nterms = [ MIN_TERMS, nterms ].max
379
+ if (nterms > self.terms)
380
+ self.grow(nterms - self.terms)
381
+ elsif (nterms < self.terms)
382
+ self.shrink(self.terms - nterms)
383
+ end
384
+ return self.terms
385
+ end # def terms=
386
+
387
+ #
388
+ # Reset the internal series to just the seed value.
389
+ #
390
+ # This can be used to free up memory.
391
+ #
392
+ # @return [void]
393
+ #
394
+ def reset
395
+ SERIES.replace(SEED)
396
+ return nil
397
+ end # def reset
398
+
399
+ #
400
+ # Iterate over the current internal series, yielding each value in
401
+ # turn.
402
+ #
403
+ # @yield [Integer]
404
+ # Each element of the internal series is yielded in turn.
405
+ #
406
+ # @return [Array<Integer>]
407
+ # if a block has been passed.
408
+ # @return [Enumerator]
409
+ # when invoked without a block.
410
+ #
411
+ def each(&block)
412
+ result = SERIES.each(&block)
413
+ return result
414
+ end # each
415
+
416
+ # @internal_comment
417
+ #
418
+ # The following method is *not* part of <tt>Enumerable</tt>, so we
419
+ # add it explicitly.
420
+ #
421
+
422
+ #
423
+ # Return the last value in the internal series.
424
+ #
425
+ # This is equivalent to
426
+ #
427
+ # Fibonaccia[-1]
428
+ #
429
+ # @return [Integer]
430
+ # the last term in the internal series.
431
+ #
432
+ def last
433
+ result = SERIES.last
434
+ return result
435
+ end # def last
436
+
437
+ #
438
+ # Obtain a slice (see Array#slice) of the Fibonacci series.
439
+ #
440
+ # The internal series will be extended, if necessary, to include
441
+ # all terms requested.
442
+ #
443
+ # @note
444
+ # The internal series is *zero-based*, which means the first
445
+ # term is numbered <tt>0</tt>.
446
+ #
447
+ # @param [Integer] first_term
448
+ # The first term of the slice from the series.
449
+ # @param [Integer] nterms
450
+ # The number of elements in the slice to be returned.
451
+ #
452
+ # @return [Integer]
453
+ # if the result is a valid slice containing only one term
454
+ # (<i>i.e.</i>, <tt>nterms</tt> is 1). Returns the Fibonacci term at
455
+ # the specified (zero-based) position in the sequence.
456
+ # @return [Array<Integer>]
457
+ # if the result is a valid multi-element slice (<i>e.g.</i>, <tt>nterms</tt>
458
+ # is greater than 1). Returns the specified slice.
459
+ # @return [nil]
460
+ # if the slice parameters are not meaningful (<i>e.g.</i>, <tt>slice(1, -1)</tt>).
461
+ #
462
+ # @raise [ArgumentError]
463
+ # if the arguments are not all integers.
464
+ #
465
+ def slice(first_term, nterms=1)
466
+ args = {
467
+ 'first_term' => first_term,
468
+ 'nterms' => nterms,
469
+ }
470
+ #
471
+ # Sanity-check our arguments; be more informative than the default
472
+ #
473
+ # TypeError: no implicit conversion of <class> into Integer
474
+ #
475
+ args.each do |argname,argval|
476
+ unless (argval.kind_of?(Integer))
477
+ raise(ArgumentError, "#{argname} must be an integer")
478
+ end
479
+ end
480
+ nterms = [ 1, nterms ].max
481
+ if (first_term < 0)
482
+ endpoint = [ 0, self.terms + first_term + nterms ].max
483
+ else
484
+ endpoint = first_term + nterms
485
+ end
486
+ Fibonaccia.extend_series(endpoint)
487
+ #
488
+ # We're going to pass this along to the array's own #slice
489
+ # method, so build its argument list appropriately.
490
+ #
491
+ args = [ first_term ]
492
+ args << nterms unless (nterms == 1)
493
+ result = SERIES.slice(*args)
494
+ #
495
+ # If we got a multi-element slice, make sure we don't return our
496
+ # master sequence! Ruby shouldn't let it happen, but defensive
497
+ # programing is all.
498
+ #
499
+ result = result.dup if (result === SERIES)
500
+ return result
501
+ end
502
+
503
+ # @internal_comment
504
+ #
505
+ # <tt>Module</tt> doesn't have <tt>#alias_method</tt>, so we have
506
+ # to work sideways to make the equivalent functionality happen.
507
+ # And Yard doesn't pick up on <tt>define_method</tt> invocations,
508
+ # so we have to add the method documentation manually.
509
+ #
510
+
511
+ # @!method [](first_term, nterms=1)
512
+ #
513
+ # Alias for {slice} ( *q.v.*).
514
+ #
515
+ # This is included because it's probably more human-readable to
516
+ # find the *n*-th term of the sequence using the syntax
517
+ #
518
+ # Fibonaccia[n]
519
+ #
520
+ # @see slice
521
+ #
522
+ define_method(:[], self.instance_method(:slice))
523
+
524
+ # @internal_comment
525
+ #
526
+ # We need to use the <tt>BigDecimal</tt> module to deal with the
527
+ # extra precision required for #is_fibonacci?.
528
+ #
529
+ # In addition, we don't use #member? or #include? for this
530
+ # functionality because it would onflict with the Enumerable
531
+ # semantics for those, which apply to the internal series.
532
+ #
533
+
534
+ #
535
+ # See if value appears in the Fibonacci series.
536
+ #
537
+ # Check to see if the given value is found in the Fibonacci series,
538
+ # using the transform described at
539
+ # {https://en.wikipedia.org/wiki/Fibonacci_number#Recognizing_Fibonacci_numbers}.
540
+ #
541
+ # @see https://en.wikipedia.org/wiki/Fibonacci_number#Recognizing_Fibonacci_numbers
542
+ #
543
+ # @param [Integer] val
544
+ # Value to be checked for membership in the Fibonacci series.
545
+ #
546
+ # @return [Boolean]
547
+ # <tt>true</tt> if the given value is a Fibonacci number, else <tt>false</tt>.
548
+ #
549
+ def is_fibonacci?(val)
550
+ #
551
+ # Needs to be an integer.
552
+ #
553
+ return false unless (val.respond_to?(:floor) && (val.floor == val))
554
+ #
555
+ # Needs to be non-negative.
556
+ #
557
+ return false if (val < 0)
558
+ return true if (SERIES.include?(val))
559
+ #
560
+ # Easy checks are done, time for some math-fu.
561
+ #
562
+ val = BigDecimal.new(val)
563
+ [ +4, -4 ].each do |c|
564
+ eqterm = 5 * (val**2) + c
565
+ root = eqterm.sqrt(BDPrecision)
566
+ return true if (root.floor == root)
567
+ end
568
+ return false
569
+ end # is_fibonacci?
570
+
571
+ end # module Fibonaccia eigenclass
572
+
573
+ end # module Fibonaccia