📜 ⬆️ ⬇️

Operator overloading in freepascal on the example of ordinary fractions

image

We all remember how ordinary fractions were taught in school. Numerators, denominators, GCD and LCM, arithmetic operations with fractions. But even in real life, ordinary fractions are successfully used in various fields of activity, including legal ones: for example, ordinary fractions can express the shares of participants in economic societies, shares in the right of common share ownership, etc.

And so, it took some time to implement a couple of functions in a corporate application for operating with ordinary fractions. The current implementation of pascal, whether delphi or freepascal, offers convenient tools for this.

The resulting module grew in the process of studying operator overloading.
When the module was ready, I saw that a year ago, participants in the English-language freepascal forum developed a similar, albeit collectively, weighed down with a mass of functions and various implementations of the same. Well, we will consider made by a kind of import substitution.

The main data type will be the following structure:

TFraction = record Numerator: longint; Denumerator: longint; function Create(ANum, ADenum: longint): TFraction; function toStr: string; function toFloat: extended; end; 

Do not forget to include the necessary compiler directive - {$ MODESWITCH ADVANCEDRECORDS}

The structure has two fields - an integer numerator and denominator, a constructor function for assigning values ​​to one line and a couple of functions for converting a common fraction to a string and a decimal fraction.
')
Auxiliary functions of the module (can be used independently):

 //     procedure SetEqualDenum(var ALeftFr, ARightFr: TFraction); //   -     function ExpandFraction(AFraction: TFraction; Factor: longint): TFraction; //    function gcd(ALeftDenum, ARightDenum: longint): longint; //    function lcm(ALeftDenum, ARightDenum: longint): longint; //   -    ,  toGCD        function CollapseFraction(AFraction: TFraction; Divider: longint = toGCD): TFraction; //     function CompareFractions(ALeftFr, ARightFr: TFraction): TfrCompareResult; //   ,        function ReverseFraction(AFraction: TFraction): TFraction; 

The main ones in the module are overloaded operators for addition, subtraction, multiplication, division, assignment and comparison of fractions:
 //    operator +(ALeftFr, ARightFr: TFraction) r: TFraction; //     operator +(ALeftFr: TFraction; const Term: longint) r: TFraction; //   operator -(ALeftFr, ARightFr: TFraction) r: TFraction; //    operator -(ALeftFr: TFraction; const Sub: longint) r: TFraction; //    operator * (ALeftFr, ARightFr: TFraction) r: TFraction; //     operator * (AFraction: TFraction; const Multiplier: longint) r: TFraction; operator * (const Multiplier: longint; AFraction: TFraction) r: TFraction; //    operator / (ALeftFr, ARightFr: TFraction) r: TFraction; //     operator / (AFraction: TFraction; const Divider: longint) r: TFraction; //    operator = (ALeftFr, ARightFr: TFraction) r: boolean; // ,     operator > (ALeftFr, ARightFr: TFraction) r: boolean; // ,     operator < (ALeftFr, ARightFr: TFraction) r: boolean; //      ( = 1) operator := (const AIntegerPart: longint) r: TFraction; //    /   operator := (const AStringFr: string) r: TFraction; 

Unfortunately, in freepascal it is impossible to transfer as an assigned value an enumeration of integers (dictionary, set, call it what you like, the meaning is that it is impossible: A: = (1,2); or so B: = [1,2] ), therefore, the initiation of a fraction goes through a constructor function or a string value, although nothing prevents you from simply setting values ​​to two fields, but I wanted to make it as simple as possible.

The implementation of overloaded methods, such as addition, division, assignment or comparison, looks like this:
 operator+(ALeftFr, ARightFr: TFraction)r: TFraction; begin SetEqualDenum(ALeftFr, ARightFr); r.Numerator := ALeftFr.Numerator + ARightFr.Numerator; r.Denumerator := ALeftFr.Denumerator; r := CollapseFraction(r, toGCD); end; ... operator/(ALeftFr, ARightFr: TFraction)r: TFraction; begin r := ALeftFr * ReverseFraction(ARightFr); end; ... operator:=(const AStringFr: string)r: TFraction; var i: integer; begin i := PosEx(char(SolidorSym), AStringFr); if not TryStrToInt(LeftStr(AStringFr, i - 1), r.Numerator) then raise Exception.Create('Numerator is not integer!'); if not TryStrToInt(RightStr(AStringFr, Length(AStringFr) - i), r.Denumerator) then raise Exception.Create('Denumerator is not integer!'); end; ... operator=(ALeftFr, ARightFr: TFraction)r: boolean; begin Result := CompareFractions(ALeftFr, ARightFr) = crEqual; end; operator>(ALeftFr, ARightFr: TFraction)r: boolean; begin Result := CompareFractions(ALeftFr, ARightFr) = crLeft; end; operator<(ALeftFr, ARightFr: TFraction)r: boolean; begin Result := CompareFractions(ALeftFr, ARightFr) = crRight; end; 


I repeat, in the module of “competitors” there are more functions and overloaded operators, so they additionally overloaded> =, <=, **, and also introduced assignment through decimal fraction and conversion to a string with the issuance of the “correct” fraction, the last for mathematical expressions is not need to.

To calculate the GCD, I chose the simplest recursive algorithm:

 function gcd(ALeftDenum, ARightDenum: longint): longint; begin if ARightDenum = 0 then Result := abs(ALeftDenum) else Result := abs(gcd(ARightDenum, ALeftDenum mod ARightDenum)); end; 

GCD we need to reduce fractions and calculate the LCM :

 function lcm(ALeftDenum, ARightDenum: longint): longint; begin Result := abs(ALeftDenum * ARightDenum) div gcd(ALeftDenum, ARightDenum); end; 

NOC, in turn, is used to bring the fractions to a common denominator:

 procedure SetEqualDenum(var ALeftFr, ARightFr: TFraction); var tDenum: longint; begin if ALeftFr.Denumerator = ARightFr.Denumerator then exit; tDenum := lcm(ALeftFr.Denumerator, ARightFr.Denumerator); ALeftFr := ExpandFraction(ALeftFr, tDenum div ALeftFr.Denumerator); ARightFr := ExpandFraction(ARightFr, tDenum div ARightFr.Denumerator); end; 

And this function is used as a result in the overloaded addition, subtraction, and comparison operators.

Operator overloading allows you to write such simple assignments:

 Fr1, Fr2: TFraction; ... Fr1 := 12; // (  12/1) Fr2 := '3/5'; // (   ) //     Fr3 := TFraction.Create(22,7); // 22/7 

It becomes easier to write operations with fractions and inequalities:

 Fr3 := Fr1+ Fr2; Fr3 := Fr1 * Fr2; Fr2 := Fr1 - 1; Fr2 := Fr1 / 3; Fr3 := Fr1 / Fr2; if Fr1 > Fr2 … 

Combined assignment operators will also work:

 Fr1 += Fr2; Fr2 -= 1; Fr3 *= '1/2'; 

Even the following expressions are allowed:

 if Fr1 > '2/3' ... while Fr2 < 1 ... 

These inequalities compile perfectly and give the correct logical result.

In the standard delivery of freepascal there are a couple of similar modules for working with mathematical matrices and complex numbers, you can look at their implementation as examples.
In my opinion, operator overloading brings the same simplification in the cleanliness and visibility of the code, like generics (the lists from fgl are especially loved by me), of course, if you don’t overload the operator plus the division method. The compiler will always (well, almost) stop you if you forget that the operator is overloaded or vice versa (due to data type mismatch).

The described module is far from perfect, you need to add processing of the result in cases of getting zero for mathematical operations, dividing by zero, you can also add conversions of decimal fractions to ordinary ones and vice versa.

→ The full text of the module is here .
→ Module from the freepascal.org forum .

Source: https://habr.com/ru/post/327450/


All Articles