Sunday, August 11, 2019

Delphi Tip of the Day - Rounding (Again!)

It's been over 7 years since I dove into the mystical world of rounding numbers within Delphi. It's amazing how much code actually gets written in support of the main stuff an application does. I am so glad I wrote my blog... it's the one place I can go back to when I find gaps from my hard drive crash of 2018.

I ran into the old familiar rounding issue - again. And of course, I didn't have the code I used to get it squared away.

My original blog post from 7 years ago, "Rounding the Currency type like they taught us in school", explains the issue in full detail and nothing has changed with regard to how Delphi handles rounding.

And as usual David Heffernan gave...


So I went back to the Stack Overflow post to revisit the answers given to my original question. And as usual David Heffernan gave a very straightforward and succinct answer.

{---------------------------------------------------------------}
{ Added 2019-08-07                                              }
{                                                               }
{ Used to solve the Delphi's rounding function.                 }
{                                                               }
{ https://stackoverflow.com/q/10965759/195983                   }
{ https://stackoverflow.com/a/10970283/195983                   }
{---------------------------------------------------------------}
Function RoundTo2dp (Value: Currency): Currency;
begin
  Result := Trunc(Value*100+IfThen(Value>0, 0.5, -0.5))/100;
end;

I added the code to my StrStuff unit. (Does everyone have a StrStuff unit or is it just me?) And it did not compile. So, when I got done cussing out Heffernan for putting bum code out there, I did some digging.

It turns out there are two IfThen functions. One in the SystemStrUtils unit and the other one in the System.Math unit. And of course, I chose the wrong unit.

This specific IfThen function is from System.Math


This specific IfThen function is from the System.Math unit:
unit Strstuff;

interface

uses System.Sysutils, System.Math;

Function RoundTo2dp (Value: Currency): Currency;
And of course after I put the correct unit in the uses clause it worked like a champ. I must admit, it felt good cussing out Heffernan. But as usual... he's right. Again!

Thank you David.


Enjoy!
Semper Fi
Gunny Mike
https://zilchworks.com


External Reference Update: 2021-06-04



8 comments:

  1. Using implicit order of uses statements is clever, but error-prone.

    I encourage you to help yourself and others understand your code and avoid errors in the future by explicitly naming the source unit, like this:

    Result := Trunc(Value*100 + SYSTEM.MATH.IfThen(Value>0, 0.5, -0.5))/100;

    ReplyDelete
    Replies
    1. Thank you for pointing that out Tom.

      Delete
    2. IfThen is overloaded, so no need for a ordered uses list or full qualified calls of the function in this case!

      Delete
  2. There are even more overloaded ifthen functions in various units.

    As for a Strstuff unit: I started off with a twmStringUtils unit, but over the more than 20 years it evolved into quite a long unit, so I subdivided it into several units.

    ReplyDelete
  3. It also works with System.Math.SimpleRoundTo

    ReplyDelete
    Replies
    1. SimpleRoundTo not always accurate on Windows 64bit.
      https://stackoverflow.com/q/61763655/195983

      Delete
  4. You might wish to take a look at John Herbster's rounding routines:

    https://cc.embarcadero.com/Item/21909

    They offer nearly any type of rounding you might want, e.g.:

    drNone, {No rounding.}
    drHalfEven,{Round to nearest or to even whole number. (a.k.a Bankers) }
    drHalfPos, {Round to nearest or toward positive.}
    drHalfNeg, {Round to nearest or toward negative.}
    drHalfDown,{Round to nearest or toward zero.}
    drHalfUp, {Round to nearest or away from zero.}
    drRndNeg, {Round toward negative. (a.k.a. Floor) }
    drRndPos, {Round toward positive. (a.k.a. Ceil ) }
    drRndDown, {Round toward zero. (a.k.a. Trunc) }
    drRndUp); {Round away from zero.}

    He also wrote a great paper on the ins and outs of rounding, but I cannot find the link right now.

    ReplyDelete
  5. Here are two external references I learned about today:

    https://www.gammon.com.au/forum/?id=7805
    https://en.wikipedia.org/wiki/Rounding

    ReplyDelete