Recent

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

CM630

  • Hero Member
  • *****
  • Posts: 1644
  • Не съм сигурен, че те разбирам.
    • http://sourceforge.net/u/cm630/profile/
Re: 'Rounding' to (say) 5 DP
« Reply #15 on: February 18, 2026, 05:47:47 pm »
The description of the task has become too complicated.
Give us 5 or 10 numbers formatted as they should be?
Лазар 4,4 32 bit (sometimes 64 bit); FPC3,2,2

wp

  • Hero Member
  • *****
  • Posts: 13412
Re: 'Rounding' to (say) 5 DP
« Reply #16 on: February 18, 2026, 06:32:33 pm »
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.
Oh - I was not aware of that, and I guess this leads to some bugs in my own projects (and the Format() docs has an error, too, saying "Precision is used to determine the number of digits after the decimal point.")

You can avoid this in two ways:
- Use the Format with the "f" argument and strip the trailing zeros manually (as I showed in the MyOtherFlotToStr function - it's just a few lines of code)
- Or switch to the FormatFloat function which has a different formatting syntax: '0' is a displayed, '*' is an optional digit (shown only when no trailing zero). The instruction "FormatFloat('0.*****', 123.456789123) returns 123.45678 with at most 5 decimal places. (Or: Format('0.00000', 123.4) returns always 5 decimals and adds trailing zeros in this specific case). If you need a variable number of decimal places you must write a little routine to construct the format string:

Code: Pascal  [Select][+][-]
  1. function BuildFormatStr(Decs: Integer): string;
  2. var
  3.   i: Integer;
  4. begin
  5.   Result := '0.';
  6.   for i := 1 to Decs do Result := Result + '#';   // or: '0' if the digit must be displayed even when it is a trailing zero
  7. end;
  8.  
  9. function MyFloatToStr(x: Double; Fmt: String): String;
  10. begin
  11.   Result := StringReplace(FormatFloat(Fmt, x), FormatSettings.DecimalSeparator, '·', []);
  12. end;
  13.  
  14. //----
  15. var
  16.   fmt: String;
  17. begin
  18.   fmt := BuildFormatStr(5);
  19.   WriteLn(MyFloatToStr(51.56432123, fmt));
  20.   WriteLn(MyFloatToStr(51.0100000001, fmt));  
  21. end.
« Last Edit: February 18, 2026, 07:13:01 pm by wp »

J-G

  • Hero Member
  • *****
  • Posts: 992
Re: 'Rounding' to (say) 5 DP
« Reply #17 on: February 18, 2026, 06:59:31 pm »
Again some useful food for thought @WP thanks.

For now I have a solution of course and there is little point in trying to find a different method as there will be no real benefit - certainly not as far as speed is concerned, display is 'instant'.

Maybe in a future project the FormatFloat may well be a better option but it's always good to discover a potential flaw - or law of unintended consequenses.

I was well pleased to find the use of Log10 - - -  'tis a long time since I've needed 'Logs'  :D


FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 881
Re: 'Rounding' to (say) 5 DP
« Reply #18 on: February 19, 2026, 12:07:41 pm »
In most cases rounding is done manually. It's usually as easy, as:
Code: Pascal  [Select][+][-]
  1. var R:Real;I:Integer;S:String;
  2. begin
  3.   I := Trunc(R * 1000) + 5;
  4.   S := IntToStr(I div 1000) + '·' + IntToStr((I div 10) mod 100);
  5. end;
  6.  
« Last Edit: February 19, 2026, 12:16:18 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

tetrastes

  • Hero Member
  • *****
  • Posts: 754
Re: 'Rounding' to (say) 5 DP
« Reply #19 on: February 19, 2026, 02:31:02 pm »
Just in case, for those who don't like banker's rounding (as me), there is SimpleRoundTo.
https://www.freepascal.org/docs-html/current/rtl/math/simpleroundto.html

wp

  • Hero Member
  • *****
  • Posts: 13412
Re: 'Rounding' to (say) 5 DP
« Reply #20 on: February 19, 2026, 05:12:27 pm »
But be warned: Rounding numbers is a risky job, you may not get what you want because the rounded number can be subject to rounding errors in the same way as the input number.

It all boils down to the question: Is the number a combination of powers of 2 (1, 0.5, 0.25, 0.125, ...)? If this is not the case, then the exact number does not exist in the (binary) computer! The computer works with the nearest combination of powers of 2 instead, and this results in lots of 0s or 9s at the end of the displayed value.

Suppose the number 0.100001. You want to round it to 1 decimal place and are expecting the value 0.1. But 0.1 is 1 / (2*5) - the 5 in there is not a power of 2, and therefore the value 0.1 does not "exist" exactly ... And when you call SimpleRoundTo the result will not be 0.1, but 0.1000000000000000000001 (maybe the count of 0s is not correct).

See this simple project:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. uses
  3.   SysUtils, Math;
  4. var
  5.   x, y: Double;
  6. begin
  7.   x := 0.100001;
  8.   y := SimpleRoundTo(x, -1);
  9.   WriteLn('x = ', x, ', y = ', y, ', y = 0.1? ', BoolToStr(y=0.1, true));
  10.  
  11.   ReadLn;
  12. end.

There are really not many reasons why numbers should be rounded to numbers (Round, RoundTo, SimpleRoundTo, except for rounding to integers). What's much more important is rounding of numbers for string representation. Here you have the FloatToStr, FormatFloat, Str etc. routines, and they handle rounding in a much more natural way.
« Last Edit: February 19, 2026, 05:17:51 pm by wp »

creaothceann

  • Sr. Member
  • ****
  • Posts: 279
Re: 'Rounding' to (say) 5 DP
« Reply #21 on: February 19, 2026, 06:39:51 pm »
But be warned: Rounding numbers is a risky job, you may not get what you want because the rounded number can be subject to rounding errors in the same way as the input number.

It all boils down to the question: Is the number a combination of powers of 2 (1, 0.5, 0.25, 0.125, ...)? If this is not the case, then the exact number does not exist in the (binary) computer! The computer works with the nearest combination of powers of 2 instead, and this results in lots of 0s or 9s at the end of the displayed value.

Hence https://wiki.freepascal.org/Fractions

wp

  • Hero Member
  • *****
  • Posts: 13412
Re: 'Rounding' to (say) 5 DP
« Reply #22 on: February 19, 2026, 07:41:36 pm »
But be warned: Rounding numbers is a risky job, you may not get what you want because the rounded number can be subject to rounding errors in the same way as the input number.

It all boils down to the question: Is the number a combination of powers of 2 (1, 0.5, 0.25, 0.125, ...)? If this is not the case, then the exact number does not exist in the (binary) computer! The computer works with the nearest combination of powers of 2 instead, and this results in lots of 0s or 9s at the end of the displayed value.

Hence https://wiki.freepascal.org/Fractions
So, when I have to  loose that particular screw and the 5/16" wrench is too small, and the 3/4" is too big, should I try next the 9/16" wrench, or the 5/8"?

LV

  • Sr. Member
  • ****
  • Posts: 427
Re: 'Rounding' to (say) 5 DP
« Reply #23 on: February 19, 2026, 07:55:36 pm »
So, when I have to  loose that particular screw and the 5/16" wrench is too small, and the 3/4" is too big, should I try next the 9/16" wrench, or the 5/8"?
Use exact decimal arithmetic.
double -> 2^n (binary)
decimal -> 10^n (decimal).

tetrastes

  • Hero Member
  • *****
  • Posts: 754
Re: 'Rounding' to (say) 5 DP
« Reply #24 on: February 19, 2026, 08:16:08 pm »
Decimal arithmetic is not more exact than binary, f.e. 1/3 cannot be exactly expressed either in decimal numbers or in binary. Not to say about irrational numbers.
« Last Edit: February 19, 2026, 08:19:31 pm by tetrastes »

LV

  • Sr. Member
  • ****
  • Posts: 427
Re: 'Rounding' to (say) 5 DP
« Reply #25 on: February 19, 2026, 08:45:22 pm »
To be more precise. If we work in the ring of numbers of the form m/10^k (fixed-point decimal), then addition, subtraction, and multiplication operations can be performed without representation errors. However, division may produce a result that lies outside this ring. 🧐

tetrastes

  • Hero Member
  • *****
  • Posts: 754
Re: 'Rounding' to (say) 5 DP
« Reply #26 on: February 19, 2026, 08:54:03 pm »
But be warned: [ ]

Moreover, it depends on precision, f.e. double(0.15) ~ 1.4999999999999999E-001, single(0.15) ~ 1.500000060E-01, and corresponding SimpleRoundTo(0.15, -1) are 1.0000000000000001E-001 and 2.000000030E-01.

EDIT. It's even worse, results depends on architecture. The above is for i386, but for x86_64 SimpleRoundTo(double(0.15), -1) ~ 2.0000000000000001E-001.  %)
« Last Edit: February 19, 2026, 09:50:20 pm by tetrastes »

creaothceann

  • Sr. Member
  • ****
  • Posts: 279
Re: 'Rounding' to (say) 5 DP
« Reply #27 on: February 19, 2026, 09:08:31 pm »
So, when I have to loose that particular screw and the 5/16" wrench is too small, and the 3/4" is too big, should I try next the 9/16" wrench, or the 5/8"?

You'd look which one is closest, and not smaller than the screw.

Code: Pascal  [Select][+][-]
  1. program FractionTest;
  2. uses
  3.         CRT, Fractions, SysUtils;
  4.  
  5.  
  6. var
  7.         d           : Double;
  8.         Delta       : TFraction;
  9.         Delta_found : TFraction;
  10.         found       : integer;
  11.         i           : integer;
  12.         s           : AnsiString;
  13.         ScrewSize   : TFraction;
  14.         Wrench      : array[1..4] of TFraction;
  15.  
  16.  
  17. begin
  18.         Delta_found := Fraction(0,  1);  // only necessary to suppress warning 'Variable "Delta_found" does not seem to be initialized'
  19.         Wrench[1]   := Fraction(5, 16)  {Fraction(1, 1)};
  20.         Wrench[2]   := Fraction(3,  4)  {Fraction(2, 1)};
  21.         Wrench[3]   := Fraction(9, 16)  {Fraction(3, 1)};
  22.         Wrench[4]   := Fraction(5,  8)  {Fraction(4, 1)};
  23.         while True do begin
  24.                 ClrScr;
  25.                 WriteLn('Enter screw size. The value can be');
  26.                 WriteLn('- a decimal form,    e.g. 0.125'   );
  27.                 WriteLn('- a single integer,  e.g. 10'      );
  28.                 WriteLn('- a single fraction, e.g. 2/3'     );
  29.                 WriteLn('- an integer + a fraction, separated by a single space, e.g. 2 1/2 (two and a half)');
  30.                 ReadLn(s);
  31.                 if TryStrToFraction(s, ScrewSize) then                                                      break;
  32.                 if TryStrToFloat   (s, d        ) then begin  ScrewSize := CF_FloatToFraction(d, 0.00001);  break;  end;
  33.         end;
  34.         WriteLn;
  35.         found := 0;
  36.         for i := 1 to 4 do begin
  37.                 Delta := Wrench[i] - ScrewSize;
  38.                 if (Delta < 0) then begin
  39.                         WriteLn('Wrench ', i, ' is too small');
  40.                         continue;
  41.                 end;
  42.                 if (found > 0) and (Delta_found < Delta) then continue;
  43.                 found       := i;
  44.                 Delta_found := Delta;
  45.         end;
  46.         if (found > 0)
  47.                 then WriteLn('Use wrench #', found, '.')
  48.                 else WriteLn('Could not find a wrench that fits.');
  49.         ReadLn;
  50. end.
  51.  
« Last Edit: February 19, 2026, 09:16:21 pm by creaothceann »

tetrastes

  • Hero Member
  • *****
  • Posts: 754
Re: 'Rounding' to (say) 5 DP
« Reply #28 on: February 19, 2026, 09:54:48 pm »
To be more precise. If we work in the ring of numbers of the form m/10^k (fixed-point decimal), then addition, subtraction, and multiplication operations can be performed without representation errors. However, division may produce a result that lies outside this ring. 🧐

The problem is that you cannot express all numbers in that form also, as in any form. 1/3 already has representation error.
I have no idea, how to fulfil decimal fixed point on binary computer, but I worked with binary fixed point arithmetic in MCU for many years. And using 32-bit integers, its precision is much worse than that of IEEE 32-bit binary floating point (aka single).

EDIT. Probably for good reason modern MCU have FPU...
« Last Edit: February 19, 2026, 10:01:34 pm by tetrastes »

creaothceann

  • Sr. Member
  • ****
  • Posts: 279
Re: 'Rounding' to (say) 5 DP
« Reply #29 on: February 19, 2026, 09:57:08 pm »
To be more precise. If we work in the ring of numbers of the form m/10^k (fixed-point decimal), then addition, subtraction, and multiplication operations can be performed without representation errors. However, division may produce a result that lies outside this ring. 🧐

The problem is that you cannot express all numbers in that form also, as in any form. 1/3 already has representation error.
I have no idea, how to fulfil decimal fixed point on binary computer, but I worked with binary fixed point arithmetic in MCU for many years. And using 32-bit integers, its precision is much worse than that of IEEE 32-bit binary floating point (aka single).

What you call "binary" is actually "floating-point", and it's just one of many possible formats. 1/3 can be perfectly represented as a fraction.

 

TinyPortal © 2005-2018