You've already forked lazarus-ccr
fpspreadsheet: More robust float-to-fraction conversion of float. Add test cases for this conversion.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4144 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@ -127,8 +127,8 @@ procedure MakeTimeIntervalMask(Src: String; var Dest: String);
|
|||||||
|
|
||||||
function ConvertFloatToStr(AValue: Double; AParams: TsNumFormatParams;
|
function ConvertFloatToStr(AValue: Double; AParams: TsNumFormatParams;
|
||||||
AFormatSettings: TFormatSettings): String;
|
AFormatSettings: TFormatSettings): String;
|
||||||
procedure FloatToFraction(AValue, APrecision: Double;
|
procedure FloatToFraction(AValue: Double; AMaxDenominator: Int64;
|
||||||
AMaxNumerator, AMaxDenominator: Int64; out ANumerator, ADenominator: Int64);
|
out ANumerator, ADenominator: Int64);
|
||||||
function TryStrToFloatAuto(AText: String; out ANumber: Double;
|
function TryStrToFloatAuto(AText: String; out ANumber: Double;
|
||||||
out ADecimalSeparator, AThousandSeparator: Char; out AWarning: String): Boolean;
|
out ADecimalSeparator, AThousandSeparator: Char; out AWarning: String): Boolean;
|
||||||
function TryFractionStrToFloat(AText: String; out ANumber: Double;
|
function TryFractionStrToFloat(AText: String; out ANumber: Double;
|
||||||
@ -1385,13 +1385,12 @@ end;
|
|||||||
numerator and denominator.
|
numerator and denominator.
|
||||||
|
|
||||||
@param AValue Floating point value to be analyzed
|
@param AValue Floating point value to be analyzed
|
||||||
@param AMaxNumerator Maximum value of the numerator allowed
|
|
||||||
@param AMaxDenominator Maximum value of the denominator allowed
|
@param AMaxDenominator Maximum value of the denominator allowed
|
||||||
@param ANumerator (out) Numerator of the best approximating fraction
|
@param ANumerator (out) Numerator of the best approximating fraction
|
||||||
@param ADenominator (out) Denominator of the best approximating fraction
|
@param ADenominator (out) Denominator of the best approximating fraction
|
||||||
-------------------------------------------------------------------------------}
|
-------------------------------------------------------------------------------}
|
||||||
procedure FloatToFraction(AValue, APrecision: Double;
|
procedure FloatToFraction(AValue: Double; AMaxDenominator: Int64;
|
||||||
AMaxNumerator, AMaxDenominator: Int64; out ANumerator, ADenominator: Int64);
|
out ANumerator, ADenominator: Int64);
|
||||||
// Uses method of continued fractions, adapted version from a function in
|
// Uses method of continued fractions, adapted version from a function in
|
||||||
// Bart Broersma's fractions.pp unit:
|
// Bart Broersma's fractions.pp unit:
|
||||||
// http://svn.code.sf.net/p/flyingsheep/code/trunk/ConsoleProjecten/fractions/
|
// http://svn.code.sf.net/p/flyingsheep/code/trunk/ConsoleProjecten/fractions/
|
||||||
@ -1400,12 +1399,10 @@ const
|
|||||||
MinInt64 = Low(Int64);
|
MinInt64 = Low(Int64);
|
||||||
var
|
var
|
||||||
H1, H2, K1, K2, A, NewA, tmp, prevH1, prevK1: Int64;
|
H1, H2, K1, K2, A, NewA, tmp, prevH1, prevK1: Int64;
|
||||||
B, diff, test: Double;
|
B, test, diff, prevdiff: Double;
|
||||||
PendingOverflow: Boolean;
|
PendingOverflow: Boolean;
|
||||||
i: Integer = 0;
|
i: Integer = 0;
|
||||||
begin
|
begin
|
||||||
Assert((APrecision > 0) and (APrecision < 1));
|
|
||||||
|
|
||||||
if (AValue > MaxInt64) or (AValue < MinInt64) then
|
if (AValue > MaxInt64) or (AValue < MinInt64) then
|
||||||
raise Exception.Create('Range error');
|
raise Exception.Create('Range error');
|
||||||
|
|
||||||
@ -1424,6 +1421,7 @@ begin
|
|||||||
NewA := Round(Floor(B));
|
NewA := Round(Floor(B));
|
||||||
prevH1 := H1;
|
prevH1 := H1;
|
||||||
prevK1 := K1;
|
prevK1 := K1;
|
||||||
|
prevdiff := 1E308;
|
||||||
repeat
|
repeat
|
||||||
inc(i);
|
inc(i);
|
||||||
A := NewA;
|
A := NewA;
|
||||||
@ -1435,9 +1433,11 @@ begin
|
|||||||
K2 := tmp;
|
K2 := tmp;
|
||||||
test := H1/K1;
|
test := H1/K1;
|
||||||
diff := test - AValue;
|
diff := test - AValue;
|
||||||
if (abs(diff) < APrecision) then
|
{ Use the previous result if the denominator becomes larger than the allowed
|
||||||
break;
|
value, or if the difference becomes worse because the "best" result has
|
||||||
if (abs(H1) >= AMaxNumerator) or (abs(K1) >= AMaxDenominator) then
|
been missed due to rounding error - this is more stable than using a
|
||||||
|
predefined precision in comparing diff with zero. }
|
||||||
|
if (abs(K1) >= AMaxDenominator) or (abs(diff) > abs(prevdiff)) then
|
||||||
begin
|
begin
|
||||||
H1 := prevH1;
|
H1 := prevH1;
|
||||||
K1 := prevK1;
|
K1 := prevK1;
|
||||||
@ -1454,6 +1454,7 @@ begin
|
|||||||
NewA := Round(Floor(B));
|
NewA := Round(Floor(B));
|
||||||
prevH1 := H1;
|
prevH1 := H1;
|
||||||
prevK1 := K1;
|
prevK1 := K1;
|
||||||
|
prevdiff := diff;
|
||||||
until PendingOverflow;
|
until PendingOverflow;
|
||||||
ANumerator := H1;
|
ANumerator := H1;
|
||||||
ADenominator := K1;
|
ADenominator := K1;
|
||||||
@ -2794,7 +2795,7 @@ var
|
|||||||
sfrint, sfrnum, sfrdenom: String;
|
sfrint, sfrnum, sfrdenom: String;
|
||||||
sfrsym, sintnumspace, snumsymspace, ssymdenomspace: String;
|
sfrsym, sintnumspace, snumsymspace, ssymdenomspace: String;
|
||||||
i, numEl: Integer;
|
i, numEl: Integer;
|
||||||
prec: Double;
|
//prec: Double;
|
||||||
begin
|
begin
|
||||||
sintnumspace := '';
|
sintnumspace := '';
|
||||||
snumsymspace := '';
|
snumsymspace := '';
|
||||||
@ -2802,7 +2803,8 @@ begin
|
|||||||
sfrsym := '/';
|
sfrsym := '/';
|
||||||
if ADigits >= 0 then begin
|
if ADigits >= 0 then begin
|
||||||
maxDenom := Round(IntPower(10, ADigits));
|
maxDenom := Round(IntPower(10, ADigits));
|
||||||
prec := 0.5/maxDenom;
|
// prec := 0.5/maxDenom;
|
||||||
|
//prec := 0.001/maxDenom;
|
||||||
end;
|
end;
|
||||||
numEl := Length(AElements);
|
numEl := Length(AElements);
|
||||||
|
|
||||||
@ -2816,7 +2818,8 @@ begin
|
|||||||
end else
|
end else
|
||||||
frint := 0;
|
frint := 0;
|
||||||
if ADigits >= 0 then
|
if ADigits >= 0 then
|
||||||
FloatToFraction(AValue, prec, MaxInt, maxdenom, frnum, frdenom)
|
FloatToFraction(AValue, maxdenom, frnum, frdenom)
|
||||||
|
// FloatToFraction(AValue, prec, MaxInt, maxdenom, frnum, frdenom)
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
frdenom := -ADigits;
|
frdenom := -ADigits;
|
||||||
@ -2834,7 +2837,8 @@ begin
|
|||||||
// "normal" fraction
|
// "normal" fraction
|
||||||
sfrint := '';
|
sfrint := '';
|
||||||
if ADigits > 0 then
|
if ADigits > 0 then
|
||||||
FloatToFraction(AValue, prec, MaxInt, maxdenom, frnum, frdenom)
|
// FloatToFraction(AValue, prec, MaxInt, maxdenom, frnum, frdenom)
|
||||||
|
FloatToFraction(AValue, maxdenom, frnum, frdenom)
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
frdenom := -ADigits;
|
frdenom := -ADigits;
|
||||||
|
@ -32,6 +32,8 @@ type
|
|||||||
procedure SetUp; override;
|
procedure SetUp; override;
|
||||||
procedure TearDown; override;
|
procedure TearDown; override;
|
||||||
|
|
||||||
|
procedure FractionTest(AMaxDigits: Integer);
|
||||||
|
|
||||||
published
|
published
|
||||||
// Tests getting Excel style A1 cell locations from row/column based locations.
|
// Tests getting Excel style A1 cell locations from row/column based locations.
|
||||||
// Bug 26447
|
// Bug 26447
|
||||||
@ -52,11 +54,17 @@ type
|
|||||||
procedure TestReadBufStream;
|
procedure TestReadBufStream;
|
||||||
procedure TestWriteBufStream;
|
procedure TestWriteBufStream;
|
||||||
// Test fractions
|
// Test fractions
|
||||||
procedure FractionTest;
|
// procedure FractionTest_0;
|
||||||
|
procedure FractionTest_1;
|
||||||
|
procedure FractionTest_2;
|
||||||
|
procedure FractionTest_3;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
Math;
|
||||||
|
|
||||||
const
|
const
|
||||||
InternalSheet = 'Internal'; //worksheet name
|
InternalSheet = 'Internal'; //worksheet name
|
||||||
|
|
||||||
@ -397,26 +405,66 @@ begin
|
|||||||
CheckEquals(s, GetCellString(r, c, flags));
|
CheckEquals(s, GetCellString(r, c, flags));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSpreadInternalTests.FractionTest;
|
procedure TSpreadInternalTests.FractionTest(AMaxDigits: Integer);
|
||||||
const
|
const
|
||||||
N = 300;
|
N = 300;
|
||||||
DIGITS = 3;
|
|
||||||
var
|
var
|
||||||
i, j: Integer;
|
j: Integer;
|
||||||
sollNum, sollDenom: Integer;
|
sollNum, sollDenom: Integer;
|
||||||
sollValue: Double;
|
sollValue: Double;
|
||||||
actualNum, actualDenom: Int64;
|
actualNum, actualDenom: Int64;
|
||||||
|
max: Integer;
|
||||||
|
prec: Double;
|
||||||
|
begin
|
||||||
|
max := Round(IntPower(10, AMaxDigits));
|
||||||
|
prec := 0.001/max;
|
||||||
|
for sollDenom := 1 to max-1 do
|
||||||
|
for sollNum := 1 to sollDenom-1 do begin
|
||||||
|
sollValue := StrToFloat(FormatFloat('0.000000000', sollNum/sollDenom));
|
||||||
|
FloatToFraction(sollValue, max, actualNum, actualDenom);
|
||||||
|
//FloatToFraction(sollValue, prec, max, max, actualNum, actualDenom);
|
||||||
|
if (actualnum*solldenom div actualdenom <> sollnum) then
|
||||||
|
fail(Format('Conversion error: %g = %d/%d turns to %d/%d (=%g)', [sollValue, sollNum, sollDenom, actualNum, actualDenom, actualNum/actualdenom]));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
(*
|
||||||
|
procedure TSpreadInternalTests.FractionTest_0;
|
||||||
|
const
|
||||||
|
N = 300;
|
||||||
|
var
|
||||||
|
j: Integer;
|
||||||
|
sollNum, sollDenom: Integer;
|
||||||
|
sollvalue: Double;
|
||||||
|
actualNum, actualDenom: Int64;
|
||||||
begin
|
begin
|
||||||
sollNum := 1;
|
sollNum := 1;
|
||||||
for j := 1 to N do
|
for j := 1 to N do
|
||||||
begin
|
begin
|
||||||
sollDenom := j;
|
sollDenom := j;
|
||||||
sollValue := StrToFloat(FormatFloat('0.00000', sollNum/sollDenom));
|
// sollValue := StrToFloat(FormatFloat('0.00000', sollNum/sollDenom));
|
||||||
FloatToFraction(sollvalue, 0.1/DIGITS, DIGITS, DIGITS, actualNum, actualDenom);
|
sollValue := 1.0/sollDenom;
|
||||||
|
// FloatToFraction(sollvalue, 0.1/DIGITS, DIGITS, DIGITS, actualNum, actualDenom);
|
||||||
|
FloatToFraction(sollvalue, 1000, actualNum, actualDenom);
|
||||||
if actualDenom > sollDenom then
|
if actualDenom > sollDenom then
|
||||||
fail(Format('Conversion error: approximated %d/%d turns to %d/%d', [sollNum, sollDenom, actualNum, actualDenom]));
|
fail(Format('Conversion error: approximated %d/%d turns to %d/%d', [sollNum, sollDenom, actualNum, actualDenom]));
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
*)
|
||||||
|
|
||||||
|
procedure TSpreadInternalTests.FractionTest_1;
|
||||||
|
begin
|
||||||
|
FractionTest(1);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TSpreadInternalTests.FractionTest_2;
|
||||||
|
begin
|
||||||
|
FractionTest(2);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TSpreadInternalTests.FractionTest_3;
|
||||||
|
begin
|
||||||
|
FractionTest(3);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TSpreadInternalTests.SetUp;
|
procedure TSpreadInternalTests.SetUp;
|
||||||
begin
|
begin
|
||||||
|
Reference in New Issue
Block a user