The binary floating point format has the limitation, that (most) decimal numbers cannot be represented correctly, e.g. the value 23.4. The FloatToStr function hides that by rounding to a precision that is smaller than the actual precision of the floating point number. At least for double that works well and the internal 2.3399999999999999E+001 is converted to the string '23.4'.
It becomes problematic when type Single variables are assigned to type Double variables. Now the FloatToStr fails with its rounding concept, as the original Single does not provide a number with a precision as high as required. I found in the source that FloatToStr internally rounds to precision 15, while type Single itself only has a precision of about 8.
As I cannot avoid the conversion I thought it should be possible to round the assigned double to a decimal precision that is possible with Single, e.g. precision 6 or 7, to allow FloatToStr(double) to work properly, again.
Below is the code and it worked for some test values. As can be seen in the comments, the type Single value of 2.34 becomes the ugly 2.33999991416931 after assigning to double and FloatToStr. And it stays the beautiful 2.34 after converting it to double with the function SingleToDouble and FloatToStr.
As short description of the algorithm, the code round(aValue*1000)/1000; e.g. will round to 3 decimals. As the aim is to round to a certain precision instead of decimals, the number of digits before the comma has to be calculated first, which can be done by a log10 calculation. The idea is that it should do a reasonable decimal rounding for any number in the float range.
So my questions:
- Is there already such kind of function/functionality? (This should somehow be a common problem, but I didn't find anything.)
- Does it make sense, or are there corner cases I didn't consider, that render the function problematic or useless?
- Could the implementation be improved? My focus is on speed and reliability.
- How can I make sure that there are not any (odd) input values that break the calculation?
Any comments welcome!
Usage:
var
single1: Single;
double1: Double;
begin
single1:= 2.34;
//direct assignment
double1:= single1;
writeln('double1: '+Floattostr(double1)+' ',double1); //Output: "double1: 2.33999991416931 2.3399999141693115E+000"
//with decimal precision rounding
double1:= SingleToDouble(single1);
writeln('double1: '+Floattostr(double1)+' ',double1); //Output: "double1: 2.34 2.3399999999999999E+000"
end;
Function:
uses Math;
Function SingleToDouble(aValue: Single): Double;
var
digits: integer;
factor: Double;
begin
Result:= aValue;
if Result = double(0.0) then EXIT;
if Result > double(1.0) then begin
digits:= trunc(log10(abs(aValue)))+1;
if digits > 30 then digits:= 30; //Largest single ~3,4E38
end else begin
digits:= trunc(log10(abs(aValue)));
if digits < -30 then digits:= -30; //Smallest single ~1,2E−38
end;
factor:= IntPower(10, 7-digits);
Result:= round(aValue*factor)/factor;
end;