admin管理员组

文章数量:1122846

Consider this function:

function DollarsToCents(dollars: Currency): Integer;
begin
  result := Trunc(dollars * 100);
end;

The expression dollars * 100 will get converted to an Extended, I believe, which is a binary floating point type, and as a general rule money should not be put into binary floating point due to the possibility of "representation error".

So, my question is: is this code safe? In our case we have no fractional cents, and a typical input to the function might be something like 1.23.

Or is there a better way to do it?

Consider this function:

function DollarsToCents(dollars: Currency): Integer;
begin
  result := Trunc(dollars * 100);
end;

The expression dollars * 100 will get converted to an Extended, I believe, which is a binary floating point type, and as a general rule money should not be put into binary floating point due to the possibility of "representation error".

So, my question is: is this code safe? In our case we have no fractional cents, and a typical input to the function might be something like 1.23.

Or is there a better way to do it?

Share Improve this question edited Nov 22, 2024 at 11:44 AmigoJack 5,9481 gold badge19 silver badges33 bronze badges asked Nov 22, 2024 at 8:35 dan-gphdan-gph 16.9k13 gold badges64 silver badges80 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 5

Currency is internally a 64-bit integer expressed in units of 0,01 cents, so $1.00 = 10000 Currency. Taking advantage of this knowledge, you can do it this way:

function DollarsToCents(dollars: currency): integer;
  var 
    I64 : Int64 ABSOLUTE dollars;

  begin
    Result:=I64 DIV 100;
  end;

The ABSOLUTE keyword overlays the I64 variable on top of the dollars parameter, so you can access it as the underlying 64-bit integer.

@HeartWare has given the best answer, but I want to answer the part of the question about whether or not the original DollarsToCents function is safe. I did a test, and I think the answer is "probably yes".

If anyone is wondering about why it might not be safe, 0.03 for example, as a double is actually 0.0299999999999999988897769753748434595763683319091796875 because that value can't be represented exactly in binary floating point.

And if we multiplied 0.02999... by 100 and did a Trunc, we might expect to get 0.02 instead of 0.03. So converting $0.03 to cents would incorrectly give us 2 cents.

But in the real world it seems that we actually do get 3 cents. There must be some rounding happening somewhere along the way.

Here's my test. I compared the old DollarsToCents to HeartWare's version for each cent value from 1.00 to 1.99, and they agreed with each other.

program centsTest;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function DollarsToCents_Old(dollars: Currency): Integer;
begin
  result := Trunc(dollars * 100);
end;

function DollarsToCents(dollars: currency): integer;
var
  I64 : Int64 ABSOLUTE dollars;
begin
  Result:=I64 DIV 100;
end;

var
  i: integer;
  dollars: currency;
  oneCent: currency;
  oneCentI64: Int64 absolute oneCent;
begin
  oneCentI64 := 0100;
  dollars := 1;
  for i := 0 to 99 do
  begin
    if DollarsToCents_Old(dollars) <> DollarsToCents(dollars) then
      raise Exception.CreateFmt('Failed: %f', [dollars]);
    Writeln(Format('%f', [dollars]));
    dollars := dollars + oneCent;
  end;
end.

本文标签: delphiConvert Currency amount to cents without going through floating pointStack Overflow