1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Runtime.CompilerServices;
9 
10 namespace Antmicro.Renode.Utilities
11 {
12     public
13 #if NET
14     readonly
15 #endif
16     struct Fraction
17     {
18         public readonly ulong Numerator;
19         public readonly ulong Denominator;
20         public readonly bool Minus;
21 
22         public ulong Integer => Numerator / Denominator;
23         public Fraction Fractional => new Fraction(Numerator % Denominator, Denominator, skipReduce: true);
24 
FractionAntmicro.Renode.Utilities.Fraction25         public Fraction(ulong numerator, ulong denominator, bool minus = false) : this(numerator, denominator, minus, skipReduce: false)
26         {
27         }
28 
29         [MethodImpl(MethodImplOptions.AggressiveInlining)]
30         public static Fraction operator +(Fraction a) => a;
31 
32         [MethodImpl(MethodImplOptions.AggressiveInlining)]
33         public static Fraction operator -(Fraction a) => new Fraction(a.Numerator, a.Denominator, !a.Minus, skipReduce: true);
34 
operator +Antmicro.Renode.Utilities.Fraction35         public static Fraction operator +(Fraction a, Fraction b)
36         {
37             var anum = a.Numerator;
38             var bnum = b.Numerator;
39             var newden = a.Denominator;
40 
41             if(anum == 0)
42             {
43                 return b;
44             }
45             if(bnum == 0)
46             {
47                 return a;
48             }
49 
50             if(a.Denominator != b.Denominator)
51             {
52                 newden = Misc.LCM(a.Denominator, b.Denominator);
53                 anum *= newden / a.Denominator;
54                 bnum *= newden / b.Denominator;
55             }
56 
57             if(a.Minus == b.Minus)
58             {
59                 return new Fraction(anum + bnum, newden, a.Minus);
60             }
61             else
62             {
63                 var minus = bnum > anum ? b.Minus : a.Minus;
64                 return new Fraction(Math.Max(bnum, anum) - Math.Min(bnum, anum), newden, minus);
65             }
66         }
67 
68         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator -Antmicro.Renode.Utilities.Fraction69         public static Fraction operator -(Fraction a, Fraction b)
70             => a + (-b);
71 
operator *Antmicro.Renode.Utilities.Fraction72         public static Fraction operator *(Fraction a, Fraction b)
73         {
74             var anum = a.Numerator;
75             var bnum = b.Numerator;
76             var aden = a.Denominator;
77             var bden = b.Denominator;
78             Reduce(ref anum, ref bden);
79             Reduce(ref aden, ref bnum);
80             return new Fraction(anum * bnum, aden * bden, a.Minus != b.Minus, skipReduce: true);
81         }
82 
operator /Antmicro.Renode.Utilities.Fraction83         public static Fraction operator /(Fraction a, Fraction b)
84         {
85             var anum = a.Numerator;
86             var bnum = b.Numerator;
87             var aden = a.Denominator;
88             var bden = b.Denominator;
89             if(bnum == 0)
90             {
91                 throw new DivideByZeroException();
92             }
93             Reduce(ref anum, ref bnum);
94             Reduce(ref aden, ref bden);
95             return new Fraction(anum * bden, aden * bnum, a.Minus != b.Minus, skipReduce: true);
96         }
97 
98         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator +Antmicro.Renode.Utilities.Fraction99         public static Fraction operator +(Fraction a, ulong b)
100         {
101             if(b == 0)
102             {
103                 return a;
104             }
105             return a + new Fraction(b, 1, skipReduce: false);
106         }
107 
108         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator +Antmicro.Renode.Utilities.Fraction109         public static Fraction operator +(ulong b, Fraction a)
110             => a + b;
111 
112         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator -Antmicro.Renode.Utilities.Fraction113         public static Fraction operator -(Fraction a, ulong b)
114         {
115             if(b == 0)
116             {
117                 return a;
118             }
119             return a + new Fraction(b, 1, minus: true, skipReduce: true);
120         }
121 
122         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator -Antmicro.Renode.Utilities.Fraction123         public static Fraction operator -(ulong b, Fraction a)
124         {
125             if(b == 0)
126             {
127                 return -a;
128             }
129             return new Fraction(b, 1, skipReduce: true) - a;
130         }
131 
132         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator *Antmicro.Renode.Utilities.Fraction133         public static Fraction operator *(Fraction a, ulong b)
134         {
135             if(b == 0)
136             {
137                 return Zero;
138             }
139             if(b == 1)
140             {
141                 return a;
142             }
143             return a * new Fraction(b, 1, skipReduce: true);
144         }
145 
146         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator *Antmicro.Renode.Utilities.Fraction147         public static Fraction operator *(ulong b, Fraction a)
148             => a * b;
149 
150         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator /Antmicro.Renode.Utilities.Fraction151         public static Fraction operator /(Fraction a, ulong b)
152         {
153             if(b == 1)
154             {
155                 return a;
156             }
157             return a / new Fraction(b, 1, skipReduce: true);
158         }
159 
160         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator /Antmicro.Renode.Utilities.Fraction161         public static Fraction operator /(ulong b, Fraction a)
162         {
163             if(b == 0)
164             {
165                 return Zero;
166             }
167             return new Fraction(b, 1, skipReduce: true) / a;
168         }
169 
170         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator ==Antmicro.Renode.Utilities.Fraction171         public static bool operator ==(Fraction a, Fraction b)
172             => (a.Numerator == b.Numerator && a.Denominator == b.Denominator && a.Minus == b.Minus) || (a.Numerator == 0 && b.Numerator == 0);
173 
174         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator !=Antmicro.Renode.Utilities.Fraction175         public static bool operator !=(Fraction a, Fraction b)
176             => !(a == b);
177 
178         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator <Antmicro.Renode.Utilities.Fraction179         public static bool operator <(Fraction a, Fraction b)
180             => (a - b).Minus;
181 
182         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator >Antmicro.Renode.Utilities.Fraction183         public static bool operator >(Fraction a, Fraction b)
184             => (b - a).Minus;
185 
186         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator <=Antmicro.Renode.Utilities.Fraction187         public static bool operator <=(Fraction a, Fraction b)
188             => !(a > b);
189 
190         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator >=Antmicro.Renode.Utilities.Fraction191         public static bool operator >=(Fraction a, Fraction b)
192             => !(a < b);
193 
194         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator ==Antmicro.Renode.Utilities.Fraction195         public static bool operator ==(Fraction a, ulong b)
196             => a.Integer == b;
197 
198         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator !=Antmicro.Renode.Utilities.Fraction199         public static bool operator !=(Fraction a, ulong b)
200             => !(a == b);
201 
202         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator <Antmicro.Renode.Utilities.Fraction203         public static bool operator <(Fraction a, ulong b)
204             => a.Integer < b;
205 
206         [MethodImpl(MethodImplOptions.AggressiveInlining)]
207         public static bool operator >(Fraction a, ulong b)
208             => a.Integer > b || (a.Integer == b && a.Fractional.Numerator > 0);
209 
210         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator <=Antmicro.Renode.Utilities.Fraction211         public static bool operator <=(Fraction a, ulong b)
212             => !(a > b);
213 
214         [MethodImpl(MethodImplOptions.AggressiveInlining)]
operator >=Antmicro.Renode.Utilities.Fraction215         public static bool operator >=(Fraction a, ulong b)
216             => !(a < b);
217 
CompareToAntmicro.Renode.Utilities.Fraction218         public int CompareTo(Fraction other)
219         {
220             if(this == other)
221             {
222                 return 0;
223             }
224             if(this < other)
225             {
226                 return -1;
227             }
228             return 1;
229         }
230 
EqualsAntmicro.Renode.Utilities.Fraction231         public override bool Equals(object obj)
232         {
233             return (obj is Fraction f) && this == f;
234         }
235 
GetHashCodeAntmicro.Renode.Utilities.Fraction236         public override int GetHashCode()
237         {
238             return (int)(Numerator ^ Denominator * (Minus ? 2L : 1UL));
239         }
240 
ToStringAntmicro.Renode.Utilities.Fraction241         public override string ToString()
242         {
243             char sign = Minus ? '-' : '+';
244             if(Denominator == 1)
245             {
246                 return $"{sign}{Numerator}";
247             }
248             else
249             {
250                 return $"{sign}{Numerator}/{Denominator}";
251             }
252         }
253 
254         public static explicit operator ulong(Fraction f) => f.Integer;
operator FractionAntmicro.Renode.Utilities.Fraction255         public static explicit operator Fraction(ulong u) => new Fraction(u, 1, false, skipReduce: true);
256         public static readonly Fraction Zero = new Fraction(0, 1, skipReduce: true);
257 
FractionAntmicro.Renode.Utilities.Fraction258         private Fraction(ulong numerator, ulong denominator, bool minus = false, bool skipReduce = false)
259         {
260             // Denominator cannot be zero. If the numerator is 0, represent as 0/1 (see Zero)
261             if(denominator == 0 || numerator == 0)
262             {
263                 denominator = 1;
264             }
265 
266             if(!skipReduce)
267             {
268                 Reduce(ref numerator, ref denominator);
269             }
270             Numerator = numerator;
271             Denominator = denominator;
272             Minus = minus;
273         }
274 
ReduceAntmicro.Renode.Utilities.Fraction275         private static void Reduce(ref ulong a, ref ulong b)
276         {
277             var gcd = Misc.GCD(a, b);
278             if(gcd > 1)
279             {
280                 a /= gcd;
281                 b /= gcd;
282             }
283         }
284     }
285 }
286