fractify 1.0.0

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