Forum > General
Single and Double, Conversion and Precision
kupferstecher:
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:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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;
ASerge:
In the general case, it is not known whether this is a loss of precision or whether this was the original number.
If the author of the code knows, then you can use this knowledge.
In this case, if you need to round to two decimal places, you can use Math.SimpleRoundTo instead of SingleToDouble.
kupferstecher:
--- Quote from: ASerge on June 13, 2026, 05:51:17 am ---In the general case, it is not known whether this is a loss of precision or whether this was the original number.
--- End quote ---
Yes, the loss of precision is there, but only by one digit "in the end" of the number. The same does FloatToStr.
The SingleToDouble function also could be called RoundToDecimalWith7SignificantDigits.
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- single1:= 1.0 / 3.0; writeln(single1); //3.333333433E-01 double1:= SingleToDouble(single1); writeln(Floattostr(double1)); //0,3333333 writeln(double1); //3.3333330000000000E-001
Perhaps it would work with rounding to 8 significant digits as well, I didn't test.
--- Quote ---If the author of the code knows, then you can use this knowledge.
In this case, if you need to round to two decimal places, you can use Math.SimpleRoundTo instead of SingleToDouble.
--- End quote ---
The big difference is that with SingleToDouble the author doesn't need to know the number of decimals, it can be a very large number and it can be a very small number.
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- single1:= 0.0000000234567890123456789012345678; double1:= SingleToDouble(single1); writeln(Floattostr(double1)); //2.345679E-8 <- rounded to 7 significant digits writeln(double1); //2.3456789999999999E-008 <- Actual double number of the rounded value single1:= 23456789012345.6789012345678; double1:= single1; writeln(Floattostr(double1)); //23456789823488 <- digits after the first 8 are unrelated to the original number double1:= SingleToDouble(single1); writeln(Floattostr(double1)); //23456790000000 <- rounded to 7 significant digits writeln(double1); //2.3456789999999984E+013 <- Actual double number of the rounded value
Paolo:
flaottostr has as parameter the number of decimal that you want retain.
the problem is that string representations of number are usually truncated and are not saying the whole truth...
see picture, if it helps.
jamie:
It's interesting to note that the FloatTostr accepts Integers, why?
Also, I noticed if you pass a currency variable to it, it never displays the fractions?
Maybe the compiler version I am using has something to do with? 3.2.4RC ?
Maybe I had the number too large for it to display the fraction but really, accepting an Int64?
Jamie
Navigation
[0] Message Index
[#] Next page