MathgeomGLS
1138 строк · 37.7 Кб
1{---------------------------------------------------------------------------}
2{ }
3{ File: Velthuis.BigRationals.pas }
4{ Function: A multiple precision rational nubmer implementation, based }
5{ on the BigInteger implementation in }
6{ Velthuis.BigIntegers.pas. }
7{ Language: Delphi version XE2 or later }
8{ Author: Rudy Velthuis }
9{ Copyright: (c) 2016,2017 Rudy Velthuis }
10{ }
11{ License: Redistribution and use in source and binary forms, with or }
12{ without modification, are permitted provided that the }
13{ following conditions are met: }
14{ }
15{ * Redistributions of source code must retain the above }
16{ copyright notice, this list of conditions and the following }
17{ disclaimer. }
18{ * Redistributions in binary form must reproduce the above }
19{ copyright notice, this list of conditions and the following }
20{ disclaimer in the documentation and/or other materials }
21{ provided with the distribution. }
22{ }
23{ Disclaimer: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" }
24{ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT }
25{ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND }
26{ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO }
27{ EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE }
28{ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, }
29{ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, }
30{ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, }
31{ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED }
32{ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT }
33{ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) }
34{ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF }
35{ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. }
36{ }
37{---------------------------------------------------------------------------}
38
39unit Velthuis.BigRationals experimental;40
41//$$RV comments are internal comments by RV, denoting a problem. Should be all removed as soon as problem solved.
42// TODO: handle extended properly, e.g. in Create(Extended)
43
44// To implement:
45// Create(Value: Double; MaxDenominator: Integer); -- see below
46// AsSingle
47// AsDouble
48// AsExtended
49// AsBigDecimal()
50// AsBigDecimal(RoundingMode)
51// AsBigDecimal(Scale, RoundingMode)
52// AsInteger
53// AsInt64
54// Add(BigRational, BigRational)
55// Add(BigRational, Integer)
56// Add(BigRational, Int64)
57// Add(BigRational, BigInteger)
58// same for Subtract, Multiply and Divide
59// Negate
60// PercentageValue ?
61// Pow(Exponent: Integer)
62// Pow(Exponent: Int64);
63// Pow(Exponent: BigInteger);
64// Pow(Exponent: Double): Double;
65// My own:
66// ToMixedString (returns "97/15" as "6 7/15")
67// IsProper (proper fraction is when num < denom)
68
69interface
70
71uses
72Velthuis.BigIntegers, Velthuis.BigDecimals, System.Math, System.SysUtils, CompilerAndRTLVersions;73
74{$IF CompilerVersion < CompilerVersionDelphiXE8}
75{$IF (DEFINED(WIN32) OR DEFINED(CPUX86)) AND NOT DEFINED(CPU32BITS)}76{$DEFINE CPU32BITS}77{$IFEND}78{$IF (DEFINED(WIN64) OR DEFINED(CPUX64)) AND NOT DEFINED(CPU64BITS)}79{$DEFINE CPU64BITS}80{$IFEND}81{$IFEND}
82
83{$IF CompilerVersion >= CompilerVersionDelphiXE3}
84{$LEGACYIFEND ON}85{$IFEND}
86
87{$IF CompilerVersion >= CompilerVersionDelphiXE}
88{$CODEALIGN 16}89{$ALIGN 16}90{$IFEND}
91
92{$IF CompilerVersion >= CompilerVersionDelphi2010}
93{$DEFINE HasClassConstructors}94{$IFEND}
95
96{$IF SizeOf(Extended) > SizeOf(Double)}
97{$DEFINE HasExtended}98{$IFEND}
99
100type
101PBigRational = ^BigRational;102
103/// <summary>BigRational is a multiple precision rational data type, where each value is expressed as the quotient104/// of two BigIntegers, the numerator and the denominator.</summary>105/// <remarks><para>BigRationals are simplified, i.e. common factors in numerator and denominator are eliminated.106/// </para>107/// <para>The resulting sign is always moved to the numerator. The denominator must always be unsigned.</para>108/// </remarks>109BigRational = record110private111type112// Error code for the Error procedure.113TErrorCode = (ecParse, ecDivByZero, ecConversion, ecInvalidArg, ecZeroDenominator);114
115var116// The numerator (the "top" part of the fraction).117FNumerator: BigInteger;118// The denominator (the "bottom" part of the fraction).119FDenominator: BigInteger;120
121class var122// Field for AlwaysReduce property.123FAlwaysReduce: Boolean;124
125// Returns -1 for negative values, 1 for positive values and 0 for zero.126function GetSign: Integer;127
128// Checks for invalid values, simplifies the fraction by eliminating common factors in numerator and denominator,129// and moves sign to numerator. In other words, turns a BigRational into its canonical form.130procedure Normalize(Forced: Boolean = False);131
132// Raises exception using error code and additional data to decide which and how.133procedure Error(Code: TErrorCode; ErrorInfo: array of const);134public135const136MinEpsilon = 5e-10;137
138class var139/// <summary>Predefined BigRational value 0.</summary>140Zero: BigRational;141
142/// <summary>Predefined BigRational value 1/10.</summary>143OneTenth: BigRational;144
145/// <summary>Predefined BigRational value 1/4.</summary>146OneFourth: BigRational;147
148/// <summary>Predefined BigRational value 1/3.</summary>149OneThird: BigRational;150
151/// <summary>Predefined BigRational value 1/2.</summary>152OneHalf: BigRational;153
154/// <summary>Predefined BigRational value 2/3.</summary>155TwoThirds: BigRational;156
157/// <summary>Predefined BigRational value 3/4.</summary>158ThreeFourths: BigRational;159
160/// <summary>Predefined BigRational value 1.</summary>161One: BigRational;162
163/// <summary>Predefined BigRational value 10.</summary>164Ten: BigRational;165
166{$IFDEF HasClassConstructors}
167class constructor Initialize;168{$ENDIF}
169
170// -- Constructors --171
172/// <summary>Creates a new BigRational with the given values as numerator and denominator, respectively. The173/// sign is adjusted thus, that the denominator is positive.</summary>174constructor Create(const Numerator, Denominator: BigInteger); overload;175
176/// <summary>Creates a new BigRational from the given value, with a denominator of 1.</summary>177constructor Create(const Value: BigInteger); overload;178
179/// <summary>Creates a new BigRational from the given value, with a denominator of 1.</summary>180constructor Create(const Value: Integer); overload;181
182/// <summary>Creates a new BigRational from the given values.</summary>183constructor Create(const Numerator: Integer; const Denominator: Cardinal); overload;184
185/// <summary>Creates a new BigRational with the exact same value as the given Double value.</summary>186/// <exception cref="EInvalidArgument">EInvalidArgument is raised if the Double represents a positive or187/// negative infinity or a NaN.</exception>188/// <remarks>Note that this is an exact conversion, taking into account the exact bit representation of the189/// double. This means that the numerator and denominator values can be rather big.</remarks>190constructor Create(const Value: Double); overload;191
192/// <summary>Creates a new BigRational with the same value as the given Double value. For this, it uses193/// at most MaxIterations and the error is at most Epsilon.</summary>194/// <exception cref="EInvalidArgument">EInvalidArgument is raised if the Double represents a positive or195/// negative infinity or a NaN.</exception>196/// <exception cref="EOverflow">EOverflow is raised when the Double represents a value that is not representable197/// as a fraction of Integers.</exception>198/// <remarks><para>Note that this conversion creates enumerator and denominator that are at most Integer199/// values.</para>200/// <para>This uses a conversion to a finite continued fraction combined with the unfolding to a simple fraction201/// in one loop, steadily refining the fraction. MaxIterations and Epsilon determine when the loop ends.202// MaxIterations governs the maximum number of iterations in the loop, and Epsilon governs the maximum difference203// between the double and the fraction.</para>204/// <para>Typical values are MaxIterations = 15 and Epsilon = 4e-10 (which is close to 1/MaxInt).</para></remarks>205constructor Create(Value, Epsilon: Double; MaxIterations: Integer); overload;206
207/// <summary>Creates a new BigRational with the best matching value, with a denominator value of at most208/// MaxDenominator</summary>209constructor Create(F: Double; MaxDenominator: Cardinal); overload;210
211//{$IFDEF HasExtended}212// /// <summary>Creates a new BigRational with the exact same value as the given Extended value.</summary>
213// /// <exception cref="EInvalidArgument">EInvalidArgument is raised if the Extended represents a positive or
214// /// negative infinity or a NaN.</exception>
215// /// <remarks>Note that this is an exact conversion, taking into account the exact bit representation of the
216// /// Extended. This means that the numerator and denominator values can be rather big.</remarks>
217// constructor Create(const Value: Extended); overload;
218//{$ENDIF}
219
220/// <summary>Creates a new BigRational from the given value, with a denominator of 1.</summary>221constructor Create(const Value: Int64); overload;222
223/// <summary>Creates a new BigRational from the given values.</summary>224constructor Create(const Numerator: Int64; const Denominator: UInt64); overload;225
226/// <summary>Creates a new BigRational from the given string, which should be in the format227/// [optional sign] + numerator + '/' + denominator.</summary>228/// <remarks>Example input: '-1234/5678'.</remarks>229constructor Create(const Value: string); overload;230
231/// <summary>Creates a new BigRational from the given BigDecimal.</summary>232constructor Create(const Value: BigDecimal); overload;233
234// TODO: Create(const Value: Double; MaxDenominator: Integer); overload; // gets best approximation235// TODO: Create(const Value: Double; MaxEpsilon: Double); overload; // gets best approximation236
237
238// -- Mathematical operators and functions --239
240/// <summary>Adds two BigRationals and returns the sum. Simplifies the result.</summary>241class function Add(const Left, Right: BigRational): BigRational; static;242
243/// <summary>Adds two BigRationals and returns the sum. Simplifies the result.</summary>244class operator Add(const Left, Right: BigRational): BigRational;245
246/// <summary>Subtracts two BigRationals and returns the difference. Simplifies the result.</summary>247class function Subtract(const Left, Right: BigRational): BigRational; static;248
249/// <summary>Subtracts two BigRationals and returns the difference. Simplifies the result.</summary>250class operator Subtract(const Left, Right: BigRational): BigRational;251
252/// <summary>Multiplies two BigRationals and returns the product. Simplifies the result.</summary>253class function Multiply(const Left, Right: BigRational): BigRational; static;254
255/// <summary>Multiplies two BigRationals and returns the product. Simplifies the result.</summary>256class operator Multiply(const Left, Right: BigRational): BigRational;257
258/// <summary>Divides two BigRationals by multiplying Left by the reciprocal of Right. Returns the quotient.259/// Simplifies the result.</summary>260class function Divide(const Left, Right: BigRational): BigRational; static;261
262/// <summary>Divides two BigRationals by multiplying Left by the reciprocal of Right. Returns the quotient.263/// Simplifies the result.</summary>264class operator Divide(const Left, Right: BigRational): BigRational;265
266/// <summary>Divides two BigRationals returning a BigInteger result.</summary>267class operator IntDivide(const Left, Right: BigRational): BigInteger;268
269/// <summary>Divides two BigRationals returning the remainder. Simplifies the result.</summary>270class operator Modulus(const Left, Right: BigRational): BigRational;271
272/// <summary>Divides two BigRationals returning the remainder. Simplifies the result.</summary>273class function Remainder(const Left, Right: BigRational): BigRational; static;274
275/// <summary>Divides two BigRationals and returns the quotient and remainder.</summary>276class procedure DivMod(const Left, Right: BigRational; var Quotient: BigInteger; var Remainder: BigRational); static;277
278/// <summary>Returns the negation of the given BigRational value.</summary>279class operator Negative(const Value: BigRational): BigRational;280
281// Instance functions282
283function IsNegative: Boolean; inline;284function IsPositive: Boolean; inline;285function IsZero: Boolean; inline;286
287/// <summary>Returns the absolute value of the current BigRational.</summary>288function Abs: BigRational;289
290/// <summary>Returns the negation of the current BigRational.</summary>291function Negate: BigRational;292
293/// <summary>Returns the multiplicative inverse of the current BigRational value by swapping numerator and294/// denominator.</summary>295function Reciprocal: BigRational;296
297/// <summary>Reduces numerator and denominator to their smallest values representing the same ratio.</summary>298function Reduce: BigRational;299
300
301// -- Comparison and relational operators --302
303/// <summary>Compares two BigRationals. Returns -1 if Left < Right, 1 if Left > Right and304/// 0 if Left = Right.</summary>305class function Compare(const Left, Right: BigRational): Integer; static;306
307/// <summary>Returns True only if Left < Right.</summary>308class operator LessThan(const Left, Right: BigRational): Boolean;309
310/// <summary>Returns True only if Left <= Right.</summary>311class operator LessThanOrEqual(const Left, Right: BigRational): Boolean;312
313/// <summary>Returns True only if Left = Right.</summary>314class operator Equal(const Left, Right: BigRational): Boolean;315
316/// <summary>Returns True only if Left >= Right.</summary>317class operator GreaterThanOrEqual(const Left, Right: BigRational): Boolean;318
319/// <summary>Returns True only if Left > Right.</summary>320class operator GreaterThan(const Left, Right: BigRational): Boolean;321
322/// <summary>Returns True only if Left <> Right.</summary>323class operator NotEqual(const Left, Right: BigRational): Boolean;324
325
326// --- Conversion operators --327
328/// <summary>Converts the string to a BigRational.</summary>329/// <exception cref="EConvertError"></exception>330class operator Implicit(const Value: string): BigRational;331
332/// <summary>Explicitly converts the BigRational to a string (using ToString).</summary>333class operator Explicit(const Value: BigRational): string;334
335/// <summary>Converts the integer to a BigRational.</summary>336class operator Implicit(const Value: Integer): BigRational;337
338/// <summary>Converts the BigRational to an Integer. If necessary, excess top bits are cut off.</summary>339class operator Explicit(const Value: BigRational): Integer;340
341/// <summary>Converts the Int64 to a BigRational.</summary>342class operator Implicit(const Value: Int64): BigRational;343
344/// <summary>Converts the BigRational to an Int64. If necessary, excess top bits are cut off.</summary>345class operator Explicit(const Value: BigRational): Int64;346
347/// <summary>Converts the given Double to a BigRational.</summary>348/// <exception cref="EInvalidArgument">Raises an EInvalidArgument exception if Value represents349/// (+/-)infinity or NaN.</exception>350class operator Implicit(const Value: Double): BigRational;351
352/// <summary>Converts the given BigRational to a Double.</summary>353class operator Explicit(const Value: BigRational): Double;354
355/// <summary>Converts the given BigDecimal to a BigRational.</summary>356/// <remarks>This conversion is exact.</remarks>357class operator Implicit(const Value: BigDecimal): BigRational;358
359/// <summary>Converts the given BigRational to a BigDecimal, using the default BigDecimal precision and360/// rounding mode.</summary>361class operator Implicit(const Value: BigRational): BigDecimal;362
363/// <summary>Converts the current BigRational to a string. This string can be used as valid input for364/// conversion.</summary>365function ToString: string;366
367function Parse(const S: string): BigRational;368function TryParse(const S: string; out Value: BigRational): Boolean;369
370
371// -- Properties --372
373/// <summary>The numerator of the fraction represented by the current BigRational.</summary>374property Numerator: BigInteger read FNumerator;375
376/// <summary>The denominator of the fraction represented by the current BigRational.</summary>377property Denominator: BigInteger read FDenominator;378
379/// <summary>The sign of the fraction represented by the current BigRational.</summary>380property Sign: Integer read GetSign;381
382/// <summary>If AlwaysReduce is set (default), fractions like 2/10 are reduced to 1/5.383/// If it is not set, you can manually call Reduce on any BigRational.</summary>384class property AlwaysReduce: Boolean read FAlwaysReduce write FAlwaysReduce;385
386end;387
388implementation
389
390uses
391Velthuis.FloatUtils, Velthuis.StrConsts;392
393{ BigRational }
394
395function BigRational.Abs: BigRational;396begin
397if Self.FNumerator.IsNegative then398Result := Self.Negate399else400Result := Self;401end;402
403class operator BigRational.Add(const Left, Right: BigRational): BigRational;404begin
405if Left.FDenominator = Right.FDenominator then406begin407Result.FNumerator := Left.FNumerator + Right.FNumerator;408Result.FDenominator := Left.FDenominator;409end410else411begin412Result.FDenominator := Left.FDenominator * Right.FDenominator;413Result.FNumerator := Left.FNumerator * Right.FDenominator + Right.FNumerator * Left.FDenominator;414end;415Result.Normalize;416end;417
418class function BigRational.Add(const Left, Right: BigRational): BigRational;419begin
420Result := Left + Right;421end;422
423constructor BigRational.Create(const Value: Integer);424begin
425FNumerator := BigInteger(Value);426FDenominator := BigInteger.One;427end;428
429constructor BigRational.Create(const Numerator: Integer; const Denominator: Cardinal);430begin
431FNumerator := BigInteger(Numerator);432FDenominator := BigInteger(Denominator);433Normalize;434end;435
436constructor BigRational.Create(const Numerator, Denominator: BigInteger);437begin
438FNumerator := Numerator;439FDenominator := Denominator;440Normalize;441end;442
443constructor BigRational.Create(const Value: BigInteger);444begin
445FNumerator := Value;446FDenominator := BigInteger.One;447end;448
449(*
450Test with e.g.
451
4520.750000000 1 / 1 3 / 4 3 / 4 3 / 4 3 / 4
4530.518518000 1 / 1 1 / 2 14 / 27 14 / 27 14 / 27
4540.905405400 1 / 1 9 / 10 67 / 74 67 / 74 67 / 74
4550.142857143 0 / 1 1 / 7 1 / 7 1 / 7 1 / 7
4563.141592654 3 / 1 22 / 7 22 / 7 355 / 113 355 / 113
4572.718281828 3 / 1 19 / 7 193 / 71 1457 / 536 25946 / 9545
458-0.423310825 0 / 1 -3 / 7 -11 / 26 -69 / 163 -1253 / 2960
45931.415926536 31 / 1 157 / 5 377 / 12 3550 / 113 208696 / 6643
4600.000000000
461*)
462
463constructor BigRational.Create(F: Double; MaxDenominator: Cardinal);464
465// https://rosettacode.org/wiki/Convert_decimal_number_to_rational#C
466
467var
468A: Int64;469H, K: array[0..2] of Int64;470X, D, N: Int64;471I: Integer;472LNegative: Boolean;473MustBreak: Boolean;474begin
475H[0] := 0; H[1] := 1; H[2] := 0;476K[0] := 1; K[1] := 0; K[2] := 0;477N := 1;478
479if MaxDenominator <= 1 then480begin481FDenominator := 1;482FNumerator := Trunc(F);483Exit;484end;485
486LNegative := F < 0;487if LNegative then488F := -F;489
490while (F <> System.Trunc(F)) and (N < (High(Int64) shr 1)) do491begin492N := N shl 1;493F := F * 2;494end;495D := System.Trunc(F);496
497// Continued fraction and check denominator each step498for I := 0 to 63 do499begin500MustBreak := False;501if N <> 0 then502A := D div N503else504A := 0;505
506if (I <> 0) and (A = 0) then507Break;508
509if N = 0 then510Break;511
512X := D;513D := N;514N := X mod N;515
516X := A;517if K[1] * A + K[0] >= MaxDenominator then518begin519X := (MaxDenominator - K[0]) div K[1];520if (X * 2 >= A) or (K[1] >= MaxDenominator) then521MustBreak := True522else523Break;524end;525H[2] := X * H[1] + H[0]; H[0] := H[1]; H[1] := H[2];526K[2] := X * K[1] + K[0]; K[0] := K[1]; K[1] := K[2];527if MustBreak then528Break;529end;530FDenominator := K[1];531if LNegative then532FNumerator := -H[1]533else534FNumerator := H[1];535end;536
537constructor BigRational.Create(const Value: Double);538var
539Exponent: Integer;540Mantissa: Int64;541begin
542if IsInfinite(Value) or IsNaN(Value) then543Error(ecInvalidArg, ['Double']);544
545if Value = 0.0 then546begin547FNumerator := BigInteger.Zero;548FDenominator := BigInteger.One;549Exit;550end;551
552Exponent := GetExponent(Value);553Mantissa := GetMantissa(Value);554
555if IsDenormal(Value) then556begin557FNumerator := Mantissa;558FDenominator := BigInteger.Zero.FlipBit(1023 + 52);559end560else561begin562FDenominator := BigInteger.Zero.FlipBit(52);563FNumerator := Mantissa;564if Exponent < 0 then565FDenominator := FDenominator shl -Exponent566else567FNumerator := FNumerator shl Exponent;568end;569if Value < 0 then570FNumerator := -FNumerator;571Normalize;572end;573
574constructor BigRational.Create(Value, Epsilon: Double; MaxIterations: Integer);575
576//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
577// //
578// One way to solve this is to convert the Value into a (finite) continous fraction, i.e. a fraction in the form //
579// a0 + 1/(a1 + 1/(a2 + 1/(a3 + ...))) or, often also written as [a0; a1, a2, a3 ...]. //
580// If we reverse this using numerator and denominator, we can easily construct a suitable normal fraction. //
581// //
582// This constructor does something similar, except that the loop to construct a continued fraction and the loop //
583// to decompose the continued fraction into a simple fraction are folded together. This repeatedly refines the //
584// fraction, until the maximum number of iterations is reached, or the difference between fraction and Value is //
585// less than the given epsilon, whichever comes first. //
586// //
587// This uses numerators and denominators as much as possible in the Integer range. A tested version using //
588// BigDecimals and BigIntegers was far too precise and did not stop early enough. //
589// //
590// There is a MinEpsilon constant of value 5e-10, which is slightly above 1/MaxInt. Smaller values could cause //
591// invalid results. //
592// //
593//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
594
595(*
596function ConvertDoubleToRational(Value, Epsilon: Double; MaxIterations: Integer; var FNumerator, FDenominator: Int64): Double; overload;
597*)
598var
599LNegative: Boolean;600LQuot: Double;601LError: Double;602I: Integer;603NewRatio, Ratio: Double;604Rest, Rest0, Inverse, Inverse0: Double;605K, H: array[0..2] of Int64;606A: Int64;607LNum, LDenom: BigInteger;608begin
609LQuot := 0.0;610
611if IsInfinite(Value) or IsNaN(Value) then612Error(ecInvalidArg, ['Double']);613
614LNegative := Value < 0;615if LNegative then616Value := -Value;617
618if Value > MaxInt then619begin620// reduce Value to range [0..MaxInt)621LQuot := Int(Value / MaxInt) * MaxInt;622Value := Value - LQuot;623// Hmmm... turn it into a suitable range624// FloatMod(Value, MaxInt, Quot, Rem);625// Quot := Int(Value / MaxInt);626// Value := Value - Quot * MaxInt;627// work on rem --> value, remember quot;628// raise EOverflow.CreateFmt('Value %g cannot be converted to an integer ratio', [Value]);629end;630
631if Value = 0.0 then632begin633FNumerator := BigInteger.Zero;634FDenominator := BigInteger.One;635Exit;636end;637
638if Epsilon = 0 then639Epsilon := 5e-10;640
641I := 0;642while I < MaxIterations do643begin644if I = 0 then645begin646A := Trunc(Value);647Rest := Value - A;648if Rest = 0.0 then649begin650FNumerator := A;651FDenominator := 1;652Exit;653end;654Inverse := 1.0 / Rest;655K[2] := A;656H[2] := 1;657NewRatio := K[2] / H[2];658LError := System.Abs(Value - NewRatio);659end660else661begin662A := Trunc(Inverse0);663Rest := Inverse0 - A;664if Rest = 0.0 then665Rest := Epsilon;666Inverse := 1.0 / Rest;667if I = 1 then668begin669K[2] := A * K[1] + 1;670H[2] := A * H[1];671end672else673begin674if (A > MaxInt) or (K[1] > MaxInt) or (H[1] > MaxInt) then675Break;676K[2] := A * K[1] + K[0];677H[2] := A * H[1] + H[0];678end;679NewRatio := K[2] / H[2];680LError := System.Abs(NewRatio - Ratio);681end;682if LError < Epsilon then683Break684else685Ratio := NewRatio;686Inc(I);687K[0] := K[1];688K[1] := K[2];689H[0] := H[1];690H[1] := H[2];691Inverse0 := Inverse;692Rest0 := Rest;693if Inverse0 > MaxInt then694Break;695end;696LNum := K[1];697LDenom := H[1];698if LQuot > 0.0 then699LNum := LNum + BigInteger(LQuot) * LDenom;700if LNegative then701LNum := -LNum;702
703FNumerator := LNum;704FDenominator := LDenom;705Normalize;706end;707
708class function BigRational.Compare(const Left, Right: BigRational): Integer;709begin
710if Left.FDenominator = Right.FDenominator then711Result := BigInteger.Compare(Left.FNumerator, Right.FNumerator)712else713Result := BigInteger.Compare(Left.FNumerator * Right.FDenominator,714Right.FNumerator * Left.FDenominator);715end;716
717constructor BigRational.Create(const Numerator: Int64; const Denominator: UInt64);718begin
719FNumerator := BigInteger(Numerator);720FDenominator := BigInteger(Denominator);721Normalize;722end;723
724constructor BigRational.Create(const Value: Int64);725begin
726FNumerator := BigInteger(Value);727FDenominator := BigInteger.One;728end;729
730constructor BigRational.Create(const Value: string);731begin
732Self := Parse(Value);733end;734
735constructor BigRational.Create(const Value: BigDecimal);736var
737Num, Denom: BigInteger;738Scale: Integer;739begin
740Num := Value.UnscaledValue;741Scale := Value.Scale;742Denom := BigInteger.One;743
744if Scale < 0 then745Num := Num * BigInteger.Pow(BigInteger.Ten, -Scale)746else if Scale > 0 then747Denom := BigInteger.Pow(BigInteger.Ten, Scale);748
749Create(Num, Denom);750end;751
752//{$IFDEF HasExtended}
753//constructor BigRational.Create(const Value: Extended);
754//var
755// D: Double;
756//begin
757// D := Value;
758// Create(D);
759//end;
760//{$ENDIF HasExtended}
761
762class function BigRational.Divide(const Left, Right: BigRational): BigRational;763begin
764Result := Left / Right;765end;766
767class operator BigRational.Divide(const Left, Right: BigRational): BigRational;768begin
769if Left.FDenominator = Right.FDenominator then770begin771Result.FNumerator := Left.FNumerator;772Result.FDenominator := Right.FNumerator;773end774else775begin776Result.FNumerator := Left.FNumerator * Right.FDenominator;777Result.FDenominator := Left.FDenominator * Right.FNumerator;778end;779Result.Normalize;780end;781
782class procedure BigRational.DivMod(const Left, Right: BigRational;783var Quotient: BigInteger; var Remainder: BigRational);784var
785AD, BC: BigInteger;786begin
787if Left.FDenominator = Right.FDenominator then788begin789AD := Left.FNumerator;790BC := Right.FNumerator;791end792else793begin794AD := Left.FNumerator * Right.FDenominator;795BC := Left.FDenominator * Right.FNumerator;796end;797
798Quotient := AD div BC;799
800Remainder.FNumerator := AD - Quotient * BC;801Remainder.FDenominator := Left.FDenominator * Right.FDenominator;802Remainder.Normalize;803end;804
805class operator BigRational.Equal(const Left, Right: BigRational): Boolean;806begin
807Result := Compare(Left, Right) = 0;808end;809
810procedure BigRational.Error(Code: TErrorCode; ErrorInfo: array of const);811begin
812case Code of813ecParse:814raise EConvertError.CreateFmt(SErrorParsingFmt, ErrorInfo);815ecDivByZero:816raise EDivByZero.Create(SDivisionByZero);817ecConversion:818raise EConvertError.CreateFmt(SConversionFailedFmt, ErrorInfo);819ecInvalidArg:820raise EInvalidArgument.CreateFmt(SInvalidArgumentFmt, ErrorInfo);821ecZeroDenominator:822raise EDivByZero.Create(SZeroDenominator);823end;824end;825
826class operator BigRational.Explicit(const Value: BigRational): Integer;827begin
828if Value.FDenominator = BigInteger.One then829Result := Integer(Value.FNumerator)830else831Result := Integer(Value.FNumerator div Value.FDenominator);832end;833
834class operator BigRational.Explicit(const Value: BigRational): string;835begin
836Result := Value.ToString;837end;838
839class operator BigRational.Explicit(const Value: BigRational): Double;840begin
841if Value.FDenominator = BigInteger.One then842Result := Value.FNumerator.AsDouble843else844Result := Value.FNumerator.AsDouble / Value.FDenominator.AsDouble;845end;846
847class operator BigRational.Explicit(const Value: BigRational): Int64;848begin
849if Value.FDenominator = BigInteger.One then850Result := Int64(Value.FNumerator)851else852Result := Int64(Value.FNumerator div Value.FDenominator);853end;854
855function BigRational.GetSign: Integer;856begin
857if FNumerator.IsZero then858Result := 0859else if FNumerator.IsNegative then860Result := -1861else862Result := 1;863end;864
865class operator BigRational.GreaterThan(const Left, Right: BigRational): Boolean;866begin
867Result := Compare(Left, Right) > 0;868end;869
870class operator BigRational.GreaterThanOrEqual(const Left, Right: BigRational): Boolean;871begin
872Result := Compare(Left, Right) >= 0;873end;874
875class operator BigRational.Implicit(const Value: Integer): BigRational;876begin
877Result.FNumerator := BigInteger(Value);878Result.FDenominator := BigInteger.One;879end;880
881class operator BigRational.Implicit(const Value: string): BigRational;882begin
883Result.Create(Value);884end;885
886class operator BigRational.Implicit(const Value: Double): BigRational;887begin
888Result.Create(Value);889end;890
891class operator BigRational.Implicit(const Value: Int64): BigRational;892begin
893Result.FNumerator := BigInteger(Value);894Result.FDenominator := BigInteger.One;895end;896
897class operator BigRational.Implicit(const Value: BigDecimal): BigRational;898begin
899if Value.Scale = 0 then900begin901Result.FNumerator := Value.UnscaledValue;902Result.FDenominator := BigInteger.One903end904else if Value.Scale > 0 then905begin906Result.FNumerator := Value.UnscaledValue;907Result.FDenominator := BigInteger.Pow(BigInteger.Ten, Value.Scale);908end909else910begin911Result.FNumerator := Value.UnscaledValue * BigInteger.Pow(BigInteger.Ten, -Value.Scale);912Result.FDenominator := BigInteger.One;913end;914Result.Normalize;915end;916
917class operator BigRational.Implicit(const Value: BigRational): BigDecimal;918begin
919Result := BigDecimal(Value.FNumerator) / Value.FDenominator;920end;921
922{$IFDEF HasClassConstructors}
923class constructor BigRational.Initialize;924{$ELSE}
925procedure Init;926{$ENDIF}
927begin
928BigRational.FAlwaysReduce := True;929BigRational.Zero := BigRational.Create(BigInteger.Zero, BigInteger.One);930BigRational.OneTenth := BigRational.Create(BigInteger.One, BigInteger.Ten);931BigRational.OneFourth := BigRational.Create(BigInteger.One, BigInteger(4));932BigRational.OneThird := BigRational.Create(BigInteger.One, BigInteger(3));933BigRational.OneHalf := BigRational.Create(BigInteger.One, BigInteger(2));934BigRational.TwoThirds := BigRational.OneThird + BigRational.OneThird;935BigRational.ThreeFourths := BigRational.OneHalf + BigRational.OneFourth;936BigRational.One := BigRational.Create(BigInteger.One, BigInteger.One);937BigRational.Ten := BigRational.Create(BigInteger.Ten, BigInteger.One);938end;939
940class operator BigRational.IntDivide(const Left, Right: BigRational): BigInteger;941begin
942Result := (Left.FNumerator * Right.FDenominator) div (Left.FDenominator * Right.FNumerator);943end;944
945function BigRational.IsNegative: Boolean;946begin
947Result := Self.FNumerator.IsNegative;948end;949
950function BigRational.IsPositive: Boolean;951begin
952Result := Self.FNumerator.IsPositive;953end;954
955function BigRational.IsZero: Boolean;956begin
957Result := Self.FNumerator.IsZero;958end;959
960function BigRational.Reciprocal: BigRational;961begin
962if FNumerator.IsZero then963Error(ecDivByZero, []);964if Self.FNumerator.IsNegative then965begin966Result.FNumerator := -Self.FDenominator;967Result.FDenominator := -Self.FNumerator;968end969else970begin971Result.FNumerator := Self.FDenominator;972Result.FDenominator := Self.FNumerator;973end;974end;975
976function BigRational.Reduce: BigRational;977begin
978Result := Self;979Result.Normalize(True);980end;981
982class function BigRational.Remainder(const Left, Right: BigRational): BigRational;983begin
984Result := Left mod Right;985end;986
987class operator BigRational.LessThan(const Left, Right: BigRational): Boolean;988begin
989Result := Compare(Left, Right) < 0;990end;991
992class operator BigRational.LessThanOrEqual(const Left, Right: BigRational): Boolean;993begin
994Result := Compare(Left, Right) <= 0;995end;996
997class operator BigRational.Modulus(const Left, Right: BigRational): BigRational;998var
999AD, BC: BigInteger;1000begin
1001// Result := Left - (Left div Right) * Right;1002// This can be elaborated:1003// X := A/B mod C/D -->1004// X := A/B - (AD div BC) * C/D; -->1005// X := AD/BD - (AD div BC) * BC/BD; -->1006// X := [AD - (AD div BC) * BC] / BD;1007
1008//$$RV: Can this be simplified?1009AD := Left.FNumerator * Right.FDenominator;1010BC := Left.FDenominator * Right.FNumerator;1011Result.FNumerator := AD - (AD div BC) * BC;1012Result.FDenominator := Left.FDenominator * Right.FDenominator;1013Result.Normalize;1014end;1015
1016class operator BigRational.Multiply(const Left, Right: BigRational): BigRational;1017begin
1018Result.FNumerator := Left.FNumerator * Right.FNumerator;1019Result.FDenominator := Left.FDenominator * Right.FDenominator;1020Result.Normalize;1021end;1022
1023function BigRational.Negate: BigRational;1024begin
1025Result.FDenominator := Self.FDenominator;1026Result.FNumerator := -Self.FNumerator;1027end;1028
1029class operator BigRational.Negative(const Value: BigRational): BigRational;1030begin
1031Result.FDenominator := Value.FDenominator;1032Result.FNumerator := -Value.FNumerator;1033end;1034
1035procedure BigRational.Normalize(Forced: Boolean = False);1036var
1037GCD: BigInteger;1038begin
1039if FDenominator.IsZero then1040Error(ecZeroDenominator, []);1041
1042if FDenominator = BigInteger.One then1043Exit;1044
1045if FDenominator.IsNegative then1046begin1047FNumerator := -FNumerator;1048FDenominator := -FDenominator;1049end;1050
1051if (FAlwaysReduce or Forced) then1052begin1053GCD := BigInteger.Abs(BigInteger.GreatestCommonDivisor(FNumerator, FDenominator));1054
1055// TODO: See if this can be simplified by shifting common low zero bits away first1056if GCD > BigInteger.One then1057begin1058FNumerator := FNumerator div GCD;1059FDenominator := FDenominator div GCD;1060end;1061end;1062end;1063
1064class operator BigRational.NotEqual(const Left, Right: BigRational): Boolean;1065begin
1066Result := Compare(Left, Right) <> 0;1067end;1068
1069function BigRational.Parse(const S: string): BigRational;1070begin
1071if not TryParse(S, Result) then1072Error(ecParse, [S, 'BigRational']);1073end;1074
1075class function BigRational.Multiply(const Left, Right: BigRational): BigRational;1076begin
1077Result := Left * Right;1078end;1079
1080class operator BigRational.Subtract(const Left, Right: BigRational): BigRational;1081begin
1082if Left.FDenominator = Right.FDenominator then1083begin1084Result.FNumerator := Left.FNumerator - Right.FNumerator;1085Result.FDenominator := Left.FDenominator;1086end1087else1088begin1089Result.FDenominator := Left.FDenominator * Right.FDenominator;1090Result.FNumerator := Left.FNumerator * Right.FDenominator - Right.FNumerator * Left.FDenominator;1091end;1092Result.Normalize;1093end;1094
1095class function BigRational.Subtract(const Left, Right: BigRational): BigRational;1096begin
1097Result := Left - Right;1098end;1099
1100function BigRational.ToString: string;1101begin
1102if FDenominator = BigInteger.One then1103Result := FNumerator.ToString1104else1105Result := FNumerator.ToString + '/' + FDenominator.ToString;1106end;1107
1108
1109function BigRational.TryParse(const S: string; out Value: BigRational): Boolean;1110var
1111LSlash: Integer;1112Num, Denom: BigInteger;1113begin
1114if S = '' then1115Exit(False);1116
1117Value := Zero;1118LSlash := Pos('/', S);1119if LSlash < 1 then1120begin1121Result := BigInteger.TryParse(Trim(S), Num);1122Denom := BigInteger.One;1123end1124else1125begin1126Result := BigInteger.TryParse(Trim(Copy(S, 1, LSlash - 1)), Num);1127Result := BigInteger.TryParse(Trim(Copy(S, LSlash + 1, MaxInt)), Denom) or Result;1128end;1129if Result then1130Value := BigRational.Create(Num, Denom);1131end;1132
1133{$IFNDEF HasClassConstructors}
1134initialization
1135Init;1136{$ENDIF}
1137
1138end.1139