And because it is so much fun, I wrote a completely general program to test it.
You can define how many resolution combinations you want to go through and what maximum value you want to start with and it goes through all possible combinations and gives you results after how many cycles a start-end pair has settled down and what was the maximum difference.
E.g. for the most typical use-case of 2 different resolutions and max scaling of 200% and max starting value of 2000, the results are:
-- Input --
Possible resolutions: 100%, 125%, 150%, 175%, 200% UniqueCombinations: True
ResolutionSteps: 2
MaxValue: 2000
-- Results --
Count: 40020
Error count after the cycle #1: 5615 (14,03%)
No errors after cycle #2
Maximal error value (difference between start and end value): 1 (5,88% from value 17 to 18 for combination 10)
So not really bad, after the 1st cycle the value always settles down and the maximal error is 1px, which is ~6% of the start value.
---------
For 3 different resolutions (100%, 125%, 200%), the maximum error value is 2px after max 10 resolution changes:
-- Input --
Possible resolutions: 100%, 125%, 200%
UniqueCombinations: False
ResolutionSteps: 10
MaxValue: 500
-- Results --
Count: 29583048
Error count after the cycle #1: 6805918 (23,01%)
No errors after cycle #2
Maximal error value (difference between start and end value): 2 (9,52% from value 21 to 19 for combination 2000000001)
----------
But even after 8 resolution steps (using 5 different resolutions), the error is not that bad:
-- Input --
Possible resolutions: 100%, 125%, 150%, 175%, 200% UniqueCombinations: False
ResolutionSteps: 8
MaxValue: 500
-- Results --
Count: 195702624
Error count after the cycle #1: 82854224 (42,34%)
Error count after the cycle #2: 9545914 (4,88%)
Error count after the cycle #3: 184165 (0,09%)
No errors after cycle #4
Maximal error value (difference between start and end value): 5 (26,32% from value 19 to 14 for combination 32401201)
---------
To sum it up, for 3 and less monitor DPI resolutions, the maximum error is 2px, which is marginal and it is there only for the very small value of 19px.
When the number of different resolutions increases (in the above example 5 different resolutions), the error gets higher, but still is acceptable and the higher the starting value, the lower the rounding error. And honestly, who has 5 monitors and every one with a different resolution and moves a window between all of them, so that it bothers him?
My conclusion is that it really is not worth it to introduce a floating-point precision sizes within the LCL. Yes, there would be some gain in scaling precision but only for a lot of different DPI values (more than 3) and the gain would not be striking. On the other hand, the effort needed would be very high, especially due to forwards compatibility of the LFM format.
Ondrej
Program code:
program TestScaling;
uses Math, SysUtils;
const
? //DefResolutions: array[0..2] of Double = (1.00, 1.25, 2.00);
? DefResolutions: array[0..4] of Double = (1.00, 1.25, 1.50, 1.75, 2.00);
? //DefResolutions: array[0..8] of Double = (1.00, 1.25, 1.50, 1.75,
2.00, 2.50, 3.00, 3.50, 4.00); // test even more resolutions - up to 400%
? MaxDiffFromValue = 16; // do not test values smaller than this for
MaxDiff*
function Scale(const aValue: Integer; const aFromResolution,
aToResolution: Double): Integer;
begin
? Result := Round(aValue / aFromResolution * aToResolution);
end;
function ScaleCycle(const V: Integer; R: TArray<Integer>): Integer;
var
? I: Integer;
begin
? Result := V;
? for I := 0 to High(R)-1 do
??? Result := Scale(Result, DefResolutions[R[I]], DefResolutions[R[I+1]]);
? Result := Scale(Result, DefResolutions[R[High(R)]],
DefResolutions[R[0]]);
end;
function NextCombination(var R: TArray<Integer>): Boolean;
var
? I: Integer;
begin
? for I := High(R) downto 0 do
? begin
??? if R[I]<High(DefResolutions) then
??? begin
????? Inc(R[I]);
????? Exit(True);
??? end;
??? R[I] := 0;
? end;
? Result := False;
end;
function CombinationIsUnique(const R: TArray<Integer>): Boolean;
var
? I, L: Integer;
begin
? for I := 0 to High(R) do
??? for L := I+1 to High(R) do
????? if R[I]=R[L] then
??????? Exit(False);
? Result := True;
end;
function WriteCombination(const R: TArray<Integer>): string;
var
? I: Integer;
begin
? Result := '';
? for I := 0 to High(R) do
??? Result := Result + IntToStr(R[I]);
end;
function WriteResolutions(const R: array of Double): string;
var
? I: Integer;
begin
? Result := '';
? for I := 0 to High(R) do
? begin
??? if Result'' then
????? Result := Result + ', ';
??? Result := Result + IntToStr(Round(R[I]*100))+'%';
? end;
end;
var
? Resolutions: array of Integer;
? I, F, RefValue, Cycle, ResolutionSteps, MaxValue, StartValue,
MaxDiff, MaxDiffStartValue, MaxDiffEndValue: Integer;
? Count: Int64;
? MaxDiffRatio: Double;
? Errors: array of Int64;
? MaxDiffCombination: string;
? UniqueCombinationsC: Char;
? UniqueCombinations: Boolean;
begin
? try
??? Write('Do you want to test only unique combinations? (y/n): ');
??? ReadLn(UniqueCombinationsC);
??? UniqueCombinations := LowerCase(UniqueCombinationsC)='y';
??? if UniqueCombinations then
????? Write(Format('Input how many resolutions do you want to cycle
through (min = 2, max = %d): ', [Length(DefResolutions)]))
??? else
????? Write('Input how many resolutions do you want to cycle through
(min = 2): ');
??? ReadLn(ResolutionSteps);
??? if ResolutionSteps<2 then
????? raise EInOutError.Create('Invalid resolution count');
??? if UniqueCombinations and (ResolutionSteps>Length(DefResolutions)) then
????? raise EInOutError.CreateFmt('Invalid resolution count (in case of
unique combintations, the resolution count must be max "%d"',
[Length(DefResolutions)]);
??? Write('Input the maximum value you want to test (e.g. 1000): ');
??? ReadLn(MaxValue);
??? if MaxValue<1 then
????? raise EInOutError.Create('Invalid maximum value');
??? Resolutions := nil;
??? SetLength(Resolutions, ResolutionSteps);
??? for I := 0 to High(Resolutions) do
????? Resolutions[I] := 0;
??? Count := 0;
??? MaxDiff := 0;
??? MaxDiffRatio := 0;
??? MaxDiffStartValue := 0;
??? MaxDiffEndValue := 0;
??? MaxDiffCombination := '';
??? Errors := nil;
??? while NextCombination(Resolutions) do
??? begin
????? if UniqueCombinations and not CombinationIsUnique(Resolutions) then
??????? continue;
????? Write('Combination: ');
????? Writeln(WriteCombination(Resolutions));
????? for I := 0 to MaxValue do
????? begin
??????? Inc(Count);
??????? StartValue := I;
??????? RefValue := I;
??????? F := ScaleCycle(RefValue, Resolutions);
??????? Cycle := 0;
??????? while not SameValue(F, RefValue) do
??????? begin
????????? // compute MaxDiff - but only for values above MaxDiffFromValue
????????? if (StartValue>=MaxDiffFromValue)
????????? and (MaxDiff<Abs(F-StartValue)) then
????????? begin
??????????? MaxDiff := Abs(F-StartValue);
??????????? MaxDiffRatio := Abs(F-StartValue) / StartValue;
??????????? MaxDiffStartValue := StartValue;
??????????? MaxDiffEndValue := F;
??????????? MaxDiffCombination := WriteCombination(Resolutions);
????????? end;
????????? RefValue := F;
????????? if High(Errors)<Cycle then
????????? begin
??????????? SetLength(Errors, Cycle+1);
??????????? Errors[Cycle] := 0;
????????? end;
????????? Inc(Errors[Cycle]);
????????? Inc(Cycle);
????????? F := ScaleCycle(F, Resolutions);
??????? end;
????? end;
??? end;
??? Writeln('end.');
??? Writeln;
??? Writeln('-- Input --');
??? Writeln('Possible resolutions: ', WriteResolutions(DefResolutions));
??? Writeln('UniqueCombinations: ', BoolToStr(UniqueCombinations, True));
??? Writeln('ResolutionSteps: ', ResolutionSteps);
??? Writeln('MaxValue: ', MaxValue);
??? Writeln('-- Results --');
??? Writeln('Count: ', Count);
??? for Cycle := 0 to High(Errors) do
????? Writeln(Format('Error count after the cycle #%d: %d (%.2f%%)',
[Cycle+1, Errors[Cycle], Errors[Cycle] / Count * 100]));
??? Writeln(Format('No errors after cycle #%d', [Length(Errors)+1]));
??? Writeln(Format('Maximal error value (difference between start and
end value): %d (%.2f%% from value %d to %d for combination %s) ',
[MaxDiff, MaxDiffRatio*100, MaxDiffStartValue, MaxDiffEndValue,
MaxDiffCombination]));
? except
??? on E: Exception do
????? Writeln(E.ClassName, ': ', E.Message);
? end;
? ReadLn;
end.