fractify 1.0.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,683 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fractify
4
+ class DividingByZeroError < StandardError; end
5
+ class IncorrectArgumentsError < StandardError; end
6
+ class IncorrectStringSyntax < StandardError; end
7
+ class Fraction
8
+ attr_accessor :whole_part, :numerator, :denominator, :negative
9
+
10
+ def initialize(whole_part: 0, numerator: 0, denominator: 1, string: '', number: false)
11
+ unless string.strip.empty?
12
+ to_zero!
13
+ parse_fraction_string!(string)
14
+ return
15
+ end
16
+
17
+ if number.is_a? Numeric
18
+ convert_number!(number)
19
+ return
20
+ end
21
+ raise DividingByZeroError if denominator.zero? && numerator != 0
22
+
23
+ @whole_part = whole_part
24
+ self.numerator = numerator
25
+ self.denominator = denominator
26
+ self.negative = false
27
+
28
+ simplify!
29
+ end
30
+
31
+ def negative?
32
+ negative
33
+ end
34
+
35
+ def zero?
36
+ return true if numerator.zero?
37
+
38
+ false
39
+ end
40
+
41
+ def one?
42
+ return true if numerator == 1 && denominator == 1 && whole_part.zero?
43
+
44
+ false
45
+ end
46
+
47
+ def simplify!
48
+ fix_negativity!
49
+ if !zero?
50
+ greatest_common_divisor = self.class.greatest_common_divisor(numerator, denominator)
51
+
52
+ @denominator /= greatest_common_divisor
53
+ @numerator /= greatest_common_divisor
54
+
55
+ if denominator == 1 && numerator != 1
56
+ @whole_part += numerator
57
+ @numerator = 1
58
+ elsif numerator > denominator
59
+ new_whole_part = numerator / denominator
60
+ @whole_part += new_whole_part
61
+ @numerator %= denominator
62
+ end
63
+ else
64
+ @negative = false
65
+ end
66
+ end
67
+
68
+ def puts
69
+ puts to_s
70
+ end
71
+
72
+ def to_zero!
73
+ @whole_part = 0
74
+ @numerator = 0
75
+ @denominator = 1
76
+ @negative = false
77
+ end
78
+
79
+ def to_improper!
80
+ return if whole_part.zero?
81
+
82
+ simplify!
83
+ @numerator -= 1 if numerator == 1 && denominator == 1
84
+ @numerator += whole_part * denominator
85
+ @whole_part = 0
86
+ end
87
+
88
+ def to_f
89
+ return 0.0 if zero?
90
+
91
+ fraction = dup
92
+ fraction.to_improper!
93
+ fraction.minus_to_numerator!
94
+
95
+ fraction.numerator.to_f / fraction.denominator
96
+ end
97
+
98
+ def to_i
99
+ return 0 if zero?
100
+
101
+ fraction = dup
102
+ fraction.simplify!
103
+
104
+ result = whole_part
105
+ result = -result if fraction.negative?
106
+
107
+ result
108
+ end
109
+
110
+ def to_s
111
+ fraction_string = '('
112
+ fraction_string += '-' if negative?
113
+
114
+ if zero?
115
+ fraction_string += '0'
116
+ elsif one?
117
+ fraction_string += '1'
118
+ else
119
+ fraction_string += whole_part.to_s if whole_part != 0
120
+ if numerator != 1 || denominator != 1
121
+ fraction_string += ' ' if whole_part != 0
122
+ fraction_string += numerator.to_s + '/' + denominator.to_s
123
+ end
124
+ end
125
+
126
+ fraction_string + ')'
127
+ end
128
+
129
+ def -(other)
130
+ fraction = dup
131
+ object = other.dup
132
+ if zero?
133
+ fraction.whole_part = object.whole_part
134
+ fraction.numerator = object.numerator
135
+ fraction.denominator = object.denominator
136
+ fraction.negative = !object.negative
137
+ else
138
+ fraction.to_common_denominator!(object) unless object.zero?
139
+
140
+ fraction.minus_to_numerator!
141
+ object.minus_to_numerator!
142
+
143
+ fraction.numerator -= object.numerator
144
+ fraction.minus_to_negative_field!
145
+ fraction.simplify!
146
+ end
147
+
148
+ fraction
149
+ end
150
+
151
+ def +(other)
152
+ fraction = dup
153
+ object = other.dup
154
+ if zero?
155
+ fraction.whole_part = object.whole_part
156
+ fraction.numerator = object.numerator
157
+ fraction.denominator = object.denominator
158
+ fraction.negative = object.negative
159
+ else
160
+ fraction.to_common_denominator!(object) unless object.zero?
161
+
162
+ fraction.minus_to_numerator!
163
+ object.minus_to_numerator!
164
+
165
+ fraction.numerator += object.numerator
166
+ fraction.minus_to_negative_field!
167
+ fraction.simplify!
168
+ end
169
+
170
+ fraction
171
+ end
172
+
173
+ def *(other)
174
+ fraction = dup
175
+ object = other.dup
176
+
177
+ fraction.to_improper!
178
+ object.to_improper!
179
+
180
+ negative_counter = 0
181
+ negative_counter += 1 if fraction.negative?
182
+ negative_counter += 1 if object.negative?
183
+ fraction.negative = negative_counter.even? ? false : true
184
+
185
+ fraction.numerator *= object.numerator
186
+ fraction.denominator *= object.denominator
187
+
188
+ fraction.simplify!
189
+
190
+ fraction.whole_part = 1 if fraction.zero?
191
+
192
+ fraction
193
+ end
194
+
195
+ def /(other)
196
+ fraction = dup
197
+ object = other.dup
198
+
199
+ object.to_reciprocal!
200
+ fraction *= object
201
+
202
+ fraction
203
+ end
204
+
205
+ def **(other)
206
+ if other.is_a? Numeric
207
+ power(other)
208
+ elsif other.is_a? Fractify::Fraction
209
+ x = other.to_f
210
+ power(x)
211
+ else
212
+ raise IncorrectArgumentsError
213
+ end
214
+ end
215
+
216
+ def self.valid?(string)
217
+ return false unless string.is_a? String
218
+
219
+ validate_fraction_string(string)
220
+ end
221
+
222
+ def self.floating_point_part(number)
223
+ raise IncorrectArgumentsError unless number.is_a? Numeric
224
+
225
+ result = '0.'
226
+ number_string = number.to_s
227
+ past_decimal_separator = false
228
+
229
+ number_string.each_char do |c|
230
+ if past_decimal_separator
231
+ result += c
232
+ elsif c == '.'
233
+ past_decimal_separator = true
234
+ end
235
+ end
236
+
237
+ result.to_f
238
+ end
239
+
240
+ def self.least_common_multiple(first, second)
241
+ raise IncorrectArgumentsError unless first.is_a?(Numeric) && second.is_a?(Numeric)
242
+
243
+ (first * second) / greatest_common_divisor(first, second)
244
+ end
245
+
246
+ def self.greatest_common_divisor(first, second)
247
+ first = first.abs
248
+ second = second.abs
249
+
250
+ while first != second
251
+ if first > second
252
+ first -= second
253
+ else
254
+ second -= first
255
+ end
256
+ end
257
+
258
+ first
259
+ end
260
+
261
+ def self.numeric?(char)
262
+ char =~ /[[:digit:]]/
263
+ end
264
+
265
+ def self.letter?(char)
266
+ char =~ /[[:alpha:]]/
267
+ end
268
+
269
+ def self.validate_fraction_string(string)
270
+ is_float = false
271
+ string.each_char do |c|
272
+ if c == '.'
273
+ is_float = true
274
+ break
275
+ end
276
+ end
277
+
278
+ if is_float
279
+ validate_float_string(string)
280
+ else
281
+ validate_string(string)
282
+ end
283
+ end
284
+
285
+ def self.validate_string(string)
286
+ stage = 0
287
+ open_bracket, incorrect_syntax, at_end, digit_present = false
288
+ minus_is_free = true
289
+
290
+ string.each_char do |c|
291
+ if at_end || letter?(c)
292
+ incorrect_syntax = true
293
+ break
294
+ end
295
+
296
+ if open_bracket
297
+ if stage.zero?
298
+ if c == '-' && minus_is_free
299
+ minus_is_free = false
300
+ elsif numeric?(c)
301
+ digit_present = true
302
+ elsif c == ' ' && digit_present
303
+ stage = 1
304
+ minus_is_free = true
305
+ digit_present = false
306
+ elsif c == '/' && digit_present
307
+ stage = 2
308
+ minus_is_free = true
309
+ digit_present = false
310
+ elsif c == ')' && digit_present
311
+ else
312
+ incorrect_syntax = true
313
+ break
314
+ end
315
+ elsif stage == 1
316
+ if c == '-' && minus_is_free
317
+ minus_is_free = false
318
+ elsif numeric?(c)
319
+ digit_present = true
320
+ elsif c == '/' && digit_present
321
+ stage = 2
322
+ minus_is_free = true
323
+ digit_present
324
+ else
325
+ incorrect_syntax = true
326
+ break
327
+ end
328
+ elsif stage == 2
329
+ if c == '-' && minus_is_free
330
+ minus_is_free = false
331
+ elsif numeric?(c)
332
+ digit_present = true
333
+ elsif c == ')' && digit_present
334
+ else
335
+ incorrect_syntax = true
336
+ break
337
+ end
338
+ end
339
+ end
340
+
341
+ if c == '('
342
+ if open_bracket
343
+ incorrect_syntax = true
344
+ break
345
+ end
346
+ open_bracket = true
347
+ elsif c == ')'
348
+ unless open_bracket
349
+ incorrect_syntax = true
350
+ break
351
+ end
352
+ open_bracket = false
353
+ at_end = true
354
+ end
355
+ end
356
+
357
+ incorrect_syntax = true if open_bracket
358
+
359
+ !incorrect_syntax
360
+ end
361
+
362
+ def self.validate_float_string(string)
363
+ open_bracket, incorrect_syntax, at_end, digit_present,
364
+ floating_point_present = false
365
+ minus_is_free = true
366
+
367
+ string.each_char do |c|
368
+ if at_end || letter?(c)
369
+ incorrect_syntax = true
370
+ break
371
+ end
372
+
373
+ if open_bracket
374
+ if c == '-'
375
+ if !minus_is_free || digit_present
376
+ incorrect_syntax = true
377
+ break
378
+ end
379
+ minus_is_free = false
380
+ elsif numeric?(c)
381
+ digit_present = true
382
+ elsif c == '.'
383
+ if floating_point_present || !digit_present
384
+ incorrect_syntax = true
385
+ break
386
+ end
387
+ floating_point_present = true
388
+ elsif c == ')'
389
+ open_bracket = false
390
+ at_end = true
391
+ else
392
+ incorrect_syntax = true
393
+ break
394
+ end
395
+ elsif c == '('
396
+ open_bracket = true
397
+ else
398
+ incorrect_syntax = true
399
+ break
400
+ end
401
+ end
402
+
403
+ incorrect_syntax = true if open_bracket
404
+
405
+ !incorrect_syntax
406
+ end
407
+
408
+ def to_common_denominator!(other)
409
+ to_improper!
410
+ other.to_improper!
411
+
412
+ least_common_multiple = self.class.least_common_multiple(denominator, other.denominator)
413
+
414
+ numerator_multiplication = least_common_multiple / denominator
415
+ @numerator *= numerator_multiplication
416
+ @denominator = least_common_multiple
417
+
418
+ numerator_multiplication = least_common_multiple / other.denominator
419
+ other.numerator *= numerator_multiplication
420
+ other.denominator = least_common_multiple
421
+ end
422
+
423
+ def to_reciprocal!
424
+ to_improper!
425
+ aux = denominator
426
+ @denominator = numerator
427
+ @numerator = aux
428
+ end
429
+
430
+ protected
431
+
432
+ def minus_to_numerator!
433
+ return unless negative
434
+
435
+ @negative = false
436
+ @numerator = -numerator
437
+ end
438
+
439
+ def minus_to_negative_field!
440
+ return unless numerator.negative?
441
+
442
+ @negative = true
443
+ @numerator = numerator.abs
444
+ end
445
+
446
+ private
447
+
448
+ def power(x)
449
+ fraction = dup
450
+ fraction.negative = false if x % 2 == 0
451
+
452
+ if x.negative?
453
+ fraction.to_reciprocal!
454
+ x = x.abs
455
+ else
456
+ fraction.to_improper!
457
+ end
458
+
459
+ if x == x.to_i
460
+ fraction.numerator **= x.to_i
461
+ fraction.denominator **= x.to_i
462
+ fraction.simplify!
463
+ else
464
+ float = fraction.to_f.abs
465
+ float **= x
466
+ float = -float if fraction.negative?
467
+ fraction = Fractify::Fraction.new(number: float)
468
+ end
469
+
470
+ fraction
471
+ end
472
+
473
+ def fix_negativity!
474
+ @negative = !negative if arguments_make_negative?
475
+
476
+ @whole_part = whole_part.abs
477
+ @numerator = numerator.abs
478
+ @denominator = denominator.abs
479
+ end
480
+
481
+ def arguments_make_negative?
482
+ negative_attributes = 0
483
+ %w[whole_part numerator denominator].each do |a|
484
+ negative_attributes += 1 if send(a).negative?
485
+ end
486
+ return true if negative_attributes.odd?
487
+
488
+ false
489
+ end
490
+
491
+ def parse_fraction_string!(string)
492
+ is_float = false
493
+ string.each_char do |c|
494
+ if c == '.'
495
+ is_float = true
496
+ break
497
+ end
498
+ end
499
+
500
+ if is_float
501
+ parse_float_string!(string)
502
+ else
503
+ parse_string!(string)
504
+ end
505
+ end
506
+
507
+ def parse_string!(string)
508
+ stage = 0
509
+ number_string = ''
510
+
511
+ open_bracket, incorrect_syntax, at_end, digit_present = false
512
+
513
+ minus_is_free = true
514
+
515
+ string.each_char do |c|
516
+ if at_end || self.class.letter?(c)
517
+ incorrect_syntax = true
518
+ break
519
+ end
520
+
521
+ if open_bracket
522
+ if stage.zero?
523
+ if c == '-' && minus_is_free
524
+ @negative = true
525
+ minus_is_free = false
526
+ elsif self.class.numeric?(c)
527
+ number_string += c
528
+ digit_present = true
529
+ elsif c == ' ' && digit_present
530
+ stage = 1
531
+ @whole_part = number_string.to_i
532
+ number_string = ''
533
+ minus_is_free = true
534
+ digit_present = false
535
+ elsif c == '/' && digit_present
536
+ stage = 2
537
+ @numerator = number_string.to_i
538
+ number_string = ''
539
+ minus_is_free = true
540
+ digit_present = false
541
+ elsif c == ')' && digit_present
542
+ @numerator = number_string.to_i
543
+ @denominator = 1
544
+ else
545
+ incorrect_syntax = true
546
+ break
547
+ end
548
+ elsif stage == 1
549
+ if c == '-' && minus_is_free
550
+ @negative = !negative
551
+ minus_is_free = false
552
+ elsif self.class.numeric?(c)
553
+ number_string += c
554
+ digit_present = true
555
+ elsif c == '/' && digit_present
556
+ stage = 2
557
+ @numerator = number_string.to_i
558
+ number_string = ''
559
+ minus_is_free = true
560
+ digit_present
561
+ else
562
+ incorrect_syntax = true
563
+ break
564
+ end
565
+ elsif stage == 2
566
+ if c == '-' && minus_is_free
567
+ @negative = !negative
568
+ minus_is_free = false
569
+ elsif self.class.numeric?(c)
570
+ number_string += c
571
+ digit_present = true
572
+ elsif c == ')' && digit_present
573
+ @denominator = number_string.to_i
574
+ else
575
+ incorrect_syntax = true
576
+ break
577
+ end
578
+ end
579
+ end
580
+
581
+ if c == '('
582
+ if open_bracket
583
+ incorrect_syntax = true
584
+ break
585
+ end
586
+ open_bracket = true
587
+ elsif c == ')'
588
+ unless open_bracket
589
+ incorrect_syntax = true
590
+ break
591
+ end
592
+ open_bracket = false
593
+ at_end = true
594
+ end
595
+ end
596
+
597
+ raise IncorrectStringSyntax if incorrect_syntax || open_bracket
598
+
599
+ simplify!
600
+ end
601
+
602
+ def parse_float_string!(string)
603
+ number_string = ''
604
+ open_bracket, incorrect_syntax, at_end, digit_present,
605
+ floating_point_present = false
606
+
607
+ minus_is_free = true
608
+
609
+ string.each_char do |c|
610
+ if at_end || self.class.letter?(c)
611
+ incorrect_syntax = true
612
+ break
613
+ end
614
+
615
+ if open_bracket
616
+ if c == '-'
617
+ if !minus_is_free || digit_present
618
+ incorrect_syntax = true
619
+ break
620
+ end
621
+ minus_is_free = false
622
+ number_string += c
623
+ elsif self.class.numeric?(c)
624
+ digit_present = true
625
+ number_string += c
626
+ elsif c == '.'
627
+ if floating_point_present || !digit_present
628
+ incorrect_syntax = true
629
+ break
630
+ end
631
+ floating_point_present = true
632
+ number_string += c
633
+ elsif c == ')'
634
+ open_bracket = false
635
+ at_end = true
636
+ else
637
+ incorrect_syntax = true
638
+ break
639
+ end
640
+ elsif c == '('
641
+ open_bracket = true
642
+ else
643
+ incorrect_syntax = true
644
+ break
645
+ end
646
+ end
647
+
648
+ raise IncorrectStringSyntax if open_bracket || incorrect_syntax
649
+
650
+ convert_number!(number_string.to_f)
651
+ end
652
+
653
+ def convert_number!(number)
654
+ if number.zero?
655
+ to_zero!
656
+ return
657
+ end
658
+
659
+ if number.negative?
660
+ number = number.abs
661
+ @negative = true
662
+ end
663
+
664
+ @whole_part = number.to_i
665
+ new_denominator = 1
666
+ floating_point_part = self.class.floating_point_part(number)
667
+
668
+ while floating_point_part.positive?
669
+ floating_point_part *= new_denominator
670
+ floating_point_part = self.class.floating_point_part(floating_point_part)
671
+ new_denominator *= 10
672
+ end
673
+
674
+ @numerator = self.class.floating_point_part(number) * new_denominator
675
+ @numerator = numerator.to_i
676
+ @denominator = new_denominator
677
+
678
+ @numerator = 1 if numerator.zero?
679
+
680
+ simplify!
681
+ end
682
+ end
683
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fractify
4
+ class IncorrectArgumentsError < StandardError; end
5
+ class Operator
6
+ attr_accessor :operator_character, :rank, :executed, :to_left, :to_right
7
+
8
+ def initialize(operator_character, rank, to_left = nil, to_right = nil)
9
+ if to_left && !to_left.is_a?(Fractify::Fraction) || to_right && !to_right.is_a?(Fractify::Fraction)
10
+ raise IncorrectArgumentsError
11
+ end
12
+
13
+ self.operator_character = operator_character
14
+ self.rank = rank
15
+ self.to_left = to_left
16
+ self.to_right = to_right
17
+ self.executed = false
18
+ end
19
+
20
+ def <=>(other)
21
+ raise IncorrectArgumentsError unless other.is_a? Fractify::Operator
22
+ return 0 if rank == other.rank
23
+ return 1 if rank > other.rank
24
+
25
+ -1
26
+ end
27
+
28
+ def to_s
29
+ "(#{operator_character}, #{rank})"
30
+ end
31
+
32
+ def decrease_rank!(number = 1)
33
+ @rank -= number
34
+ end
35
+
36
+ def increase_rank!(number = 1)
37
+ @rank += number
38
+ end
39
+
40
+ def executed!
41
+ self.executed = true
42
+ end
43
+
44
+ def executed?
45
+ executed
46
+ end
47
+
48
+ def not_executed?
49
+ !executed
50
+ end
51
+ end
52
+ end