10

I'm trying to get Delphi to Round like Excel but I can't. Here is the code:

procedure TForm1.Button1Click(Sender: TObject);
var
 s : string;
 c : currency;
begin
 c := 54321.245;
 s := '';
 s := s + Format('Variable: %m',[c]);
 s := s + chr(13);
 s := s + Format('   Literal: %m',[54321.245]);
 ShowMessage(s);
end;

Delphi Rounding

I'm using a currency variable that is set to 54321.245 and when I format this variable it rounds using Bankers Rounding. However, when I format the same value as a literal it rounds the way that Excel rounds.

I was expecting this to round to $54,321.25 whether it's formating a currency variable or a literal value. How can I make sure that Delphi rounds the same way as Excel every time?

Edit

The rounding I expect to see is as follows:  
54,321.245   = 54,321.25  
54,321.2449  = 54,321.24  
54,431.2499  = 54,421.25 

I am only using literals to show the different ways Delphi rounds. I expect to use variables in the actual code.

Note:
If I change the variable from currency to extended it rounds correctly

Edit #2

Some have suggested that I do not have a clear understanding of my requirements, this is absolutely not true. I have a very clear understanding of my requirements, I'm obviously not doing a very good job of explaining them. The rounding method I want is two decimal places. When the deimal part has a thousandths value >= 0.005 I want it rounded to 0.01 the currency type offered by Delphi does not do this. I also tried this example using Microsoft SQL with a money datatype (which I assumed was the same as Delphi's currency) and SQL rounds it's money type the way I described.

  • SQL Money >= 0.005 = 0.01
  • Delphi Currency >= 0.005 := 0.00

Edit #3
Good Article: http://rvelthuis.de/articles/articles-floats.html
Possible Solution: http://rvelthuis.de/programs/decimals.html

Edit #4
Here is one of the solutions from the Embarcadero discussion

function RoundCurrency(const Value: Currency): Currency;
var
  V64: Int64 absolute Result;
  Decimals: Integer;
begin
  Result := Value;
  Decimals := V64 mod 100;
  Dec(V64, Decimals);
  case Decimals of
    -99 .. -50 : Dec(V64, 100);
    50 .. 99 : Inc(V64, 100);
  end;
end;
  • 1
    Are you sure that Excel is 100% consistent in rounding throughout all its APIs and features? – Warren P Jun 10 '12 at 01:49
  • @Warren - No I'm not 100% sure. I sell financial applications and I need to make sure that my numbers match what Excel puts out. – Michael Riley - AKA Gunny Jun 10 '12 at 01:54
  • 2
    To properly answer this question, you really need to tell us what kind of rounding you expect. – Nick Hodges Jun 10 '12 at 02:35
  • 2
    Unless you carry exactly the same precision and do calculations in the same order you can't count on 100% matching any other program. Furthermore, I don't think Excel supports fixed-precision data, Excel will sometimes get the financial calculations wrong. – Loren Pechtel Jun 10 '12 at 04:23
  • You still haven't given any indication of what rounding method you want. Three examples does not a specification make. Until you have a clear understanding of your requirements, how can you hope to meet those requirements? – David Heffernan Jun 10 '12 at 06:27
  • @Sertac - That is exactly what I was looking for. Make this an answer I you get my vote. – Michael Riley - AKA Gunny Jun 10 '12 at 12:59
  • Also checkout workarrounds in other languages that may be applicable https://stackoverflow.com/questions/2221167/javascript-formatting-a-rounded-number-to-n-decimals/56632526#56632526 – George Birbilis Aug 12 '19 at 15:11

4 Answers4

16

If I understand you correctly, you are looking for this:

function RoundTo2dp(Value: Currency): Currency;
begin
  Result := Trunc(Value*100+IfThen(Value>0, 0.5, -0.5))/100;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • As far as I've read so far, I can tell using the FPU when dealing with currency is not recommended. It can introduce hard to track rounding issues. There are solutions in the thread I linked that avoid this or do it with great care. – Sertac Akyuz Jun 10 '12 at 16:38
  • @SertacAkyuz What are you driving at? Arithmetic in Currency makes use of FPU I think. – David Heffernan Jun 10 '12 at 17:06
  • @Sertac - I read that thread plus Rudy's blog. David's solution is very good. It does not violate any of the conversion issues that were talked about. – Michael Riley - AKA Gunny Jun 10 '12 at 17:13
  • @David - I'm not quite sure what I'm driving at... - Cape - I have not answered your question in the title anyway. If you can change the accept, I'll be more comfortable. – Sertac Akyuz Jun 10 '12 at 17:15
  • David, just to let you know... I'm totally *in love* with this func and I use it in my code :) – Interface Unknown Oct 27 '15 at 09:05
  • Note this will fail for some large numbers. For Win-32: 147_573_952_589_676.4250. For Win-64: 72_057_594_037.9450. – David Dubois Aug 07 '22 at 13:32
6

It's not possible to make RTL to round the way you want. The way to affect the rounding in Delphi is to use SetRoundMode which sets the FPU conrol word for rounding, however, as far as I can tell, there's no FPU support for rounding the exact in-between to upwards (which is generally avoided because it generates a bias for higher values).

You have to implement your own rounding function. There's an extended discussion in Delphi Rounding thread on Embarcadero forums, which includes several solutions.

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
1

use function System.Math.SimpleRoundTo

Ravaut123
  • 2,764
  • 31
  • 46
0

You can gain control on how delphi rounding numbers by :

uses Math;
...

procedure TForm1.Button1Click(Sender: TObject);
var
s : string;
c : currency;
begin
 SetRoundMode(rmNearest);

 c := 54321.245;
 s := '';
 s := s + Format('Variable: %m',[c]);
 s := s + chr(13);
 s := s + Format('   Literal: %m',[54321.245]);
 ShowMessage(s);
end;

Unfortunately, using rmNearest, Delphi decides the number 54321.245 is closer to 54321.24 than 54321.25

Hendra
  • 720
  • 4
  • 8
  • That's not exactly true. If you change the value of c := 54321.244 it formats as $54,321.45 and it should be $54,321.44 (I wish it were that simple). It appears that using an extended type instead of currency is the work around for this. – Michael Riley - AKA Gunny Jun 10 '12 at 04:21
  • Don't appologize, we are all learning. It appears that the currency type does not round or format like the extended type. I just looked at several of the financial functions inside Delphis's Math unit and not one of them uses a currency type. They all use the extended type. So, if it works for Delphi's Math unit it works for me. :-) – Michael Riley - AKA Gunny Jun 10 '12 at 04:41
  • 1
    54321.245 is not exactly representable in floating point, so once again the example in this answer turn out to illustrate issues of representability and not issues of rounding. @Cape Financial calculations are no good with binary floating point. You really need to use decimal arithmetic. The .net decimal type is effective. It's rather a shame that Delphi is so lame in this area. – David Heffernan Jun 10 '12 at 06:22