Recent

Author Topic: 'Rounding' to (say) 5 DP  (Read 3643 times)

J-G

  • Hero Member
  • *****
  • Posts: 992
'Rounding' to (say) 5 DP
« on: February 17, 2026, 06:47:59 pm »
I suspect that I'm missing something in the 'Math' Unit but it seems that RoundTo is the only option and that appears not to take the number of Decimal Points into account.

Th back story is that I'm having difficulty displaying results to a variable number of DP which is complicated by the fact that I also wish to remove the decimal point and replace it with a 'Dot' character (·) which turns out to be 2 chars wide as far as Lazarus is concerned, which confuses the heck out of the 'Copy' function;  ie.
Code: Pascal  [Select][+][-]
  1. p := pos('·',S);
  2. S := copy(S,1,N+p);
  3.  
S is returned one character shorter than requested.

It is also not taking account of the fact that the next digit in the number may well be 5 or greater - in which case the last returned digit ought to be increased.

Am I simply missing a Floating Point rounding function that will accept a number of Decimal Places ?    Or do I have to write my own ?
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

CM630

  • Hero Member
  • *****
  • Posts: 1641
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: 'Rounding' to (say) 5 DP
« Reply #1 on: February 17, 2026, 07:10:05 pm »
Code: Pascal  [Select][+][-]
  1. uses...LazUTF8...
  2. ...
  3.   ShowMessage ( UTF8StringReplace ('2.345','.','·',[]));
  4. ...
Of course, you shall take care, maybe the input decimal separator is a comma.
Documentation says that RoundTo uses banker's rounding, in other words, it rounds oddly.
Middle dot · is a multiplication sign, not a decimal separator, btw.
« Last Edit: February 17, 2026, 07:14:08 pm by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

J-G

  • Hero Member
  • *****
  • Posts: 992
Re: 'Rounding' to (say) 5 DP
« Reply #2 on: February 17, 2026, 07:22:40 pm »
Thanks for the input CM630.

I'm very well aware that 'Middle Dot' indicates multiplication when used in a formula but I'm using it to display numerical data in what I consider a better 'visual' format.

Replacing the character is not a problem - - - -  unless you are suggesting that using the UTF8 routine retains the numeric value - - - - which I suspect is unlikely.

In a search, I did find a similar question some years ago, but there was no definitive answer.
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

wp

  • Hero Member
  • *****
  • Posts: 13409
Re: 'Rounding' to (say) 5 DP
« Reply #3 on: February 17, 2026, 07:27:30 pm »
Code: Pascal  [Select][+][-]
  1. function MyFloatToStr(x: Double; Decs: Integer): String;
  2. begin
  3.   Result := StringReplace(Format('%.*f', [Decs, x]), FormatSettings.DecimalSeparator, '·', []);
  4. end;

creaothceann

  • Sr. Member
  • ****
  • Posts: 278
Re: 'Rounding' to (say) 5 DP
« Reply #4 on: February 17, 2026, 07:33:36 pm »
it seems that RoundTo is the only option and that appears not to take the number of Decimal Points into account. [...] I'm having difficulty displaying results to a variable number of DP
What if you multiply by 10n, do the rounding, then divide again by 10n?

CM630

  • Hero Member
  • *****
  • Posts: 1641
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: 'Rounding' to (say) 5 DP
« Reply #5 on: February 17, 2026, 07:41:05 pm »
I have done this many years ago, AFAIR it worked fine:

Code: Pascal  [Select][+][-]
  1. //Performs commons rounding
  2. //Examples: ComRound(1.5) = 2; ComRound(2.5) = 3; ComRound(3.5) = 4 (Compare with the lazarus inbuilt Round, where Round(1.5) = 2 but also Round(2.5) = 2)
  3. //          ComRound(1.55555,2) = 1.56
  4. function ComRound(Value: Extended; Width: integer = 0) : extended;
  5. Var
  6.   Added: extended = 0;
  7. begin
  8.   if Width = 0
  9.     then Result := trunc(Value)
  10.     else
  11.     begin
  12.       if (power(10,Width) * Value) mod 1 >= 0.5
  13.         then Added := 1
  14.         else Added := 0;
  15.       Result := (trunc (power(10,Width) * Value) + added)/power(10,Width);
  16.     end;
  17. end;
  18.  

...
Replacing the character is not a problem - - - -  unless you are suggesting that using the UTF8 routine retains the numeric value - - - - which I suspect is unlikely.
...
I think this is what UTF* is for.

Code: Pascal  [Select][+][-]
  1. function MyFloatToStr(x: Double; Decs: Integer): String;
  2. begin
  3.   Result := StringReplace(Format('%.*f', [Decs, x]), FormatSettings.DecimalSeparator, '·', []);
  4. end;

This is again bankers's rounding. Compare the results:
Code: Pascal  [Select][+][-]
  1. //Performs commons rounding
  2. //Examples: ComRound(1.5) = 2; ComRound(2.5) = 3; ComRound(3.5) = 4 (Compare with the lazarus inbuilt Round, where Round(1.5) = 2 but also Round(2.5) = 2)
  3. //          ComRound(1.55555,2) = 1.56
  4. function ComRound(Value: Extended; Width: integer = 0) : extended;
  5. Var
  6.   Added: extended = 0;
  7. begin
  8.   if Width = 0
  9.     then Result := trunc(Value)
  10.     else
  11.     begin
  12.       if (power(10,Width) * Value) mod 1 >= 0.5
  13.         then Added := 1
  14.         else Added := 0;
  15.       Result := (trunc (power(10,Width) * Value) + added)/power(10,Width);
  16.     end;
  17. end;
  18.  
  19. function MyFloatToStr(x: Double; Decs: Integer): String;
  20. begin
  21.   Result := StringReplace(Format('%.*f', [Decs, x]), FormatSettings.DecimalSeparator, '·', []);
  22. end;
  23.  
  24.  
  25. procedure TForm1.Button1Click(Sender: TObject);
  26. begin
  27.   ShowMessage(  FloatToStr(ComRound(1.2325,3)));
  28.   ShowMessage(         MyFloatToStr(1.2325,3)); //This result is wrong
  29.  
  30.   ShowMessage(  FloatToStr(ComRound(1.2335,3)));
  31.   ShowMessage(         MyFloatToStr(1.2335,3));
  32. end;    
« Last Edit: February 17, 2026, 07:49:01 pm by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

J-G

  • Hero Member
  • *****
  • Posts: 992
Re: 'Rounding' to (say) 5 DP
« Reply #6 on: February 17, 2026, 07:49:37 pm »
Code: Pascal  [Select][+][-]
  1. function MyFloatToStr(x: Double; Decs: Integer): String;
  2. begin
  3.   Result := StringReplace(Format('%.*f', [Decs, x]), FormatSettings.DecimalSeparator, '·', []);
  4. end;

That looks like an efficient option @WP - thanks.

I'll have to spend some time correcting adjusting my code which currently does the display job as a separate entity, but it may well be worth the effort :)
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

LV

  • Sr. Member
  • ****
  • Posts: 427
Re: 'Rounding' to (say) 5 DP
« Reply #7 on: February 17, 2026, 08:57:03 pm »
Or
Code: Pascal  [Select][+][-]
  1.   DefaultFormatSettings.DecimalSeparator := '.';  // the format is set once for the entire program
  2. ...
  3.   WriteLn(Format('%.*f', [5, Pi]));  // 3.14159
  4. ...
  5.   WriteLn(Format('%.*f', [5, Pi / 2]));  // 1.57080  
  6.  

dseligo

  • Hero Member
  • *****
  • Posts: 1672
Re: 'Rounding' to (say) 5 DP
« Reply #8 on: February 17, 2026, 09:09:56 pm »
I have done this many years ago, AFAIR it worked fine:

Code: Pascal  [Select][+][-]
  1. //Performs commons rounding
  2. //Examples: ComRound(1.5) = 2; ComRound(2.5) = 3; ComRound(3.5) = 4 (Compare with the lazarus inbuilt Round, where Round(1.5) = 2 but also Round(2.5) = 2)
  3. //          ComRound(1.55555,2) = 1.56
  4. function ComRound(Value: Extended; Width: integer = 0) : extended;
  5. Var
  6.   Added: extended = 0;
  7. begin
  8.   if Width = 0
  9.     then Result := trunc(Value)
  10.     else
  11.     begin
  12.       if (power(10,Width) * Value) mod 1 >= 0.5
  13.         then Added := 1
  14.         else Added := 0;
  15.       Result := (trunc (power(10,Width) * Value) + added)/power(10,Width);
  16.     end;
  17. end;
  18.  

I tried your function and didn't get results as you mentioned in your examples.

This is complete program:
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. uses math;
  4.  
  5. //Performs commons rounding
  6. //Examples: ComRound(1.5) = 2; ComRound(2.5) = 3; ComRound(3.5) = 4 (Compare with the lazarus inbuilt Round, where Round(1.5) = 2 but also Round(2.5) = 2)
  7. //          ComRound(1.55555,2) = 1.56
  8. function ComRound(Value: Extended; Width: integer = 0) : extended;
  9. Var
  10.   Added: extended = 0;
  11. begin
  12.   if Width = 0
  13.     then Result := trunc(Value)
  14.     else
  15.     begin
  16.       if (power(10,Width) * Value) mod 1 >= 0.5
  17.         then Added := 1
  18.         else Added := 0;
  19.       Result := (trunc (power(10,Width) * Value) + added)/power(10,Width);
  20.     end;
  21. end;
  22.  
  23. procedure Z(A: Extended);
  24. begin
  25.   writeln(a:3:2, ' ', ComRound(a):3:2);
  26. end;
  27.  
  28. begin
  29.   z(0);
  30.   z(0.5);
  31.   z(1.0);
  32.   z(1.5);
  33.   z(2);
  34.   z(2.5);
  35.  
  36.   z(-0.5);
  37.   z(-1.0);
  38.   z(-1.5);
  39.   z(-2);
  40.   z(-2.5);
  41.  
  42.   readln;
  43. end.

These are results:
Code: Text  [Select][+][-]
  1. 0.00 0.00
  2. 0.50 0.00
  3. 1.00 1.00
  4. 1.50 1.00
  5. 2.00 2.00
  6. 2.50 2.00
  7. -0.50 0.00
  8. -1.00 -1.00
  9. -1.50 -1.00
  10. -2.00 -2.00
  11. -2.50 -2.00

Then I commented lines 'if Width = 0 then Result := trunc(Value)' and get this results:
Code: Text  [Select][+][-]
  1. 0.00 0.00
  2. 0.50 1.00
  3. 1.00 1.00
  4. 1.50 2.00
  5. 2.00 2.00
  6. 2.50 3.00
  7. -0.50 0.00
  8. -1.00 -1.00
  9. -1.50 -1.00
  10. -2.00 -2.00
  11. -2.50 -2.00

Did you want it like that?

J-G

  • Hero Member
  • *****
  • Posts: 992
Re: 'Rounding' to (say) 5 DP
« Reply #9 on: February 17, 2026, 10:14:20 pm »
Thanks all,  Lots of 'Food for thought'.

Every project will have it's own 'issues' of course. I have moved forward some way using the code suggested by @WP but even that has caused some problem (overcome but irritating) - specifically, I also want to remove trailing Zeros and of course the modified Decimal Point should the result actually be an integer.

Because '·' is a complex character 2 units wide I can't just check for it being at the end using  :
If S[length(s)] = '·' then delete(S,length(s),1)'  (or even 2)

 I can use 

p := Pos('·', S)  and delete 2 charcters from the string

but even that caused a further issue which I've yet to fully understand - -  but at least I do now have the figures looking more 'professional'.
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

wp

  • Hero Member
  • *****
  • Posts: 13409
Re: 'Rounding' to (say) 5 DP
« Reply #10 on: February 17, 2026, 11:48:59 pm »
I also want to remove trailing Zeros and of course the modified Decimal Point should the result actually be an integer.
To get rid of the trailing zeros you only must replace the "f" parameter ("fixed format")  in the Format() instruction by "g" ("general format"); this also avoids the decimal separator at the end of an integer. Please read the docs of the Format() function: https://www.freepascal.org/docs-html/rtl/sysutils/format.html

CM630 is right: the Format function applies the Bankers' rounding rules. If you don't want that you can do the number-to-string conversion by means of the "old-fashioned" Str() procedure which apply the "normal" rounding rules; but you must remove the trailing zeros yourself:

Code: Pascal  [Select][+][-]
  1. const
  2.   DEC_SEPARATOR = '·';
  3.  
  4. //"Bankers' rounding"
  5. function MyFloatToStr(x: Double; Decs: Integer): String;
  6. begin
  7.   Result := StringReplace(Format('%.*g', [Decs, x]), FormatSettings.DecimalSeparator, DEC_SEPARATOR, []);
  8.                                 // '*' -- Symbol for the number of decimal places
  9.                                 // 'g' -- Code for "general number format"
  10. end;  
  11.  
  12. // "Normal" rounding
  13. function MyOtherFloatToStr(x: Double; Decs: Integer): String;
  14. begin
  15.   Str(x:0:Decs, Result);
  16.   if pos('.', Result) > 0 then
  17.   begin
  18.     while Result[Length(Result)] = '0' do
  19.       SetLength(Result, Length(Result)-1);
  20.     if Result[Length(Result)] = '.' then
  21.       SetLength(Result, Length(Result)-1);
  22.   end;
  23.   Result := StringReplace(Result, '.', DEC_SEPARATOR,[]);
  24. end;  
  25.  
  26. // Test code:
  27. procedure TForm1.Button1Click(Sender: TObject);
  28. const
  29.   DECS = 1;
  30. var
  31.   L: TStringList;
  32. begin
  33.   L := TStringList.Create;
  34.   try
  35.     L.Add(MyFloatToStr(1.45, DECS));
  36.     L.Add(MyFloatToStr(1.55, DECS));
  37.     L.Add(MyFloatToStr(1.65, DECS));
  38.     L.Add('');
  39.     L.Add(MyOtherFloatToStr(1.45, DECS));
  40.     L.Add(MyOtherFloatToStr(1.55, DECS));
  41.     L.Add(MyOtherFloatToStr(1.65, DECS));
  42.     L.Add('');
  43.     L.Add(MyOtherFloatToStr(1.001, DECS));
  44.  
  45.     ShowMessage(L.Text);
  46.   finally
  47.     L.Free;
  48.   end;
  49. end;
« Last Edit: February 18, 2026, 12:01:33 am by wp »

LV

  • Sr. Member
  • ****
  • Posts: 427
Re: 'Rounding' to (say) 5 DP
« Reply #11 on: February 17, 2026, 11:51:29 pm »
Outputting to the Windows 10 and 11 console requires additional effort. 🔮

J-G

  • Hero Member
  • *****
  • Posts: 992
Re: 'Rounding' to (say) 5 DP
« Reply #12 on: February 18, 2026, 12:28:25 pm »
Thanks again @WP - I should have looked up the details of the Format Unit before responding  -  pressing commitments yesterday meant that I simply took your suggestion 'as is'.

I DO search the vast Pascal Archive and have (had) 11 short-cut links to various topics permanently on my desktop - it's now 12 of course :)

Being an 'old' programmer - my main reference work is The Turbo Pascal 4.0 Manual  :o - I tend to use methods that I learned many years ago and have to be dragged (kicking & screaming) into the modern world !!
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

CM630

  • Hero Member
  • *****
  • Posts: 1641
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: 'Rounding' to (say) 5 DP
« Reply #13 on: February 18, 2026, 02:31:58 pm »
...
Then I commented lines 'if Width = 0 then Result := trunc(Value)' and get this results:
Code: Text  [Select][+][-]
  1. 0.00 0.00
  2. 0.50 1.00
  3. 1.00 1.00
  4. 1.50 2.00
  5. 2.00 2.00
  6. 2.50 3.00
  7. -0.50 0.00
  8. -1.00 -1.00
  9. -1.50 -1.00
  10. -2.00 -2.00
  11. -2.50 -2.00

Did you want it like that?
I do not like it, because it is wrong  :-[

This seems better:

Code: Pascal  [Select][+][-]
  1. function ComRound(Value: Extended; Width: integer = 0) : extended;
  2. var
  3.   Added: extended = 0;
  4.   sign : integer = 1;
  5. begin
  6.   if (Value < 0) then sign := -1;
  7.   if (power(10,Width) * abs(Value)) mod 1 >= 0.5
  8.     then Added := 1
  9.     else Added := 0;
  10.   Result := sign*(trunc (power(10,Width) * abs(Value)) + added)/power(10,Width);
  11. end;  

This code:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender:TObject);
  2. var
  3.   i: integer;
  4.   num: extended = -5;
  5.   Precision : integer = 0;
  6. begin
  7.   Precision := 0;
  8.   num := 0;
  9.   for i:= 0 to 10 do
  10.   begin
  11.     Memo1.Append(FloatToStr (num) + #9 + FloatToStrF (ComRound(num,Precision),ffFixed,3,Precision));
  12.     num := num + 0.5;
  13.   end;
  14.  
  15.   num := 0;
  16.   for i:= 0 to 10 do
  17.   begin
  18.     Memo1.Append(FloatToStr (num) + #9 + FloatToStrF (ComRound(num,Precision),ffFixed,3,Precision));
  19.     num := num - 0.5;
  20.   end;
  21.  
  22.   Precision := 1;
  23.   num := 0;
  24.   for i:= 0 to 10 do
  25.   begin
  26.     Memo1.Append(FloatToStr (num) + #9 + FloatToStrF (ComRound(num,Precision),ffFixed,3,Precision));
  27.     num := num + 0.05;
  28.   end;
  29.  
  30.   num := 0;
  31.   for i:= 0 to 10 do
  32.   begin
  33.     Memo1.Append(FloatToStr (num) + #9 + FloatToStrF (ComRound(num,Precision),ffFixed,3,Precision));
  34.     num := num - 0.05;
  35.   end;
  36. end;  

results in:

Code: Pascal  [Select][+][-]
  1. 0       0
  2. 0       0
  3. 0,5     1
  4. 1       1
  5. 1,5     2
  6. 2       2
  7. 2,5     3
  8. 3       3
  9. 3,5     4
  10. 4       4
  11. 4,5     5
  12. 5       5
  13. 0       0
  14. -0,5    -1
  15. -1      -1
  16. -1,5    -2
  17. -2      -2
  18. -2,5    -3
  19. -3      -3
  20. -3,5    -4
  21. -4      -4
  22. -4,5    -5
  23. -5      -5
  24. 0       0,0
  25. 0,05    0,1
  26. 0,1     0,1
  27. 0,15    0,2
  28. 0,2     0,2
  29. 0,25    0,3
  30. 0,3     0,3
  31. 0,35    0,4
  32. 0,4     0,4
  33. 0,45    0,5
  34. 0,5     0,5
  35. 0       0,0
  36. -0,05   -0,1
  37. -0,1    -0,1
  38. -0,15   -0,2
  39. -0,2    -0,2
  40. -0,25   -0,3
  41. -0,3    -0,3
  42. -0,35   -0,4
  43. -0,4    -0,4
  44. -0,45   -0,5
  45. -0,5    -0,5
  46.  
« Last Edit: February 18, 2026, 02:41:02 pm by CM630 »
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

J-G

  • Hero Member
  • *****
  • Posts: 992
Re: 'Rounding' to (say) 5 DP
« Reply #14 on: February 18, 2026, 05:32:18 pm »
That's certainly an interesting excercise @CM630 but for my current project use somewhat irrelevant :)   -  I'll never have Negative numbers - or even zero come to that.

My project is Triangle Solution  so if there is ever a Zero or Negative something has gone dramatically wrong !

Reporting back on @WP's suggestion (with appropriate mods) I now do have a working solution but there is a small issue that it seems appropriate to make known in case this discusion rears it's head in future.  The variable 'DECS' in the statement :

Result := StringReplace(Format('%.*g', [Decs, x]), FormatSettings.DecimalSeparator, '·', []);

is NOT the number of Decimal Places (well, it isn't in my use case)  -  it turns out to be the total number of digits in the number.  ie.  Given x = 51.56432123 and Decs = 5 it returns 51.564 - - -   to get 51.56432 I need to set Decs to 7.  Similarly, given x=123.456789123,  with Decs = 5 it returns 123.45;  With Decs = 8, I get 123.45678.

A simple enough solution is to determine the number of digits in the argument by using :
I := trunc(Log10(x)+1);  Decs = Deci (my variable for DP) +I

so I call the StringReplace with  x and Deci+I.

I'm not dealing with esoteric Reflex Triangles so the figures are never going to be outside the range .00001 (say) to 179.99999  - though that would depend to some extent upon what I set the DP to.
« Last Edit: February 18, 2026, 06:44:27 pm by J-G »
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

 

TinyPortal © 2005-2018