Search code examples
c++c++buildercurrency

System::Currency and C++ Builder


I am using Embarcadero C++ Builder XE10 for building an application that does some monetary calculations.

While trying to use the System::Currency datatype I am facing a couple of problems.

Q1: Why does the calculation of the modulus fail when using the "%" operator?

System::Currency TaxValue = 1.665;
System::Currency Rest = 0;

// Correct result: Rest will be 0.005 when converted to double
Rest = TaxValue - ( TaxValue / 100 * 100 );

// Incorrect result: Rest is the same as TaxValue
Rest = TaxValue % 100;

Edit: I have been totally fooled by the debugger's output where the System::Currency's value is shown in its integer represention multiplied by 10.000.

What I really expected to see was:

Rest = (TaxValue * 10000) % 100;

==> Now Rest is 50, which is exactly what I expected.

Q2: How can I do a correct banker's rounding with the Curreny data type?

Examples:

1.664 => 1.66
1.665 => 1.67
1.666 => 1.67

Herwig


Solution

  • Q1: Why does the calculation of the modulus fail when using the "%" operator?

    System::Currency operates with a precision of 4 decimal places. Your example expects 2-digit precision instead.

    System::Currency maintains its precision without rounding errors by internally multiplying input values by 10000 and then using integer math instead of floating-point math to manipulate values.

    When you initialize TaxValue with 1.665, its internal Val member (which is an __int64) is set to (1.665 * 10000) = 16650. Here is what that constructor looks like:

    __fastcall Currency(double val) {Val = _roundToInt64(10000 * val);}
    

    When you then perform TaxValue % 100, the % operator is implemented like this:

    Currency __fastcall operator %(int rhs) const
    {return Currency(static_cast<int>(Val % (10000 * (__int64)rhs))) / 10000;}
    

    The first part creates a temp Currency object that is initialized with an int value of (16650 % (10000 * 100)) = 16650, which gets multiplied by 10000 to 166500000 by the constructor of the temp object:

    __fastcall Currency(int val) {Val = 10000*(__int64)val;}
    

    The second part then divides the temp by 10000. The / operator is implemented like this:

    Currency& __fastcall operator /=(const Currency& rhs)
    {Val *= 10000; Val /= rhs.Val; return *this;}
    
    Currency __fastcall operator /(int rhs) const
    {Currency tmp(*this); return tmp /= Currency(rhs);}
    

    Thus producing a final Currency object whose Val has been set to (166500000 * 10000) / (10000 * 10000) = 16650.

    When that final Currency is then assigned to Rest and converted to double, the value is divided by 10000, thus producing 1.665:

    __fastcall operator double() const {return ((double)Val) / 10000;}
    

    Q2: How can I do a correct banker's rounding with the Curreny data type?

    Have a look at the System::Round() function, which uses banker's rounding.

    If you want more control over the rounding, use the System::Math::RoundTo() function, or find a 3rd party rounding function.

    There are several other questions on StackOverflow that deal with Currency rounding, for instance:

    How to get Delphi Currency Type to Round like Excel all the time?

    rounding a currency

    (System::Currency is C++Builder's wrapper for Delphi's native Currency type).