🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉

# Selticq's solution

## to Rational Numbers in the Delphi Pascal Track

Published at Feb 21 2021 · 0 comments
Instructions
Test suite
Solution

A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`.

The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`.

The sum of two rational numbers `r1 = a1/b1` and `r2 = a2/b2` is `r1 + r2 = a1/b1 + a2/b2 = (a1 * b2 + a2 * b1) / (b1 * b2)`.

The difference of two rational numbers `r1 = a1/b1` and `r2 = a2/b2` is `r1 - r2 = a1/b1 - a2/b2 = (a1 * b2 - a2 * b1) / (b1 * b2)`.

The product (multiplication) of two rational numbers `r1 = a1/b1` and `r2 = a2/b2` is `r1 * r2 = (a1 * a2) / (b1 * b2)`.

Dividing a rational number `r1 = a1/b1` by another `r2 = a2/b2` is `r1 / r2 = (a1 * b2) / (a2 * b1)` if `a2 * b1` is not zero.

Exponentiation of a rational number `r = a/b` to a non-negative integer power `n` is `r^n = (a^n)/(b^n)`.

Exponentiation of a rational number `r = a/b` to a negative integer power `n` is `r^n = (b^m)/(a^m)`, where `m = |n|`.

Exponentiation of a rational number `r = a/b` to a real (floating-point) number `x` is the quotient `(a^x)/(b^x)`, which is a real number.

Exponentiation of a real number `x` to a rational number `r = a/b` is `x^(a/b) = root(x^a, b)`, where `root(p, q)` is the `q`th root of `p`.

Implement the following operations:

• addition, subtraction, multiplication and division of two rational numbers,
• absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number.

Your implementation of rational numbers should always be reduced to lowest terms. For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`.

Assume that the programming language you are using does not have an implementation of rational numbers.

## Hints

• Operator overloading is being introduced in this exercise. The Embarcadero docwiki on the subject will be very helpful to you in understanding how overriding class operators is possible along with Implicit and Explicit casting.

## Testing

In order to run the tests for this track, you will need to install DUnitX. Please see the installation instructions for more information.

If Delphi is properly installed, and `*.dpr` file types have been associated with Delphi, then double clicking the supplied `*.dpr` file will start Delphi and load the exercise/project. `control + F9` is the keyboard shortcut to compile the project or pressing `F9` will compile and run the project.

Alternatively you may opt to start Delphi and load your project via. the `File` drop down menu.

### When Questions Come Up

We monitor the Pascal-Delphi support room on gitter.im to help you with any questions that might arise.

### Submitting Exercises

Note that, when trying to submit an exercise, make sure the exercise file you're submitting is in the `exercism/delphi/<exerciseName>` directory.

For example, if you're submitting `ubob.pas` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/delphi/bob/ubob.pas`.

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you may request help from a mentor.

### RationalNumbersTest.dpr

``````program RationalNumbersTest;

{\$IFNDEF TESTINSIGHT}
{\$APPTYPE CONSOLE}
uses
System.SysUtils,
{\$IFDEF TESTINSIGHT}
TestInsight.DUnitX,
{\$ENDIF }
DUnitX.Loggers.Console,
DUnitX.Loggers.Xml.NUnit,
DUnitX.TestFramework,
uRationalNumbersTests in 'uRationalNumbersTests.pas',
uRationalNumbers in 'uRationalNumbers.pas';

var
runner : ITestRunner;
results : IRunResults;
logger : ITestLogger;
nunitLogger : ITestLogger;
begin
{\$IFDEF TESTINSIGHT}
TestInsight.DUnitX.RunRegisteredTests;
exit;
{\$ENDIF}
try
//Check command line options, will exit if invalid
TDUnitX.CheckCommandLine;
//Create the test runner
runner := TDUnitX.CreateRunner;
//Tell the runner to use RTTI to find Fixtures
runner.UseRTTI := True;
//tell the runner how we will log things
//Log to the console window
logger := TDUnitXConsoleLogger.Create(true);
//Generate an NUnit compatible XML File
nunitLogger := TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile);
runner.FailsOnNoAsserts := True; //When true, Assertions must be made during tests;

//Run tests
results := runner.Execute;
if not results.AllPassed then
System.ExitCode := EXIT_ERRORS;

{\$IFNDEF CI}
//We don't want this happening when running under CI.
if TDUnitX.Options.ExitBehavior = TDUnitXExitBehavior.Pause then
begin
System.Write('Done.. press <Enter> key to quit.');
end;
{\$ENDIF}
except
on E: Exception do
System.Writeln(E.ClassName, ': ', E.Message);
end;
end.``````

### uRationalNumbersTests.pas

``````unit uRationalNumbersTests;

interface
uses
DUnitX.TestFramework;

const
CanonicalVersion = '1.1.0.2';

type

public
[Test]
//    [Ignore('Comment the "[Ignore]" statement to run the test')]

[Test]
[Ignore]

[Test]
[Ignore]

[Test]
[Ignore]
end;

[TestFixture('Subtraction')]
TSubtractionTests = class(TObject)
public
[Test]
[Ignore]
procedure SubtractTwoPositiveRationalNumbers;

[Test]
[Ignore]
procedure SubtractAPositiveRationalNumberAndANegativeRationalNumber;

[Test]
[Ignore]
procedure SubtractTwoNegativeRationalNumbers;

[Test]
[Ignore]
procedure SubtractARationalNumberFromItself;
end;

[TestFixture('Multiplication')]
TMultiplicationTests = class(TObject)
public
[Test]
[Ignore]
procedure MultiplyTwoPositiveRationalNumbers;

[Test]
[Ignore]
procedure MultiplyANegativeRationalNumberByAPositiveRationalNumber;

[Test]
[Ignore]
procedure MultiplyTwoNegativeRationalNumbers;

[Test]
[Ignore]
procedure MultiplyARationalNumberByItsReciprocal;

[Test]
[Ignore]
procedure MultiplyARationalNumberByOne;

[Test]
[Ignore]
procedure MultiplyARationalNumberByZero;
end;

[TestFixture('Division')]
TDivisionTests = class(TObject)
public
[Test]
[Ignore]
procedure DivideTwoPositiveRationalNumbers;

[Test]
[Ignore]
procedure DivideAPositiveRationalNumberByANegativeRationalNumber;

[Test]
[Ignore]
procedure DivideTwoNegativeRationalNumbers;

[Test]
[Ignore]
procedure DivideARationalNumberByOne;

[Test]
[Ignore]
procedure DivideAWholeNumberByARationalNumber;
end;

[TestFixture('Absolute value')]
TAbsoluteValueTests = class(TObject)
public
[Test]
[Ignore]
procedure AbsoluteValueOfAPositiveRationalNumber;

[Test]
[Ignore]
procedure AbsoluteValueOfAPositiveRationalNumberWithNegativeNumeratorAndDenominator;

[Test]
[Ignore]
procedure AbsoluteValueOfANegativeRationalNumber;

[Test]
[Ignore]
procedure AbsoluteValueOfANegativeRationalNumberWithNegativeDenominator;

[Test]
[Ignore]
procedure AbsoluteValueOfZero;
end;

[TestFixture('Exponentiation of a rational number')]
TExpoRationalNumberTests = class(TObject)
public
[Test]
[Ignore]
procedure RaiseAPositiveRationalNumberToAPositiveIntegerPower;

[Test]
[Ignore]
procedure RaiseANegativeRationalNumberToAPositiveIntegerPower;

[Test]
[Ignore]
procedure RaiseZeroToAnIntegerPower;

[Test]
[Ignore]
procedure RaiseOneToAnIntegerPower;

[Test]
[Ignore]
procedure RaiseAPositiveRationalNumberToThePowerOfZero;

[Test]
[Ignore]
procedure RaiseANegativeRationalNumberToThePowerOfZero;
end;

[TestFixture('Exponentiation of a real number to a rational number')]
TExpoRealToRatNumber = class(TObject)
public
[Test]
[Ignore]
procedure RaiseARealNumberToAPositiveRationalNumber;

[Test]
[Ignore]
procedure RaiseARealNumberToANegativeRationalNumber;

[Test]
[Ignore]
procedure RaiseARealNumberToAZeroRationalNumber;
end;

[TestFixture('Reduction to lowest terms')]
TReduceTests = class(TObject)
public
[Test]
[Ignore]
procedure ReduceAPositiveRationalNumberToLowestTerms;

[Test]
[Ignore]
procedure ReduceANegativeRationalNumberToLowestTerms;

[Test]
[Ignore]
procedure ReduceARationalNumberWithANegativeDenominatorToLowestTerms;

[Test]
[Ignore]
procedure ReduceZeroToLowestTerms;

[Test]
[Ignore]
procedure ReduceAnIntegerToLowestTerms;

[Test]
[Ignore]
procedure ReduceOneToLowestTerms;
end;

implementation
uses
System.Math, uRationalNumbers;

var
LPositiveRationalValue: TFraction;
LNegativeRationalValue: TFraction;
Expected: string;
Actual: TFraction;
begin
Expected := '-1/6';
LPositiveRationalValue := TFraction.CreateFrom(1, 2);
LNegativeRationalValue := TFraction.CreateFrom(2, 3);
Actual := LPositiveRationalValue + -LNegativeRationalValue;
Assert.AreEqual(Expected, string(Actual));
end;

var
LRationalNumber: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '0/1';
LRationalNumber := TFraction.CreateFrom(1, 2);
Actual := LRationalNumber + -LRationalNumber;
Assert.AreEqual(Expected, string(actual));
end;

var
lNegFracA: TFraction;
lNegFracB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '-7/6';
LNegFracA := TFraction.CreateFrom(-1, 2);
LNegFracB := TFraction.CreateFrom(-2 ,3);
Actual := LNegFracA + LNegFracB;
Assert.AreEqual(Expected, string(actual));
end;

var
lPosFracA: TFraction;
lPosFracB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '7/6';
LPosFracA := TFraction.CreateFrom(1, 2);
LPosFracB := TFraction.CreateFrom(2 ,3);
Actual := LPosFracA + LPosFracB;
Assert.AreEqual(Expected, string(actual));
end;
{\$endregion}

{\$region 'TSubtractionTests'}

procedure TSubtractionTests.SubtractAPositiveRationalNumberAndANegativeRationalNumber;
var
LPosRatNum: TFraction;
LNegRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '7/6';
LPosRatNum := TFraction.CreateFrom(1, 2);
LNegRatNum := TFraction.CreateFrom(-2, 3);
Actual := LPosRatNum - LNegRatNum;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TSubtractionTests.SubtractARationalNumberFromItself;
var
LRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '0/1';
LRatNum := TFraction.CreateFrom(1, 2);
Actual := LRatNum - LRatNum;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TSubtractionTests.SubtractTwoNegativeRationalNumbers;
var
LNegRatNumA: TFraction;
LNegRatNumB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/6';
LNegRatNumA := TFraction.CreateFrom(-1, 2);
LNegRatNumB := TFraction.CreateFrom(-2, 3);
Actual := LNegRatNumA - LNegRatNumB;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TSubtractionTests.SubtractTwoPositiveRationalNumbers;
var
LPosRatNumA: TFraction;
LPosRatNumB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '-1/6';
LPosRatNumA := TFraction.CreateFrom(1, 2);
LPosRatNumB := TFraction.CreateFrom(2, 3);
Actual := LPosRatNumA - LPosRatNumB;
Assert.AreEqual(Expected, string(Actual));
end;
{\$endregion}

{\$region 'TMultiplicationTests'}

procedure TMultiplicationTests.MultiplyANegativeRationalNumberByAPositiveRationalNumber;
var
LNegRatA: TFraction;
LPosRatB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '-1/3';
LNegRatA := TFraction.CreateFrom(-1, 2);
LPosRatB := TFraction.CreateFrom(2, 3);
Actual := LNegRatA * LPosRatB;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TMultiplicationTests.MultiplyARationalNumberByItsReciprocal;
var
LRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/1';
LRatNum := TFraction.CreateFrom(1, 2);
Actual := LRatNum * (1 / LRatNum);
Assert.AreEqual(Expected, string(Actual));
end;

procedure TMultiplicationTests.MultiplyARationalNumberByOne;
var
LRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/2';
LRatNum := TFraction.CreateFrom(1, 2);
Actual := LRatNum * 1;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TMultiplicationTests.MultiplyARationalNumberByZero;
var
LRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '0/1';
LRatNum := TFraction.CreateFrom(1, 2);
Actual := LRatNum * 0;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TMultiplicationTests.MultiplyTwoNegativeRationalNumbers;
var
LNegRatA: TFraction;
LNegRatB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/3';
LNegRatA := TFraction.CreateFrom(-1, 2);
LNegRatB := TFraction.CreateFrom(-2, 3);
Actual := LNegRatA * LNegRatB;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TMultiplicationTests.MultiplyTwoPositiveRationalNumbers;
var
LPosRatA: TFraction;
LPosRatB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/3';
LPosRatA := TFraction.CreateFrom(1, 2);
LPosRatB := TFraction.CreateFrom(2, 3);
Actual := LPosRatA * LPosRatB;
Assert.AreEqual(Expected, string(Actual));
end;
{\$endregion}

{\$region 'TDivisionTests'}

procedure TDivisionTests.DivideAPositiveRationalNumberByANegativeRationalNumber;
var
LPosRatA: TFraction;
LNegRatB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '-3/4';
LPosRatA := TFraction.CreateFrom(1, 2);
LNegRatB := TFraction.CreateFrom(-2, 3);
Actual := LPosRatA / LNegRatB;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TDivisionTests.DivideARationalNumberByOne;
var
LRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/2';
LRatNum := TFraction.CreateFrom(1, 2);
Actual := LRatNum / 1;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TDivisionTests.DivideAWholeNumberByARationalNumber;
var
LRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '14/1';
LRatNum := TFraction.CreateFrom(2, 7);
Actual := 4 / LRatNum;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TDivisionTests.DivideTwoNegativeRationalNumbers;
var
LNegRatA: TFraction;
LNegRatB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '3/4';
LNegRatA := TFraction.CreateFrom(-1, 2);
LNegRatB := TFraction.CreateFrom(-2, 3);
Actual := LNegRatA / LNegRatB;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TDivisionTests.DivideTwoPositiveRationalNumbers;
var
LPosRatA: TFraction;
LPosRatB: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '3/4';
LPosRatA := TFraction.CreateFrom(1, 2);
LPosRatB := TFraction.CreateFrom(2, 3);
Actual := LPosRatA / LPosRatB;
Assert.AreEqual(Expected, string(Actual));
end;
{\$endregion}

{\$region 'TAbsoluteValueTests'}

procedure TAbsoluteValueTests.AbsoluteValueOfANegativeRationalNumber;
var
LNegRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/2';
LNegRatNum := TFraction.CreateFrom(-1, 2);
Actual := TFraction(Abs(LNegRatNum));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TAbsoluteValueTests.AbsoluteValueOfANegativeRationalNumberWithNegativeDenominator;
var
LNegRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/2';
LNegRatNum := TFraction.CreateFrom(1, -2);
Actual := TFraction(Abs(LNegRatNum));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TAbsoluteValueTests.AbsoluteValueOfAPositiveRationalNumber;
var
LPosRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/2';
LPosRatNum := TFraction.CreateFrom(1, 2);
Actual := TFraction(Abs(LPosRatNum));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TAbsoluteValueTests.AbsoluteValueOfAPositiveRationalNumberWithNegativeNumeratorAndDenominator;
var
LNegRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/2';
LNegRatNum := TFraction.CreateFrom(-1, -2);
Actual := TFraction(Abs(LNegRatNum));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TAbsoluteValueTests.AbsoluteValueOfZero;
var
LZero: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '0/1';
LZero := TFraction.CreateFrom(0, 1);
Actual := TFraction(Abs(LZero));
Assert.AreEqual(Expected, string(Actual));
end;
{\$endregion}

{\$region 'TExpoRationalNumberTests'}

procedure TExpoRationalNumberTests.RaiseANegativeRationalNumberToAPositiveIntegerPower;
var
NegRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '-1/8';
NegRatNum := TFraction.CreateFrom(-1, 2);
Actual := TFraction(System.Math.Power(NegRatNum,3));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TExpoRationalNumberTests.RaiseANegativeRationalNumberToThePowerOfZero;
var
NegRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/1';
NegRatNum := TFraction.CreateFrom(-1, 2);
Actual := TFraction(System.Math.Power(NegRatNum,0));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TExpoRationalNumberTests.RaiseAPositiveRationalNumberToAPositiveIntegerPower;
var
PosRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/8';
PosRatNum := TFraction.CreateFrom(1, 2);
Actual := TFraction(System.Math.Power(PosRatNum, 3));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TExpoRationalNumberTests.RaiseAPositiveRationalNumberToThePowerOfZero;
var
PosRatNum: TFraction;
Actual: TFraction;
Expected: string;
begin
Expected := '1/1';
PosRatNum := TFraction.CreateFrom(1, 2);
Actual := TFraction(System.Math.Power(PosRatNum, 0));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TExpoRationalNumberTests.RaiseOneToAnIntegerPower;
var
Actual: TFraction;
Expected: string;
begin
Expected := '1/1';
Actual := TFraction(System.Math.Power(1, 4));
Assert.AreEqual(Expected, string(Actual));
end;

procedure TExpoRationalNumberTests.RaiseZeroToAnIntegerPower;
var
Actual: TFraction;
Expected: string;
begin
Expected := '0/1';
Actual := TFraction(System.Math.Power(0, 5));
Assert.AreEqual(Expected, string(Actual));
end;
{\$endregion}

{\$region 'TExpoRealToRatNumber'}

procedure TExpoRealToRatNumber.RaiseARealNumberToANegativeRationalNumber;
var
NegRatNum: TFraction;
Actual: Double;
Expected: Double;
begin
Expected := 1 / 3;
NegRatNum := TFraction.CreateFrom(-1, 2);
Actual := System.Math.Power(9, NegRatNum);
Assert.AreEqual(Expected, Actual);
end;

procedure TExpoRealToRatNumber.RaiseARealNumberToAPositiveRationalNumber;
var
PosRatNum: TFraction;
Actual: Double;
Expected: Double;
begin
Expected := 16.0;
PosRatNum := TFraction.CreateFrom(4, 3);
Actual := System.Math.Power(8, PosRatNum);
Assert.AreEqual(Expected, Actual);
end;

procedure TExpoRealToRatNumber.RaiseARealNumberToAZeroRationalNumber;
var
ZeroRatNum: TFraction;
Actual: Double;
Expected: Double;
begin
Expected := 1.0;
ZeroRatNum := TFraction.CreateFrom(0, 1);
Actual := System.Math.Power(2, ZeroRatNum);
Assert.AreEqual(Expected, Actual);
end;
{\$endregion}

{\$region 'TReduceTests'}

procedure TReduceTests.ReduceANegativeRationalNumberToLowestTerms;
var
Actual: TFraction;
Expected: string;
begin
Expected := '-2/3';
Actual := TFraction.CreateFrom(-4, 6).Reduced;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TReduceTests.ReduceAnIntegerToLowestTerms;
var
Actual: TFraction;
Expected: string;
begin
Expected := '-2/1';
Actual := TFraction.CreateFrom(-14, 7).Reduced;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TReduceTests.ReduceAPositiveRationalNumberToLowestTerms;
var
Actual: TFraction;
Expected: string;
begin
Expected := '1/2';
Actual := TFraction.CreateFrom(2, 4).Reduced;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TReduceTests.ReduceARationalNumberWithANegativeDenominatorToLowestTerms;
var
Actual: TFraction;
Expected: string;
begin
Expected := '-1/3';
Actual := TFraction.CreateFrom(3, -9).Reduced;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TReduceTests.ReduceOneToLowestTerms;
var
Actual: TFraction;
Expected: string;
begin
Expected := '1/1';
Actual := TFraction.CreateFrom(13, 13).Reduced;
Assert.AreEqual(Expected, string(Actual));
end;

procedure TReduceTests.ReduceZeroToLowestTerms;
var
Actual: TFraction;
Expected: string;
begin
Expected := '0/1';
Actual := TFraction.CreateFrom(0, 6).Reduced;
Assert.AreEqual(Expected, string(Actual));
end;
{\$endregion}

initialization
TDUnitX.RegisterTestFixture(TSubtractionTests);
TDUnitX.RegisterTestFixture(TMultiplicationTests);
TDUnitX.RegisterTestFixture(TDivisionTests);
TDUnitX.RegisterTestFixture(TAbsoluteValueTests);
TDUnitX.RegisterTestFixture(TExpoRationalNumberTests);
TDUnitX.RegisterTestFixture(TExpoRealToRatNumber);
TDUnitX.RegisterTestFixture(TReduceTests);
end.``````
``````unit uRationalNumbers;

interface

type
TFraction = Record
private
FNumerator: Integer;
FDenominator: Integer;
function GetNumerator(): Integer;
function GetDenominator(): Integer;
public
function Reduced(): TFraction;
property Numerator : Integer read GetNumerator;
class operator Add(const ALeft, ARight: TFraction): TFraction;
class operator Divide(const ALeft, ARight: TFraction): TFraction;
class operator Explicit(const AValue: TFraction): string;
class operator Explicit(const Avalue: Double): TFraction;
class operator Implicit(const AValue: TFraction): Double;
class operator Implicit(const AValue: Integer): TFraction;
class operator Multiply(const ALeft, ARight: TFraction): TFraction;
class operator Negative(const AValue: TFraction): TFraction;
class operator Subtract(const ALeft, ARight: TFraction): TFraction;
end;

implementation

uses
System.Math, System.SysUtils;

begin
raise EArgumentException.Create('Creation : Denominator cannot be 0.');
FNumerator := ANumerator;
end;

function TFraction.GetNumerator(): Integer;
begin
if FDenominator < 0 then
Result :=  - FNumerator
else
Result := FNumerator;
end;

function TFraction.GetDenominator(): Integer;
begin
Result := Abs(FDenominator);
end;

function TFraction.Reduced(): TFraction;
var
min, max: integer;
begin
// Par défaut, pas de commun diviseur => on renvoie les mêmes valeurs.
Result.FNumerator := Numerator;
Result.FDenominator := Denominator;
// Si le numérateur = 0, on met le dénominateur à 1.
if (Numerator = 0) then
begin
Result := TFraction.CreateFrom(0, 1);
Exit;
end;
min := MinIntValue([Abs(Numerator), Denominator]);
max := MaxIntValue([Abs(Numerator), Denominator]);
// Le numérateur et le dénominateur sont multiples entre eux.
if (max mod min = 0) then
begin
Result.FNumerator := Numerator div min;
Result.FDenominator := Denominator div min;
Exit;
end;
// On cherche un commun diviseur en partant du plus petit facteur de la fraction
// divisé par 2 par pas de 1 en descendant jusque 2.
for var I := min div 2 downto 2 do
if (Numerator mod I = 0) and (Denominator mod I = 0) then
begin
Result.FNumerator := Numerator div I;
Result.FDenominator := Denominator div I;
end;
end;

class operator TFraction.Add(const ALeft, ARight: TFraction): TFraction;
begin
Result.FNumerator := (ALeft.Numerator * ARight.Denominator) +
(ARight.Numerator * ALeft.Denominator);
Result.FDenominator := ALeft.Denominator * ARight.Denominator;
Result := Result.Reduced;
end;

class operator TFraction.Divide(const ALeft, ARight: TFraction): TFraction;
begin
if ALeft.Denominator * ARight.Numerator = 0 then
raise Exception.Create('Division : Denominator cannot be 0.');
Result.FNumerator := ALeft.Numerator * ARight.Denominator;
Result.FDenominator := ALeft.Denominator * ARight.Numerator;
Result := Result.Reduced;
end;

class operator TFraction.Explicit(const AValue: TFraction): string;
begin
Result :=  IntToStr(AValue.Numerator) + '/' + IntToStr(AValue.Denominator);
end;

class operator TFraction.Explicit(const AValue: Double): TFraction;
const
Multiply = 10000;
begin
Result.FNumerator := Round(Avalue * Multiply);
Result.FDenominator := Multiply;
Result := Result.Reduced;
end;

class operator TFraction.Implicit(const AValue: TFraction): Double;
begin
Result := AValue.Numerator / AValue.Denominator;
end;

class operator TFraction.Implicit(const AValue: Integer): TFraction;
begin
Result.FNumerator := AValue;
Result.FDenominator := 1;
end;

class operator TFraction.Multiply(const ALeft, ARight: TFraction): TFraction;
begin
Result.FNumerator := ALeft.Numerator * ARight.Numerator;
Result.FDenominator := ALeft.Denominator * ARight.Denominator;
Result := Result.Reduced;
end;

class operator TFraction.Negative(const AValue: TFraction): TFraction;
begin
Result.FNumerator := - AValue.FNumerator;
Result.FDenominator := AValue.FDenominator;
end;

class operator TFraction.Subtract(const ALeft, ARight: TFraction): TFraction;
begin
Result.FNumerator := (ALeft.Numerator * ARight.Denominator) -
(ARight.Numerator * ALeft.Denominator);
Result.FDenominator := ALeft.Denominator * ARight.Denominator;
Result := Result.Reduced;
end;

end.``````

### What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

• What compromises have been made?