fibonaccia 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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