In Delphi6 or in Delphi 2010, declare two variables of type Currency (vtemp1,vtemp2) and feed them a value of 0.09.
Embed one of the variables with the ABS function and compare it to the other.
You would expect for the comparison to yield a positive result as the compiler
watch reveals the same value for abs(vtemp1) and vtemp2.
Oddly the if statement fails!!!
Notes:
-This problem is experienced only when dealing with the
number 0.09 (trying several other near values revealed normal results)
-Declaring the variable as Double instead of currency, the problem ceases to exist.
I think that the reason is type conversions. Abs() function returns real results, so currency variable casts to real. Take a look at documentation:
Currency is a fixed-point data type that minimizes rounding errors in
monetary calculations. On the Win32 platform, it is stored as a scaled
64-bit integer with the four last 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.
so Currency is fixed and real is floating-point.
Sample code for your question is :
program Project3;
{$APPTYPE CONSOLE}
const VALUE = 0.09;
var a,b : currency;
begin
a := VALUE;
b := VALUE;
if a = Abs(b) then writeln('equal')
else writeln('not equal', a - Abs(b));
readln;
end.
produces not equal result, because of type conversions;
compiler watch reveals the same value for abs(vtemp1) and vtemp2
Try to add x : real, then call x := abs(b);, add x to watches list, select it and press Edit watch, then select Floating point. X becomes 0.899...967.
not only 0.09 value produces such result. you can try this code to check:
for i := 0 to 10000 do begin
a := a + 0.001;
b := a;
if a <> abs(b) then writeln('not equal', a);
end;
so, if you need absolute value of Currency variable - just do it. don't use floating-point abs():
function Abs(x : Currency):Currency; inline;
begin
if x > 0 then result := x
else result := -x;
end;
A little clarification. The 'issue' appears if float values are compared:
var
A: Currency;
begin
A:= 0.09;
Assert(A = Abs(A));
end;
That is because Abs(A) returns a float value, and A = Abs(A) is implemented as a float compare.
I could not reproduce it if Currency values are compared:
var
A, B: Currency;
begin
A:= 0.09;
B:= Abs(A);
Assert(A = B);
end;
But the second sample is also a potential bug because B:= Abs(A) internally is a float division/multiplication by 10000 with rounding to Currency (int64), and depends on FPU rounding mode.
I have created a qc report #107893, it was opened.
I have just found out the hard way that Delphi XE2 Abs function does not overload the currency type.
See the Delphi XE2 docwiki
these are the only types supported by abs
function Abs(X: ): Real; overload;
function Abs(X: ): Int64; overload;
function Abs(X: ): Integer; overload;
Related
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;
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;
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;
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.
use function System.Math.SimpleRoundTo
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
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.
You can do the following:
Multiply the value by 100.
Truncate to an integer, towards zero.
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.
You can use SetRoundMode with old Delphi RoundTo
SetRoundMode(rmDown);
function RoundTo(const AValue: Double; const ADigit: TRoundToRange): Double;
var
LFactor: Double;
begin
LFactor := IntPower(10, ADigit);
Result := Round(AValue / LFactor) * LFactor;
end;
Obviously it was changed in recent versions
i have the following code to round the currency
function MyRound(value :currency) : integer;
begin
if value > 0 then
result := Trunc(value + 0.5)
else
result := Trunc(value - 0.5);
end;
it worked well so far, my problem now is if i want to round a currency like 999999989000.40 it is giving negative value since Truc takes int and MyRound also returns int.
My possible solutions is to convert currency to string and get the string before . and convert the string back to currency. Is this a right approach? i am new to delpi so pls help me out.
From my point of view, you have two options:
You use the Round function, as David Heffernan pointed;
You can use the SimpleRoundTo function, as described here. The advantage of SimpleRoundTo is that it receives parameters of Single, Double and Extended data types and they convert round very well numbers like those stated.
You don't need any type conversions. There are plenty of rounding functions already available to you. Just round the desired number.
You are overcomplicating matters. You can simply use Round:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
C: Currency;
begin
C := 999999989000.4;
Writeln(Round(C));
C := 999999989000.5;
Writeln(Round(C));
C := 999999989000.6;
Writeln(Round(C));
C := 999999989001.4;
Writeln(Round(C));
C := 999999989001.5;
Writeln(Round(C));
C := 999999989001.6;
Writeln(Round(C));
Readln;
end.
which outputs
999999989000
999999989000
999999989001
999999989001
999999989002
999999989002
If you don't want banker's rounding, and you really do want your Trunc logic then you do need to write your own function. But the problem with your function is that it was truncating to 32 bit integer. Make the function return a 64 bit integer:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Math;
var
C: Currency;
function MyRound(const Value: Currency): Int64;
begin
if Value > 0 then
result := Trunc(Value + 0.5)
else
result := Trunc(Value - 0.5);
end;
begin
C := 999999989000.4;
Writeln(MyRound(C));
C := 999999989000.5;
Writeln(MyRound(C));
C := 999999989000.6;
Writeln(MyRound(C));
C := 999999989001.4;
Writeln(MyRound(C));
C := 999999989001.5;
Writeln(MyRound(C));
C := 999999989001.6;
Writeln(MyRound(C));
Readln;
end.
999999989000
999999989001
999999989001
999999989001
999999989002
999999989002
Take a look at John Herbster's rounding routines. 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.}
I can't give you a link right now, but Google: decimal rounding John Herbster
I think his latest rounding routines are in DecimalRounding_JH1.pas. His discussion of floating point rounding (somewhere on Embarcadero's website is a "must-read".
This is what I actually use (would love to hear if there is any problems with this approach!):
function RoundingFunction(X: Real): Int64;
begin
Result := Trunc(SimpleRoundTo(X, 0));
end;
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;
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;
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;
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.
use function System.Math.SimpleRoundTo
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
I have the following procedure :
procedure GetDegree(const num : DWORD ; var degree : DWORD ; min ,sec : Extended);
begin
degree := num div (500*60*60);
min := num div (500*60) - degree *60;
sec := num/500 - min *60 - degree *60*60;
end;
After degree variable gets assigned the debugger skips to the end of the procedure . Why is that?
It's an optimisation. The variables min and sec are passed by value. That means that modifications to them are not seen by the caller and are private to this procedure. Hence the compiler can work out that assigning to them is pointless. The values assigned to the variables can never be read. So the compiler elects to save time and skip the assignments.
I expect that you meant to declare the procedure like this:
procedure GetDegree(const num: DWORD; var degree: DWORD; var min, sec: Extended);
As I said in your previous question, there's not really much point in using Extended. You would be better off with one of the standard floating point types, Single or Double. Or even using the generic Real which maps to Double.
Also, you have declared min to be of floating point type, but the calculation computes an integer. My answer to your previous question is quite precise in this regard.
I would recommend that you create a record to hold these values. Passing three separate variables around makes your function interfaces very messy and breaks encapsulation. These three values only have meaning when consider as a whole.
type
TGlobalCoordinate = record
Degrees: Integer;
Minutes: Integer;
Seconds: Real;
end;
function LongLatToGlobalCoordinate(const LongLat: DWORD): TGlobalCoordinate;
begin
Result.Degrees := LongLat div (500*60*60);
Result.Minutes := LongLat div (500*60) - Result.Degrees*60;
Result.Seconds := LongLat/500 - Result.Minutes*60 - Result.Degrees*60*60;
end;
function GlobalCoordinateToLongLat(const Coord: TGlobalCoordinate): DWORD;
begin
Result := Round(500*(Coord.Seconds + Coord.Minutes*60 + Coord.Degrees*60*60));
end;