delphirounding

How to add an epsilon when rounding a value in Delphi to avoid inconsistent results?


I have encountered an issue with rounding in Delphi where values very close to the midpoint (like 21.499991254) sometimes round down instead of up, which can lead to inconsistencies in my calculations.

For example:

var
  value1, value2: Double;
begin
  value1 := 21.5;
  value2 := 21.499991254;
  ShowMessage(IntToStr(Round(value1))); // Returns 22, as expected
  ShowMessage(IntToStr(Round(value2))); // Returns 21, which is not ideal for my case
end;

I understand that adding a small epsilon value before rounding might help, like this:

Round(Value + Epsilon)

But I’m wondering if there is a more reliable or standard approach in Delphi to handle this situation? Are there any built-in functions or best practices to ensure that values that are effectively the same (like 21.499991254 and 21.5) round in a consistent manner?

Any advice or insights would be greatly appreciated!


Solution

  • As others have pointed out, the results you are getting are correct. Depending on how many digits you want to take into account, you could do something like:

    Round(Round(Value2 * 10) / 10);
    
    { What this is doing:                  }
    
    { Move the decimal place to the right: }
    {   21.499991254 * 10 = 214.99991254   }
    
    { Round the decimal places:            }
    {   Round(214.99991254) = 215.0        }
    
    { Move the decimal place back:         }
    {   215.0 / 10 = 21.5                  }
    
    { Round the decimal places:            }
    {   Round(21.5) = 22.0                 }
    

    If you want the rounding to be tighter, increase the number of decimal places being used in the *10 and /10 section.

    Note that the rounding towards the nearest even number is still in effect on the least significant digit in the calculation. So 21.4999 will return 22 but 20.4999 will return 20.

    If you want to always round up for numbers that end in .5 and higher, I think you'd need an if statement such as:

    if Frac(Value) >= 0.5 then
    begin
      Result := Ceil(Value);
    end
    else
    begin
      Result := Floor(Value);
    end;
    

    That being said, I'm very new to Delphi, so someone might have a better solution.

    I'd also like to add that if these values are a result of the floating point precision, then maybe change your variable data type to something more robust:

    Decimal1: Single;   //  7  significant digits, exponent   -38 to +38
    Decimal2: Currency; // 50+ significant digits, fixed 4 decimal places
    Decimal3: Double;   // 15  significant digits, exponent  -308 to +308
    Decimal4: Extended; // 19  significant digits, exponent -4932 to +4932
    

    Details of the data types from Delphi Basics.