Search code examples
delphirounding

Delphi - RoundTo - always down


I need to round a floating point number to two decimal places, but always down. Now I use RoundTo(number, -2), but it does the rounding mathematically correctly, which is undesired behavior for my situation. Let the reason, why I need to do this, aside...

I eventually achieved it using this:

var a,b: currency;
    floatStr: string;
    format: TFormatSettings;
    localeDec: char;
begin


  format:= TFormatSettings.Create;
  localeDec:= format.DecimalSeparator;
  format.DecimalSeparator:= ',';
  System.SysUtils.FormatSettings:= format;

  a:= 2/30;
  floatStr:= floatToStr(a);
  b:= strToCurr(
      copy(floatStr, 1, ansiPos(',', floatStr) + 2)
  );
  showMessage(currToStr(b));

  format.DecimalSeparator := localeDec;
  System.SysUtils.FormatSettings:= format;

end;

However, this solution just doesn't feel right. Is there a "mathematically clean" way to do it, without messing with strings and resetting decimal separators etc. ? I searched a lot, but didn't find any.


Solution

  • You can do the following:

    1. Multiply the value by 100.
    2. Truncate to an integer, towards zero.
    3. Divide the value by 100.

    Like this:

    function RoundCurrTo2dpTruncate(const Value: Currency): Currency;
    begin
      Result := Trunc(Value*100) / 100;
    end;
    

    I've assumed that by rounding down you mean towards zero. So 0.678 rounds down to 0.67 and -0.678 to -0.67. However, if you want to round towards -∞ then you should replace Trunc with Floor.

    function RoundCurrTo2dpDown(const Value: Currency): Currency;
    begin
      Result := Floor(Value*100) / 100;
    end;
    

    Another way to tackle the problem is to recognise that a Currency value is simply a 64 bit integer with an implicit shift of 10000. So the entire operation can be performed using integer operations, unlike the code above which uses floating point operations.

    From the documentation:

    Currency is a fixed-point data type that minimizes rounding errors in monetary calculations. It is stored as a scaled 64-bit integer with the 4 least significant digits implicitly representing decimal places. When mixed with other real types in assignments and expressions, Currency values are automatically divided or multiplied by 10000.

    For example you could implement RoundCurrTo2dpTruncate like this:

    function RoundCurrTo2dpTruncate(const Value: Currency): Currency;
    begin
      PInt64(@Result)^ := (PInt64(@Value)^ div 100)*100;
    end;
    

    Note that here the arithmetic has been an shifted by 10000. So multiplication by 100 has become division by 100. And so on.