Currency

From Rosetta Code
Task
Currency
You are encouraged to solve this task according to the task description, using any language you may know.
Task

Show how to represent currency in a simple example, using a data type that represent exact values of dollars and cents.


Note

The IEEE 754 binary floating point representations of numbers like   2.86   and   .0765   are not exact.

For this example, data will be two items with prices in dollars and cents, a quantity for each, and a tax rate.

Use the values:

  • 4000000000000000 hamburgers at $5.50 each       (four quadrillion burgers)
  • 2 milkshakes at $2.86 each, and
  • a tax rate of 7.65%.


(That number of hamburgers is a 4 with 15 zeros after it.   The number is contrived to exclude naïve task solutions using 64 bit floating point types.)

Compute and output (show results on this page):

  • the total price before tax
  • the tax
  • the total with tax


The tax value must be computed by rounding to the nearest whole cent and this exact value must be added to the total price before tax.

The output must show dollars and cents with a decimal point.

The three results displayed should be:

  • 22000000000000005.72
  • 1683000000000000.44
  • 23683000000000006.16


Dollar signs and thousands separators are optional.

11l

Translation of: Nim
F currency(units, subunits)
   R BigInt(units) * 100 + subunits

F currency_from_str(s)
   V (units, subunits) = s.split(‘.’)
   R BigInt(units) * 100 + Int(subunits)

F percentage(a, num, denom)
   R (a * num * 10 I/ denom + 5) I/ 10

F to_str(c)
   R String(c I/ 100)‘’‘.#02’.format(c % 100)

V hamburgers = currency(5, 50) * 4'000'000'000'000'000
V milkshakes = currency_from_str(‘2.86’) * 2
V beforeTax = hamburgers + milkshakes
V tax = percentage(beforeTax, 765, 10'000)
V total = beforeTax + tax

V maxlen = max(to_str(beforeTax).len, to_str(tax).len, to_str(total).len)

print(‘Total price before tax: ’to_str(beforeTax).rjust(maxlen))
print(‘Tax:                    ’to_str(tax).rjust(maxlen))
print(‘Total with tax:         ’to_str(total).rjust(maxlen))
Output:
Total price before tax: 22000000000000005.72
Tax:                     1683000000000000.44
Total with tax:         23683000000000006.16

Ada

Numeric constants in Ada do not have to be given an explicit type. When used in constant expressions together, they have arbitrary precision. This enables simple calculation of the values. Ada also has builtin fixed-point types, which can define a minimum interval between decimal values (in this case, 1 cent or $0.01).

When using the Dollar_IO package to print each value, the constant is converted into a Dollar, then printed. All of the arbitrary-precision arithmetic operations are done at compile-time, incurring no runtime cost.

with Ada.Text_IO;

procedure Currency is
   type Dollar is delta 0.01 range 0.0 .. 24_000_000_000_000_000.0;
   package Dollar_IO is new Ada.Text_IO.Fixed_IO(Dollar);

   hamburger_cost : constant := 5.50;
   milkshake_cost : constant := 2.86;
   tax_rate : constant := 0.0765;

   total_cost : constant := hamburger_cost * 4_000_000_000_000_000.0 + milkshake_cost * 2;
   total_tax : constant := total_cost * tax_rate;
   total_with_tax : constant := total_cost + total_tax;
begin
   Ada.Text_IO.Put("Price before tax:");
   Dollar_IO.Put(total_cost);
   Ada.Text_IO.New_Line;

   Ada.Text_IO.Put("Tax:             ");
   Dollar_IO.Put(total_tax);
   Ada.Text_IO.New_Line;

   Ada.Text_IO.Put("Total:           ");
   Dollar_IO.Put(total_with_tax);
   Ada.Text_IO.New_Line;
end Currency;
Output:
Price before tax: 22000000000000005.72
Tax:               1683000000000000.44
Total:            23683000000000006.16

ALGOL 68

Works with: ALGOL 68G version Any - tested with release 2.8.3.win32
BEGIN
    # currency calculations                                              #
    # simple fixed point type, LONG INT is 64-bit in Algol 68G           #
    MODE FIXED    = STRUCT( LONG INT value
                          , INT      decimals
                          , INT      fraction modulus
                          );
    # make CURRENCY a synonym for FIXED                                  #
    MODE CURRENCY = FIXED;
    # dyadic operator so we can write e.g. 5 DOLLARS 50 to construct     #
    # a CURRENCY value with 2 decimal places                             #
    PRIO DOLLARS = 9;
    OP DOLLARS = ( LONG INT v, INT dp )CURRENCY: ( ( v * 100 ) + dp, 2, 100 );
    OP DOLLARS = (      INT v, INT dp )CURRENCY: LENG v DOLLARS dp;
    # issues an error message and stops the program if a has a different #
    # number of decimal places to b                                      # 
    PROC check compatible = ( CURRENCY a, b )VOID:
         IF decimals OF a /= decimals OF b THEN print( ( "Incompatible CURRENCY values", newline ) ); stop FI;
    # operators to multiply CURRENCY values by integers                  #
    OP * = ( CURRENCY v, LONG INT m )CURRENCY: ( value OF v * m, decimals OF v, fraction modulus OF v );
    OP * = ( CURRENCY v,      INT m )CURRENCY: v * LENG m;
    # returns the CURRENCY value a + b                                   #
    OP + = ( CURRENCY a, CURRENCY b )CURRENCY:
         BEGIN
            check compatible( a, b );
            ( value OF a + value OF b, decimals OF a, fraction modulus OF a ) 
         END # + # ;
    # multiplies the CURRENCY value a by the FIXED value b,              #
    # rounding the result to the decimal places of a                     #
    OP * = ( CURRENCY a, FIXED b )CURRENCY:
         BEGIN
            LONG INT result := ( value OF a * value OF b );
            IF decimals OF b > 0 THEN
                INT d                = fraction modulus OF b;
                LONG INT abs result := ABS result;
                INT extra places     = SHORTEN ( abs result MOD d );
                abs result OVERAB d;
                IF extra places >= d OVER 2 THEN abs result +:= 1 FI;
                IF result < 0 THEN result := - abs result ELSE result := abs result FI
            FI;
            ( result, decimals OF a, fraction modulus OF a )
         END # * # ;
    # converts a FIXED value to a STRING with the appropriate number of  #
    # decimal places                                                     #
    OP TOSTRING = ( FIXED v )STRING:
         IF decimals OF v < 1 THEN
            whole( value OF v, 0 )
         ELSE
            INT    d       = fraction modulus OF v;
            STRING result := whole( value OF v OVER d, 0 );
            STRING dp     := whole( ( ABS value OF v ) MOD d, - decimals OF v );
            FOR i FROM LWB dp TO UPB dp DO IF dp[ i ] = " " THEN dp[ i ] := "0" FI OD;
            result + "." + dp
         FI # TOSTRING # ;
    # Task calculations                                                   #
    CURRENCY hb    = 5 DOLLARS 50 * LONG 4000000000000000;
    CURRENCY ms    = 2 DOLLARS 86 * 2;
    FIXED    rate  = ( 765, 4, 10 000 ); # 0.0765                         #
    CURRENCY net   = hb + ms;
    CURRENCY tax   = net * rate;
    CURRENCY total = net + tax;
    print( ( "before tax: ", TOSTRING net,   newline ) );
    print( ( "tax:        ", TOSTRING tax,   newline ) );
    print( ( "total:      ", TOSTRING total, newline ) )
END
Output:
before tax: 22000000000000005.72
tax:        1683000000000000.44
total:      23683000000000006.16

AppleScript

The AppleScript core language doesn't recognise currency values specifically and its numbers don't have the precision required for this task, but through ASObjC, it's able to use Foundation classes which do.

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"

-- Derive an NSDecimalNumber from an AppleScript number or numeric text.
-- NSDecimalNumbers also allow arithmetic and have a far greater range than AS numbers.
on decimalNumberFrom(n)
    return current application's class "NSDecimalNumber"'s decimalNumberWithString:(n as text)
end decimalNumberFrom

-- Multiply two NSDecimalNumbers.
on multiply(dn1, dn2)
    return dn1's decimalNumberByMultiplyingBy:(dn2)
end multiply

-- Add two NSDecimalNumbers.
on add(dn1, dn2)
    return dn1's decimalNumberByAdding:(dn2)
end add

on billTotal(quantitiesAndPrices, taxRate, currencySymbol)
    -- Set up an NSNumberFormatter for converting between currency strings and NSDecimalNumbers.
    set currencyFormatter to current application's class "NSNumberFormatter"'s new()
    tell currencyFormatter to setNumberStyle:(current application's NSNumberFormatterCurrencyStyle)
    tell currencyFormatter to setCurrencySymbol:(currencySymbol)
    tell currencyFormatter to setGeneratesDecimalNumbers:(true)
    
    -- Tot up the bill from the list of quantities (numbers or numeric strings) and unit prices (currency strings with symbols).
    set subtotal to decimalNumberFrom(0) -- or:  current application's class "NSDecimalNumber"'s zero()
    repeat with thisEntry in quantitiesAndPrices
        set {quantity:quantity, unitPrice:unitPrice} to thisEntry
        set entryTotal to multiply(decimalNumberFrom(quantity), currencyFormatter's numberFromString:(unitPrice))
        set subtotal to add(subtotal, entryTotal)
    end repeat
    -- Work out the tax and add it to the subtotal.
    set tax to multiply(subtotal, decimalNumberFrom(taxRate / 100))
    set total to add(subtotal, tax)
    
    -- Format and return the results.
    return (current application's class "NSString"'s stringWithFormat_("Subtotal:  %@
Tax:  %@
Total:  %@", ¬
        currencyFormatter's stringFromNumber:(subtotal), ¬
        currencyFormatter's stringFromNumber:(tax), ¬
        currencyFormatter's stringFromNumber:(total))) ¬
        as text
end billTotal

-- Demo code:
set currencySymbol to "$"
set quantitiesAndPrices to {{quantity:"4000000000000000", unitPrice:currencySymbol & "5.50"}, ¬
    {quantity:2, unitPrice:currencySymbol & 2.86}}
set taxRate to 7.65
return billTotal(quantitiesAndPrices, taxRate, currencySymbol)
Output:
"Subtotal:  $22,000,000,000,000,005.72
Tax:  $1,683,000,000,000,000.44
Total:  $23,683,000,000,000,006.16"

AWK

version 1

# syntax: GAWK -M -f CURRENCY.AWK
# using GNU Awk 4.1.1, API: 1.1 (GNU MPFR 3.1.2, GNU MP 5.1.2)
BEGIN {
    PREC = 100
    hamburger_p = 5.50
    hamburger_q = 4000000000000000
    hamburger_v = hamburger_p * hamburger_q
    milkshake_p = 2.86
    milkshake_q = 2
    milkshake_v = milkshake_p * milkshake_q
    subtotal = hamburger_v + milkshake_v
    tax = subtotal * .0765
    printf("%-9s %8s %18s %22s\n","item","price","quantity","value")
    printf("hamburger %8.2f %18d %22.2f\n",hamburger_p,hamburger_q,hamburger_v)
    printf("milkshake %8.2f %18d %22.2f\n\n",milkshake_p,milkshake_q,milkshake_v)
    printf("%37s %22.2f\n","subtotal",subtotal)
    printf("%37s %22.2f\n","tax",tax)
    printf("%37s %22.2f\n","total",subtotal+tax)
    exit(0)
}

Output:

item         price           quantity                  value
hamburger     5.50   4000000000000000   22000000000000000.00
milkshake     2.86                  2                   5.72

                             subtotal   22000000000000005.72
                                  tax    1683000000000000.41
                                total   23683000000000006.13

version 2

# syntax: GAWK -M -f CURRENCY2.AWK
# using GNU Awk 4.1.1, API: 1.1 (GNU MPFR 3.1.2, GNU MP 5.1.2)
# INT is used to define values and do math; results then converted to FLOAT
BEGIN {
    PREC = 100
    hamburger_p = 550
    hamburger_q = 4000000000000000
    hamburger_v = hamburger_p * hamburger_q
    milkshake_p = 286
    milkshake_q = 2
    milkshake_v = milkshake_p * milkshake_q
    subtotal = hamburger_v + milkshake_v
    tax = subtotal * 765
    subtotal /= 100
    tax /= 1000000
    printf("%-9s %8s %18s %22s\n","item","price","quantity","value")
    printf("hamburger %8.2f %18d %22.2f\n",hamburger_p/100,hamburger_q,hamburger_v/100)
    printf("milkshake %8.2f %18d %22.2f\n\n",milkshake_p/100,milkshake_q,milkshake_v/100)
    printf("%37s %22.2f\n","subtotal",subtotal)
    printf("%37s %22.2f\n","tax",tax)
    printf("%37s %22.2f\n","total",subtotal+tax)
    exit(0)
}

Output:

item         price           quantity                  value
hamburger     5.50   4000000000000000   22000000000000000.00
milkshake     2.86                  2                   5.72

                             subtotal   22000000000000005.72
                                  tax    1683000000000000.44
                                total   23683000000000006.16

BBC BASIC

      REM No need for BigNum library.
      REM This language uses 80bit (10 bytes!) for real values internally.
      Price = 4E15 * 5.50 + 2.0 * 2.86
      Tax   = Price * .0765
      Total = Price + Tax

      REM Number printing will use 2 decimal places and 21 positions zone
      @%=&020215
      PRINT "Price = $" Price
      PRINT "Tax   = $" Tax
      PRINT "Total = $" Total
Output:
Price = $ 22000000000000005.72
Tax   = $  1683000000000000.44
Total = $ 23683000000000006.16

Bracmat

The amounts before-tax, tax, after-tax are computed as rounded amounts in dollar cents.

  div$((4000000000000000*550+2*286)+1/2,1):?before-tax
& div$(!before-tax*765/10000+1/2,1):?tax
& !before-tax+!tax:?after-tax
& ( fix
  =   cents dollars
    .   mod$(!arg.100):?cents
      & ( !cents:<10&0 !cents:?cents
        |
        )
      & div$(!arg.100):?dollars
      & str$(!dollars "." !cents)
  )
&   str
  $ ( "before-tax "
      fix$!before-tax
      "\ntax "
      fix$!tax
      \n
      "after-tax "
      fix$!after-tax
      \n
    )

Output

before-tax 22000000000000005.72
tax 1683000000000000.44
after-tax 23683000000000006.16

C

This implementation uses the GMP library for arbitrary precision arithmetic. The only data type used here is mpf_t, the following text is from the GMP documentation :

Floating point number or Float for short, is an arbitrary precision mantissa with a limited precision exponent. The C data type for such objects is mpf_t. For example:

mpf_t fp;

One remark about the code, notice that for setting all other variables the mpf_set_d function is used:

	mpf_set_d(burgerUnitPrice,5.50);
	mpf_set_d(milkshakePrice,2 * 2.86);
	mpf_set_d(burgerNum,4000000000000000);
	mpf_set_d(milkshakeNum,2);

But when it comes to the tax rate, it's mpf_set_str:

	mpf_set_str(tax,"0.0765",10);

The reason is a weird rounding off error which happens if the mpf_set_d function is used. Documentation and example usages of GMP are very rare on the net possibly because it is used almost exclusively by academia and high tech industries. The implementation below is the result of a lot of fiddling, gotchas and lessons learnt, just how good programming should always be :)

Library: GMP
#include<stdio.h>
#include<gmp.h>

int main()
{
	mpf_t burgerUnitPrice, milkshakePrice, burgerTotalPrice, totalPrice, tax, burgerNum, milkshakeNum;
	
	mpf_inits(burgerUnitPrice, milkshakePrice, burgerTotalPrice, totalPrice, tax,burgerNum, milkshakeNum,NULL);
	
	mpf_set_d(burgerUnitPrice,5.50);
	mpf_set_d(milkshakePrice,2 * 2.86);
	mpf_set_d(burgerNum,4000000000000000);
	mpf_set_d(milkshakeNum,2);
	
	mpf_mul(burgerTotalPrice,burgerNum,burgerUnitPrice);
	mpf_add(totalPrice,burgerTotalPrice,milkshakePrice);
	
	mpf_set_str(tax,"0.0765",10);
	mpf_mul(tax,totalPrice,tax);
	
	gmp_printf("\nTotal price before tax : $ %.*Ff", 2, totalPrice);
	gmp_printf("\nTotal tax : $ %.*Ff", 2, tax);
	
	mpf_add(totalPrice,totalPrice,tax);
	
	gmp_printf("\nTotal price after tax : $ %.*Ff", 2, totalPrice);
	
	return 0;
}

Output:

Total price before tax : $ 22000000000000005.72
Total tax : $ 1683000000000000.44
Total price after tax : $ 23683000000000006.16

C#

The built in C# type decimal has a max value of 79228162514264337593543950335.

using System;
using System.Collections.Generic;

namespace Currency
{
    class Program
    {
        static void Main(string[] args)
        {
            MenuItem hamburger = new MenuItem() { Name = "Hamburger", Price = 5.5M };
            MenuItem milkshake = new MenuItem() { Name = "Milkshake", Price = 2.86M };

            IList<CartItem> cart = new List<CartItem>();
            cart.Add(new CartItem() { item = hamburger, quantity = 4000000000000000 });
            cart.Add(new CartItem() { item = milkshake, quantity = 2 });

            decimal total = CalculateTotal(cart);

            Console.WriteLine(string.Format("Total before tax: {0:C}", total));

            // Add Tax
            decimal tax = total * 0.0765M;

            Console.WriteLine(string.Format("Tax: {0:C}", tax));

            total += tax;

            Console.WriteLine(string.Format("Total with tax: {0:C}", total));
        }

        private static decimal CalculateTotal(IList<CartItem> cart)
        {
            decimal total = 0M;

            foreach (CartItem item in cart)
            {
                total += item.quantity * item.item.Price;
            }

            return total;
        }

        private struct MenuItem
        {
            public string Name { get; set; }
            public decimal Price { get; set; }
        }

        private struct CartItem
        {
            public MenuItem item { get; set; }
            public decimal quantity { get; set; }
        }
    }
}
Output:
Total before tax: $22,000,000,000,000,005.72
Tax: $1,683,000,000,000,000.44
Total with tax: $23,683,000,000,000,006.16

Clojure

(require '[clojurewerkz.money.amounts    :as ma])
(require '[clojurewerkz.money.currencies :as mc])
(require '[clojurewerkz.money.format     :as mf])

(let [burgers (ma/multiply (ma/amount-of mc/USD 5.50) 4000000000000000) 
      milkshakes (ma/multiply (ma/amount-of mc/USD 2.86) 2)
      pre-tax (ma/plus burgers milkshakes)
      tax (ma/multiply pre-tax 0.0765 :up)]
  (println "Total before tax: " (mf/format pre-tax))
  (println "             Tax: " (mf/format tax))
  (println "  Total with tax: " (mf/format (ma/plus pre-tax tax))))
Output:
Total before tax:  $22,000,000,000,000,005.72
             Tax:  $1,683,000,000,000,000.44
  Total with tax:  $23,683,000,000,000,006.16
(require '[io.randomseed.bankster.money :as m])

(let [burgers    (m/mul #money[USD 5.50] 4000000000000000) 
      milkshakes (m/mul #money[USD 2.86] 2)
      pre-tax    (m/add burgers milkshakes)
      tax        (m/with-rounding UP (m/mul pre-tax 0.0765))]
  (println "Total before tax: " (m/format pre-tax))
  (println "             Tax: " (m/format tax))
  (println "  Total with tax: " (m/format (m/add pre-tax tax))))
Output:
Total before tax:  $22,000,000,000,000,005.72
             Tax:  $1,683,000,000,000,000.44
  Total with tax:  $23,683,000,000,000,006.16

COBOL

COBOL supports up to 31 digits of decimal precision, so won't need any fancy currency/BigInteger types!

During calculations the default ROUNDED MODE IS clause, when specified, is NEAREST-AWAY-FROM-ZERO. When ROUNDED is not specified, the default mode is TRUNCATION. The term "banker's rounding" implies NEAREST-EVEN.

Works with: GNU Cobol version 2.1
       >>SOURCE FREE
IDENTIFICATION DIVISION.
PROGRAM-ID. currency-example.

DATA DIVISION.
WORKING-STORAGE SECTION.
01  Burger-Price                        CONSTANT 5.50.
01  Milkshake-Price                     CONSTANT 2.86.
01  num-burgers                         PIC 9(18) VALUE 4000000000000000.
01  num-milkshakes                      PIC 9(18) VALUE 2.
01  tax                                 PIC 9(18)V99.
01  tax-edited                          PIC $(17)9.99.
01  Tax-Rate                            CONSTANT 7.65.
01  total                               PIC 9(18)V99.
01  total-edited                        PIC $(17)9.99.

PROCEDURE DIVISION.
    COMPUTE total rounded, total-edited rounded =
        num-burgers * Burger-Price + num-milkshakes * Milkshake-Price
    DISPLAY "Total before tax: " total-edited

    COMPUTE tax rounded, tax-edited rounded = total * (Tax-Rate / 100)
    DISPLAY "             Tax: " tax-edited

    ADD tax TO total GIVING total-edited rounded
    DISPLAY "  Total with tax: " total-edited
    .
END PROGRAM currency-example.
Output:
Total before tax: $22000000000000005.72
             Tax:  $1683000000000000.44
  Total with tax: $23683000000000006.16

Common Lisp

Let us just use the built-in and convenient rationals (which use two bignums for numerator and denominator).

(defun print-$ (rat &key (prefix "") (stream t))
  (multiple-value-bind (dollars cents) (truncate rat)
    (format stream "~A~D.~D~%" prefix dollars (round (* 100 cents)))))

(defun compute-check (order-alist tax-rate)
  (let* ((total-before-tax
           (loop :for (amount . price) in order-alist
                 :sum (* (rationalize price) amount)))
         (tax (* (rationalize tax-rate) total-before-tax)))
    (print-$ total-before-tax         :prefix "Total before tax: ")
    (print-$ tax                      :prefix "Tax:              ")
    (print-$ (+ total-before-tax tax) :prefix "Total with tax:   ")))

(compute-check '((4000000000000000 . 5.5) (2 . 2.86)) 0.0765)
Output:
Total before tax: 22000000000000005.72
Tax:              1683000000000000.44
Total with tax:   23683000000000006.16

A reader macro can be used as extra nice icing on the cake:

(defun read-$ (stream char)
  (declare (ignore char))
  (let* ((str (with-output-to-string (out)
                ;; TODO: read integer, if dot, read dot and another integer
                (loop :for next = (peek-char nil stream t nil t)
                      :while (or (digit-char-p next) (char= next #\.))
                      :do (write-char (read-char stream t nil t) out))))
         (dot-pos (position #\. str))
         (dollars (parse-integer str :end dot-pos))
         (cents (if dot-pos
                    (/ (parse-integer str :start (+ dot-pos 1))
                       (expt 10 (- (length str) (+ dot-pos 1))))
                    0)))
    (+ dollars cents)))
(set-macro-character #\$ #'read-$ t)

(defun print-$ (rat &key (prefix "") (stream t))
  (multiple-value-bind (dollars cents) (truncate rat)
    (format stream "~A~D.~D~%" prefix dollars (round (* 100 cents)))))

(defun compute-check (order-alist tax-rate)
  (let* ((total-before-tax
           (loop :for (amount . price) in order-alist
                 :sum (* price amount)))
         (tax (* (rationalize tax-rate) total-before-tax)))
    (print-$ total-before-tax         :prefix "Total before tax: ")
    (print-$ tax                      :prefix "Tax:              ")
    (print-$ (+ total-before-tax tax) :prefix "Total with tax:   ")))

(compute-check '((4000000000000000 . $5.5) (2 . $2.86)) 0.0765)

Delphi

Translation of: Go
program Currency;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  Velthuis.BigRationals,
  Velthuis.BigDecimals,
  Velthuis.BigIntegers;

var
  one: BigInteger;
  hundred: BigInteger;
  half: BigRational;

type
  TDc = record
    value: BigInteger;
    function ToString: string;
    function Extend(n: BigInteger): TDc;
    class operator Add(a, b: TDc): TDc;
  end;

  TTR = record
    value: BigRational;
    function SetString(const s: string; var TR: TTR): boolean;
    function Tax(dc: TDc): TDc;
  end;

{ TDc }

// Extend returns extended price of a unit price.
class operator TDc.Add(a, b: TDc): TDc;
begin
  Result.value := a.value + b.value;
end;

function TDc.Extend(n: BigInteger): TDc;
begin
  Result.value := n * value;
end;

function TDc.ToString: string;
var
  d: BigInteger;
begin
  d := value.Divide(value, 100);
  if value < 0 then
    value := -value;
  Result := Format('%s.%2s', [d.ToString, (value mod 100).ToString]);
end;

// ParseDC parses dollars and cents as a string into a DC.
function ParseDC(s: string; var Dc: TDc): Boolean;
var
  r: BigRational;
  d: BigDecimal;
begin
  Result := d.TryParse(s, d);
  if not Result then
  begin
    Dc.value := 0;
    exit(false);
  end;

  r := r.Create(d);
  r := r.Multiply(r, 100);
  if BigInteger.Compare(r.Denominator, 1) <> 0 then
  begin
    Dc.value := 0;
    exit(false);
  end;
  Result := true;
  Dc.value := r.Numerator;
end;

{ TTR }

function TTR.SetString(const s: string; var TR: TTR): boolean;
var
  d: BigDecimal;
begin

  Result := d.TryParse(s, d);
  if Result then
    TR.value := BigRational.Create(d);
end;

function TTR.Tax(dc: TDc): TDc;
var
  r: BigRational;
  i: BigInteger;
begin
  r := BigRational.Create(dc.value, 1);
  r := r.Multiply(r, self.value);
  r := r.add(r, half);
  i := i.Divide(r.Numerator, r.Denominator);
  Result.value := i;
end;

var
  hamburgerPrice, milkshakePrice, totalBeforeTax, tax, total: TDc;
  taxRate: TTR;

begin
  one := 1;
  hundred := 100;
  half := BigRational.Create(1, 2);

  if not ParseDC('5.50', hamburgerPrice) then
  begin
    Writeln('Invalid hamburger price');
    halt(1);
  end;

  if not ParseDC('2.86', milkshakePrice) then
  begin
    Writeln('Invalid milkshake price');
    halt(2);
  end;

  if not taxRate.SetString('0.0765', taxRate) then
  begin
    Writeln('Invalid tax rat');
    halt(3);
  end;

  totalBeforeTax := hamburgerPrice.Extend(4000000000000000) + milkshakePrice.Extend(2);
  tax := taxRate.Tax(totalBeforeTax);
  total := totalBeforeTax + tax;
  Writeln('Total before tax: ', totalBeforeTax.ToString: 22);
  Writeln('             Tax: ', tax.ToString: 22);
  Writeln('           Total: ', total.ToString: 22);
  readln;
end.

F#

open System

let hamburgers = 4000000000000000M
let hamburgerPrice = 5.50M
let milkshakes = 2M
let milkshakePrice = 2.86M
let taxRate = 0.0765M

let total = hamburgers * hamburgerPrice + milkshakes * milkshakePrice
let tax = total * taxRate
let totalWithTax = total + tax

printfn "Total before tax:\t$%M" <| Math.Round (total, 2)
printfn "             Tax:\t$%M" <| Math.Round (tax, 2)
printfn "           Total:\t$%M" <| Math.Round (totalWithTax, 2)
Output:
Total before tax:    $22000000000000005.72
             Tax:    $1683000000000000.44
  Total with tax:    $23683000000000006.16

Factor

Factor's ratio type can handle arbitrary-precision calculations with rational numbers. The money vocabulary implements convenience words for treating rationals as money. The DECIMAL: parsing word is used to convert the tax rate 0.0765 to a ratio 153/2000. The money. word is used to print the subtotal 22000000000000005+18/25, tax 1683000000000000+21879/50000, and total 23683000000000006+7879/50000 formatted as you would expect.

USING: combinators.smart io kernel math math.functions money ;

10 15 ^ 4 * 5+50/100 * ! hamburger subtotal
2 2+86/100 *           ! milkshake subtotal
+                      ! subtotal
dup DECIMAL: 0.0765 *  ! tax
[ + ] preserving       ! total

"Total before tax: " write [ money. ] 2dip
"Tax: " write [ money. ] dip
"Total with tax: " write money.
Output:
Total price before tax: $22,000,000,000,000,005.72
Tax: $1,683,000,000,000,000.44
Total with tax: $23,683,000,000,000,006.16


FreeBASIC

Pero el subtotal y el tax los muestra redondeados. Y no encuentro el porqué.

Dim As Longint hamburger_p = 550
Dim As Longint hamburger_q = 4000000000000000
Dim As Longint hamburger_v = hamburger_p * hamburger_q
Dim As Longint milkshake_p = 286
Dim As Longint milkshake_q = 2
Dim As Longint milkshake_v = milkshake_p * milkshake_q
Dim As Longint subtotal = hamburger_v + milkshake_v
Dim As Longint tax = subtotal * .765
Print Using "\   \       \     \          \       \                    \     \";"item";"price";"quantity";"value" 
Print Using "hamburger   ##.##    ################  #####################.##";hamburger_p/100;hamburger_q;hamburger_v/100
Print Using "milkshake   ##.##    ################  #####################.##";milkshake_p/100;milkshake_q;milkshake_v/100
?
Print Using "                             subtotal  #####################.##";subtotal/10
Print Using "                                  tax  #####################.##";tax/100
Print Using "                                total  #####################.##";subtotal/10+tax/100
Sleep


Frink

Frink tracks units of measure through all calculations, so you can specify quantities as "dollar", "cent", etc.

st = 4000000000000000 * 5.50 dollars + 2 * 2.86 dollars
tax = round[st * 7.65 percent, cent]
total = st + tax
println["Subtotal: " + format[st, "dollars", 2]]
println["Tax: " + format[tax, "dollars", 2]]
println["Total: " + format[total, "dollars", 2]]
Output:
Subtotal: 22000000000000005.72 dollars
Tax: 1683000000000000.44 dollars
Total: 23683000000000006.16 dollars

FutureBasic

local fn Lunch_Invoice( burger_price as CFStringRef, burger_amount as CFStringRef, shake_price as CFStringRef, shake_amount as CFStringRef, tax as CFStringRef )
'~'1
DecimalNumberRef  burgerPriceDecimal = fn DecimalNumberWithString( burger_price  )
DecimalNumberRef burgerAmountDecimal = fn DecimalNumberWithString( burger_amount )
DecimalNumberRef      burgersDecimal = fn DecimalNumberByMultiplyingBy( burgerPriceDecimal, burgerAmountDecimal )
DecimalNumberRef   shakePriceDecimal = fn DecimalNumberWithString( shake_price  )
DecimalNumberRef  shakeAmountDecimal = fn DecimalNumberWithString( shake_amount )
DecimalNumberRef       shakesDecimal = fn DecimalNumberByMultiplyingBy( shakePriceDecimal, shakeAmountDecimal )
DecimalNumberRef          taxDecimal = fn DecimalNumberWithString( tax )
DecimalNumberRef     subtotalDecimal = fn DecimalNumberByAdding( burgersDecimal, shakesDecimal )
DecimalNumberRef     taxTotalDecimal = fn DecimalNumberByMultiplyingBy( subtotalDecimal, taxDecimal )
DecimalNumberRef  adjTaxTotalDecimal = fn DecimalNumberByAdding( taxTotalDecimal, fn DecimalNumberWithString( @"0.01" ) )
DecimalNumberRef    billTotalDecimal = fn DecimalNumberByAdding( subtotalDecimal, adjTaxTotalDecimal )

CFStringRef   burgersString = fn DecimalNumberString( burgersDecimal   )
CFStringRef    shakesString = fn DecimalNumberString( shakesDecimal    )
CFStringRef  taxTotalString = fn DecimalNumberString( adjTaxTotalDecimal  )
CFStringRef billTotalString = fn DecimalNumberString( billTotalDecimal )

printf @"%@", fn StringByPaddingToLength( @"", 55, @"-", 0 )
printf @"Item         Price  Quantity          Cost"
printf @"Hamburgers %6s %18s %18s", fn StringUTF8String( burger_price ), fn StringUTF8String( burger_amount ), fn StringUTF8String( burgersString )
printf @"Milkshakes %6s %18s %18s", fn StringUTF8String( shake_price ),  fn StringUTF8String( shake_amount  ), fn StringUTF8String( shakesString  )
printf @"%@", fn StringByPaddingToLength( @"", 55, @"-", 0 )
printf @"%34s %@", fn StringUTF8String( @"Subtotal:"  ), fn DecimalNumberString( subtotalDecimal  )
printf @"%35s %@", fn StringUTF8String( @"     Tax: " ), fn StringSubstringToIndex(  taxTotalString, len(taxTotalString)  - 3 )
printf @"%34s %@", fn StringUTF8String( @"   Total:"  ), fn StringSubstringToIndex( billTotalString, len(billTotalString) - 3 )
end fn

NSLog( @"%@", fn WindowPrintViewString( 1 ) )

HandleEvents
Output:
-------------------------------------------------------
Item         Price  Quantity          Cost
Hamburgers   5.50   4000000000000000  22000000000000000
Milkshakes   2.86                  2               5.72
-------------------------------------------------------
                         Subtotal: 22000000000000005.72
                              Tax:  1683000000000000.44
                            Total: 23683000000000006.16

Go

package main

import (
    "fmt"
    "log"
    "math/big"
)

// DC for dollars and cents.  Value is an integer number of cents.
type DC int64

func (dc DC) String() string {
    d := dc / 100
    if dc < 0 {
        dc = -dc
    }
    return fmt.Sprintf("%d.%02d", d, dc%100)
}

// Extend returns extended price of a unit price.
func (dc DC) Extend(n int) DC {
    return dc * DC(n)
}

var one = big.NewInt(1)
var hundred = big.NewRat(100, 1)

// ParseDC parses dollars and cents as a string into a DC.
func ParseDC(s string) (DC, bool) {
    r, ok := new(big.Rat).SetString(s)
    if !ok {
        return 0, false
    }
    r.Mul(r, hundred)
    if r.Denom().Cmp(one) != 0 {
        return 0, false
    }
    return DC(r.Num().Int64()), true
}

// TR for tax rate.  Value is an an exact rational.
type TR struct {
    *big.Rat
}
func NewTR() TR {
    return TR{new(big.Rat)}
}

// SetString overrides Rat.SetString to return the TR type.
func (tr TR) SetString(s string) (TR, bool) {
    if _, ok := tr.Rat.SetString(s); !ok {
        return TR{}, false
    }
    return tr, true
}

var half = big.NewRat(1, 2)

// Tax computes a tax amount, rounding to the nearest cent.
func (tr TR) Tax(dc DC) DC {
    r := big.NewRat(int64(dc), 1)
    r.Add(r.Mul(r, tr.Rat), half)
    return DC(new(big.Int).Div(r.Num(), r.Denom()).Int64())
}

func main() {
    hamburgerPrice, ok := ParseDC("5.50")
    if !ok {
        log.Fatal("Invalid hamburger price")
    }
    milkshakePrice, ok := ParseDC("2.86")
    if !ok {
        log.Fatal("Invalid milkshake price")
    }
    taxRate, ok := NewTR().SetString("0.0765")
    if !ok {
        log.Fatal("Invalid tax rate")
    }

    totalBeforeTax := hamburgerPrice.Extend(4000000000000000) +
        milkshakePrice.Extend(2)
    tax := taxRate.Tax(totalBeforeTax)
    total := totalBeforeTax + tax

    fmt.Printf("Total before tax: %22s\n", totalBeforeTax)
    fmt.Printf("             Tax: %22s\n", tax)
    fmt.Printf("           Total: %22s\n", total)
}
Output:
Total before tax:   22000000000000005.72
             Tax:    1683000000000000.44
           Total:   23683000000000006.16

Haskell

import Data.Fixed
import Text.Printf

type Percent = Centi
type Dollars = Centi

tax :: Percent -> Dollars -> Dollars
tax rate = MkFixed . round . (rate *)

printAmount :: String -> Dollars -> IO ()
printAmount name = printf "%-10s %20s\n" name . showFixed False

main :: IO ()
main = do
  let subtotal = 4000000000000000 * 5.50 + 2 * 2.86
      tx       = tax 7.65 subtotal
      total    = subtotal + tx
  printAmount "Subtotal" subtotal
  printAmount "Tax"      tx
  printAmount "Total"    total
Output:
$ ./currency 
Subtotal   22000000000000005.72
Tax         1683000000000000.44
Total      23683000000000006.16

J

We use a naive implementation with arbitrary precision (rational) numbers:

require 'format/printf'
 
Items=:    ;:    'Hamburger     Milkshake'
Quantities=:   4000000000000000      2
Prices=:   x:        5.50          2.86
Tax_rate=: x:  0.0765

makeBill=: verb define
  'items prices quantities'=. y
  values=. prices * quantities
  subtotal=. +/ values
  tax=. Tax_rate * subtotal
  total=. subtotal + tax

  '%9s %8s %20s %22s' printf ;:'Item Price Quantity Value'
  '%9s %8.2f %20d %22.2f' printf"1 items ,. <"0 prices ,. quantities ,. values
  '%62s' printf <'-------------------------------'
  '%40s %21.2f' printf"1 (;:'Subtotal: Tax: Total:') ,. subtotal;tax;total
)

makeBill Items;Prices;Quantities
Output:
     Item    Price             Quantity                  Value
Hamburger     5.50     4000000000000000   22000000000000000.00
Milkshake     2.86                    2                   5.72
                               -------------------------------
                               Subtotal:  22000000000000005.72
                                    Tax:   1683000000000000.44
                                  Total:  23683000000000006.16

(Note that if you ever get a bill like this in real life, you should question the person who gave it to you. And, possibly consider legal action and/or patronizing a different establishment. This is because (a) you did not order that many hamburgers, and (b) they did not deliver that many hamburgers, and (c) that much hamburger probably does not exist, and ...)

Java

import java.math.*;
import java.util.*;

public class Currency {
    final static String taxrate = "7.65";

    enum MenuItem {

        Hamburger("5.50"), Milkshake("2.86");

        private MenuItem(String p) {
            price = new BigDecimal(p);
        }

        public final BigDecimal price;
    }

    public static void main(String[] args) {
        Locale.setDefault(Locale.ENGLISH);

        MathContext mc = MathContext.DECIMAL128;

        Map<MenuItem, BigDecimal> order = new HashMap<>();
        order.put(MenuItem.Hamburger, new BigDecimal("4000000000000000"));
        order.put(MenuItem.Milkshake, new BigDecimal("2"));

        BigDecimal subtotal = BigDecimal.ZERO;
        for (MenuItem it : order.keySet())
            subtotal = subtotal.add(it.price.multiply(order.get(it), mc));

        BigDecimal tax = new BigDecimal(taxrate, mc);
        tax = tax.divide(new BigDecimal("100"), mc);
        tax = subtotal.multiply(tax, mc);

        System.out.printf("Subtotal: %20.2f%n", subtotal);
        System.out.printf("     Tax: %20.2f%n", tax);
        System.out.printf("   Total: %20.2f%n", subtotal.add(tax));
    }
}
Subtotal: 22000000000000005.72
     Tax:  1683000000000000.44
   Total: 23683000000000006.16

JavaScript

const money = require('money-math')                                            

let hamburgers = 4000000000000000                                              
let hamburgerPrice = 5.50                                                      

let shakes = 2                                                                 
let shakePrice = 2.86                                                          

let tax = 7.65                                                                 

let hamburgerTotal = money.mul(hamburgers.toFixed(0), money.floatToAmount(hamburgerPrice))                                                                     
let shakeTotal = money.mul(shakes.toFixed(0), money.floatToAmount(shakePrice)) 

let subTotal = money.add(hamburgerTotal, shakeTotal)                           

let taxTotal = money.percent(subTotal, tax)                                    

let total = money.add(subTotal, taxTotal)                                      

console.log('Hamburger Total:', hamburgerTotal)                                
console.log('Shake Total:', shakeTotal)                                        
console.log('Sub Total:', subTotal)                                            
console.log('Tax:', taxTotal)                                                  
console.log('Total:', total)

jq

For simplicity, we will use the gojq implementation of jq as it supports unbounded-precision integer arithmetic.

If the standard implementation of jq were used, then it would probably be simplest to use the BigInt.jq library, which uses strings for precision.

The strategy here is to use cents except when presenting results as dollars and cents, and when calculating the taxes.

def lpad($len): tostring | ($len - length) as $l | (" " * $l)[:$l] + .;

# print as dollars and cents
def dollars:
  (. % 100) as $c
  | "$\((. - $c) /100).\($c)";

def dollars($width):
  dollars | lpad($width);

def innerproduct($y):
  . as $x
  | reduce range(0;$x|length) as $i (0; . + ($x[$i]*$y[$i]));

def plus($y):
  . as $x
  | reduce range(0;$x|length) as $i ([]; .[$i] = ($x[$i]+$y[$i]));

# Round up or down
def integer_division($y):
  (. % $y) as $remainder
  | (. - $remainder) / $y
  | if $remainder * 2 > $y then . + 1 else . end;

# For computing taxes
def precision: 10000;
def cents: integer_division(precision);


### The task:

def p: [550, 286];
def q: [4000000000000000, 2];

def taxrate: 765;  # relative to `precision`

(p | innerproduct(q))         as $before_tax     # cents
| ($before_tax * taxrate)     as $taxes          # relative to precision
| ((($before_tax * precision) + $taxes) | cents) as $after_tax # cents
| ($after_tax|tostring|length + 2) as $width
|
  " Total before tax: \($before_tax    | dollars($width))",
  " -            tax: \($taxes | cents | dollars($width))",
  " Total after  tax: \($after_tax     | dollars($width))"
Output:
 Total before tax: $22000000000000005.72
 -            tax:  $1683000000000000.44
 Total after  tax: $23683000000000006.16

Julia

Works with: Julia version 1.2
using Printf

p  = [big"5.50", big"2.86"]
q  = [4000000000000000, 2]
tr = big"0.0765"

beftax = p' * q
tax    = beftax * tr
afttax = beftax + tax

@printf " - tot. before tax: %20.2f \$\n" beftax
@printf " -             tax: %20.2f \$\n" tax
@printf " - tot. after  tax: %20.2f \$\n" afttax
Output:
 - tot. before tax: 22000000000000005.72 $
 -             tax:  1683000000000000.44 $
 - tot. after  tax: 23683000000000006.16 $

Kotlin

// version 1.1.2

import java.math.BigDecimal
import java.math.MathContext

fun main(args: Array<String>) {
    val mc = MathContext.DECIMAL128
    val nHamburger  = BigDecimal("4000000000000000", mc)
    val pHamburger  = BigDecimal("5.50")
    val nMilkshakes = BigDecimal("2", mc)
    val pMilkshakes = BigDecimal("2.86")
    val taxRate     = BigDecimal("0.0765")
    val price = nHamburger * pHamburger + nMilkshakes * pMilkshakes
    val tax = price * taxRate
    val fmt = "%20.2f"
    println("Total price before tax : ${fmt.format(price)}")
    println("Tax thereon @ 7.65%    : ${fmt.format(tax)}")
    println("Total price after tax  : ${fmt.format(price + tax)}")
}
Output:
Total price before tax : 22000000000000005.72
Tax thereon @ 7.65%    :  1683000000000000.44
Total price after tax  : 23683000000000006.16

Lua

Using the lbc library for arbitrary precision support.

C = setmetatable(require("bc"), {__call=function(t,...) return t.new(...) end})
C.digits(6) -- enough for .nn * .nnnn ==> .nnnnnn, follow with trunc(2) to trim trailing zeroes

subtot = (C"4000000000000000" * C"5.50" + C"2" * C"2.86"):trunc(2) -- cosmetic trunc
tax = (subtot * C"0.0765" + C"0.005"):trunc(2) -- rounding trunc
total = (subtot + tax):trunc(2) -- cosmetic trunc

print(("Before tax:  %20s"):format(subtot:tostring()))
print(("Tax       :  %20s"):format(tax:tostring()))
print(("With tax  :  %20s"):format(total:tostring()))
Output:
Before tax:  22000000000000005.72
Tax       :   1683000000000000.44
With tax  :  23683000000000006.16

M2000 Interpreter

This task written in M2000 Environment running on Wine 3.6, in a Linux Ubuntu Studio. M2000 environment is an ActiveX object, written with VB6, which use many types from COM Variant Type.

Module Currency_Task {
      Locale 1033
      Font "Courier New"
      Form 80,32
      \\Decimal type
      hamburgers=4000000000000000@
      \\ Currency type
      hamburger_price=5.5#
      milkshakes=2#
      milkshake_price=2.86#
      tax_rate=0.0765#
      \\ Using Columns with variable width in console
      PrHeadLine("Item","price","quantity", "value")
      PrLine("hamburger",hamburger_price,hamburgers,hamburgers*hamburger_price)
      PrLine("milkshake", milkshake_price,milkshakes,milkshakes*milkshake_price)
      PrResults( "subtotal", hamburgers*hamburger_price+milkshakes*milkshake_price)
      PrResults("tax", (hamburgers*hamburger_price+milkshakes*milkshake_price)*tax_rate)
      \\ 1 is double by default we can use 1# or 1@
      PrResults("total", (hamburgers*hamburger_price+milkshakes*milkshake_price)*(tax_rate+1))
      
      \\ Using variables for partial calculations. They get type from expression result
      h_p_q=hamburgers*hamburger_price
      m_p_q=milkshakes*milkshake_price
      
      \\ Using format$ to prepare final strings
      Print format$("{0:15}{1:-8}{2:-25}{3:-25}","Item", "price", "quantity", "value")
      Print format$("{0:15}{1:2:-8}{2:0:-25}{3:2:-25}","hamburger",hamburger_price,hamburgers, h_p_q)
      Print format$("{0:15}{1:2:-8}{2:0:-25}{3:2:-25}","milkshake", milkshake_price,milkshakes,m_p_q)
      Print format$("{0:-48}{1:2:-25}","subtotal", h_p_q+m_p_q)
      Print format$("{0:-48}{1:2:-25}","tax", (h_p_q+m_p_q)*tax_rate)
      Print format$("{0:-48}{1:2:-25}","total", (h_p_q+m_p_q)*(tax_rate+1))
      \\ Another time to feed Document to export to clipboard
      Document Doc$=format$("{0:15}{1:-8}{2:-25}{3:-25}","Item", "price", "quantity", "value")+{
      }+format$("{0:15}{1:2:-8}{2:0:-25}{3:2:-25}","hamburger",hamburger_price,hamburgers, h_p_q)+{
      }+format$("{0:15}{1:2:-8}{2:0:-25}{3:2:-25}","milkshake", milkshake_price,milkshakes,m_p_q)+{
      }+format$("{0:-48}{1:2:-25}","subtotal", h_p_q+m_p_q)+{
      }+format$("{0:-48}{1:2:-25}","tax", (h_p_q+m_p_q)*tax_rate)+{
      }+format$("{0:-48}{1:2:-25}","total", (h_p_q+m_p_q)*(tax_rate+1))+{
      }
      clipboard Doc$
      \\ one line user function definition
      \\ x get type from passed value
      Def ExpressionType$(x)=Type$(X)
      \\ Check Expression final type
      Print ExpressionType$(hamburgers)="Decimal"
      Print ExpressionType$(milkshakes)="Currency"
      Print ExpressionType$(h_p_q)="Decimal"
      Print ExpressionType$(m_p_q)="Currency"
      Print ExpressionType$((h_p_q+m_p_q)*tax_rate)="Decimal"
      Print ExpressionType$((h_p_q+m_p_q)*(tax_rate+1))="Decimal"
      
      Sub PrHeadLine(a$,b$,c$,d$)
            Print Part  $(1,15),a$,$(3,8),b$, $(3,25),c$, $(3,25),d$
            Print
      End Sub
      Sub PrLine(a$,b,c,d)
            Print Part  $(1,15),a$,$("0.00"),$(3,8),b, $("0"),$(3,25),c,$("0.00"), $(3,25),d
            Print
      End Sub
      Sub PrResults(a$,b)
            Print Part  $(3,48),a$,$("0.00"),$(3,25),b
            Print
      End Sub
}
Currency_Task

Optional with $ and thousands separator (a smaller version from above)

Module Currency_Task {
      Locale 1033
      Font "Courier New"
      Form 80,32
      hamburgers=4000000000000000@
      hamburger_price=5.5#
      milkshakes=2#
      milkshake_price=2.86#
      tax_rate=0.0765#
      PrHeadLine("Item","price","quantity", "value")
      PrLine("hamburger",hamburger_price,hamburgers,hamburgers*hamburger_price)
      PrLine("milkshake", milkshake_price,milkshakes,milkshakes*milkshake_price)
      PrResults( "subtotal", hamburgers*hamburger_price+milkshakes*milkshake_price)
      PrResults("tax", (hamburgers*hamburger_price+milkshakes*milkshake_price)*tax_rate)
      PrResults("total", (hamburgers*hamburger_price+milkshakes*milkshake_price)*(tax_rate+1))
 
      h_p_q=hamburgers*hamburger_price
      m_p_q=milkshakes*milkshake_price
      Document Doc$=format$("{0:15}{1:-8}{2:-25}{3:-30}","Item", "price", "quantity", "value")+{
      }+format$("{0:15}{1:-8}{2:-25}{3:-30}","hamburger",str$(hamburger_price,"$#,##0.00"),str$(hamburgers, "#,##0"), Str$(h_p_q,"$#,##0.00"))+{
      }+format$("{0:15}{1:-8}{2:-25}{3:-30}","milkshake", str$(milkshake_price,"$#,##0.00"),Str$(milkshakes, "#,##0"), Str$(m_p_q,"$#,##0.00"))+{
      }+format$("{0:-48}{1:-30}","subtotal", Str$(h_p_q+m_p_q,"$#,##0.00"))+{
      }+format$("{0:-48}{1:-30}","tax", Str$((h_p_q+m_p_q)*tax_rate,"$#,##0.00"))+{
      }+format$("{0:-48}{1:-30}","total", Str$((h_p_q+m_p_q)*(tax_rate+1),"$#,##0.00"))+{
      }
      clipboard Doc$
 
 
      Sub PrHeadLine(a$,b$,c$,d$)
            Print Part  $(1,15),a$,$(3,8),b$, $(3,25),c$, $(3,30),d$
            Print
      End Sub
      Sub PrLine(a$,b,c,d)
            Print Part  $(1,15),a$,$("$#,###.00"),$(3,8),b, $("#,##0"),$(3,25),c,$("$#,###.00"), $(3,30),d
            Print
      End Sub
      Sub PrResults(a$,b)
            Print Part  $(3,48),a$,$("$#,###.00"),$(3,30),b
            Print
      End Sub
}
Currency_Task
Output:
Item              price                 quantity                    value
hamburger          5.50         4000000000000000     22000000000000000.00
milkshake          2.86                        2                     5.72
                                        subtotal     22000000000000005.72
                                             tax      1683000000000000.44
                                           total     23683000000000006.16
From Optional
Item              price                 quantity                         value
hamburger         $5.50    4,000,000,000,000,000    $22,000,000,000,000,000.00
milkshake         $2.86                        2                         $5.72
                                        subtotal    $22,000,000,000,000,005.72
                                             tax     $1,683,000,000,000,000.44
                                           total    $23,683,000,000,000,006.16

Maple

Digits := 50;
tax := .0765;

burgersquantity := 4000000000000000;
burgersprice := 5.50;
burgerscost := burgersquantity * burgersprice;

milkshakesquantity := 2;
milkshakesprice := 2.86;
milkshakescost := milkshakesquantity * milkshakesprice;

total := burgerscost + milkshakescost;
printf("%.2f\n",total);

totaltax := total * tax;
printf("%.2f\n",totaltax);

totalprice := totaltax + total;
printf("%.2f\n",totalprice);
Output:
22000000000000005.72
1683000000000000.44
23683000000000006.16

Mathematica / Wolfram Language

total = 4000000000000000 Rationalize[5.50] + 2 Rationalize[2.86];
AccountingForm[N[total, 20], {\[Infinity], 2}]
tax = total Rationalize[0.0765];
AccountingForm[N[tax, 20], {\[Infinity], 2}]
AccountingForm[N[total + tax, 20], {\[Infinity], 2}]
Output:
22000000000000005.72
1683000000000000.44
23683000000000006.16

Nim

Library: bignum

Nim doesn’t provide a standard module to deal with decimal values. There exist some third party modules but we have chosen to use the “bignum” library which provides big integers and big rationals.

import strutils
import bignum

type Currency = Int

#---------------------------------------------------------------------------------------------------

func currency(units, subunits: int): Currency =
  ## Build a currency from units and subunits.
  ## Units may be negative. Subunits must be in range 0..99.
  if subunits notin 0..99:
    raise newException(ValueError, "wrong value for subunits")
  result = if units >= 0: newInt(units * 100 + subunits)
           else: newInt(subunits * 100 - subunits)

#---------------------------------------------------------------------------------------------------

func currency(value: string): Currency =
  ## Build a currency from a string.
  ## Negative values are allowed. At most two digits are allowed for subunits.

  const StartingChars = Digits + {'-'}
  if value.len == 0 or value[0] notin StartingChars:
    raise newException(ValueError, "wrong currency string")

  # process sign and units.
  var units = newInt(0)
  var subunits = 0
  let sign = if value[0] == '-': -1 else: 1
  var idx = if sign == 1: 0 else: 1
  while idx < value.len:
    if value[idx] notin Digits: break
    units = 10 * units + ord(value[idx]) - ord('0')
    inc idx

  # Process separator.
  if idx <= value.high:
    if value[idx] != '.':
      raise newException(ValueError, "expected a separator")
    inc idx

  # Process subunits.
  for _ in 0..1:
    let c = if idx >= value.len: '0' else: value[idx]
    if c notin Digits:
      raise newException(ValueError, "wrong value for subunits")
    subunits = 10 * subunits + ord(c) - ord('0')
    inc idx

  if idx <= value.high:
    raise newException(ValueError, "extra characters after subunits digits")

  result = sign * (units * 100 + subunits)

#---------------------------------------------------------------------------------------------------

func `//`(a, b: int): Rat =
  ## Create a rational value.
  newRat(a, b)

#---------------------------------------------------------------------------------------------------

func percentage(a: Currency; p: Rat): Currency =
  ## Compute a percentage on currency value "a".
  ## Returned value is rounded to nearest integer.

  (a * p.num * 10 div p.denom + 5) div 10

#---------------------------------------------------------------------------------------------------

func `$`(a: Currency): string =
  ## Build a string representation of a currency value.

  result = bignum.`$`(a div 100) & '.' & ($(a mod 100).toInt).align(2, '0')

#———————————————————————————————————————————————————————————————————————————————————————————————————

let hamburgers = currency(5, 50) * int 4_000_000_000_000_000
let milkshakes = currency("2.86") * 2
let rate = 765 // 10_000
let beforeTax = hamburgers + milkshakes
let tax = beforeTax.percentage(rate)
let total = beforeTax + tax

# Find the maximum length of numerical value representations.
let beforeTaxStr = $beforeTax
let taxStr = $tax
let totalStr = $total
let length = max([beforeTaxStr.len, taxStr.len, totalStr.len])

# Display the results.
echo "Total price before tax: ", beforeTaxStr.align(length)
echo "Tax:                    ", taxStr.align(length)
echo "Total with tax:         ", totalStr.align(length)
Output:
Total price before tax: 22000000000000005.72
Tax:                     1683000000000000.44
Total with tax:         23683000000000006.16

OCaml

Using the decimal library.

let () =
  let open Decimal in (* bring all functions and operators into scope locally *)
  let s = of_string in
  let i = of_int in

  let hamburgers = s "4e15" * s "5.50" in
  let milkshakes = i 2 * s "2.86" in
  let tax_rate = s "7.65e-2" in
  let subtotal = hamburgers + milkshakes in
  let tax = subtotal * tax_rate in
  let total = subtotal + tax in

  Printf.printf
    "Subtotal: %20s
     Tax: %20s
   Total: %20s\n"
    (to_string (round ~n:2 subtotal))
    (to_string (round ~n:2 tax))
    (to_string (round ~n:2 total))
Output:
Subtotal: 22000000000000005.72
     Tax:  1683000000000000.44
   Total: 23683000000000006.16

Perl

use Math::Decimal qw(dec_canonise dec_add dec_mul dec_rndiv_and_rem);

@check = (
    [<Hamburger 5.50 4000000000000000>],
    [<Milkshake 2.86                2>]
);

my $fmt = "%-10s %8s %18s %22s\n";
printf $fmt, <Item Price Quantity Extension>;

my $subtotal = dec_canonise(0);
for $line (@check) {
    ($item,$price,$quant) = @$line;
    $dp = dec_canonise($price); $dq = dec_canonise($quant);
    my $extension = dec_mul($dp,$dq);
    $subtotal = dec_add($subtotal, $extension);
    printf $fmt, $item, $price, $quant, rnd($extension);
}

my $rate  = dec_canonise(0.0765);
my $tax   = dec_mul($subtotal,$rate);
my $total = dec_add($subtotal,$tax);

printf $fmt, '', '', '',          '-----------------';
printf $fmt, '', '', 'Subtotal ', rnd($subtotal);
printf $fmt, '', '', 'Tax ',      rnd($tax);
printf $fmt, '', '', 'Total ',    rnd($total);

sub rnd {
    ($q, $r) = dec_rndiv_and_rem("FLR", @_[0], 1);
    $q . substr((sprintf "%.2f", $r), 1, 3);
}
Output:
Item          Price           Quantity              Extension
Hamburger      5.50   4000000000000000   22000000000000000.00
Milkshake      2.86                  2                   5.72
                                            -----------------
                             Subtotal    22000000000000005.72
                                  Tax     1683000000000000.44
                                Total    23683000000000006.16

Phix

Library: Phix/mpfr
requires("1.0.4") -- (mpfr_get_fixed() busted in 1.0.2|3)
include mpfr.e
mpfr_set_default_precision(-20) -- ensure accuracy to at least 20 d.p.
 
mpfr total_price = mpfr_init("4000000000000000"),
     tmp = mpfr_init("5.5"),
     tax = mpfr_init("0.0765"),
     total = mpfr_init()
mpfr_mul(total_price,total_price,tmp)
mpfr_set_str(tmp,"2.86")
mpfr_mul_si(tmp,tmp,2)
mpfr_add(total_price,total_price,tmp)
mpfr_mul(tax,total_price,tax)
mpfr_add(total,total_price,tax)
printf(1,"Total before tax:%26s\n",{mpfr_get_fixed(total_price,2,comma_fill:=true)})
printf(1,"             Tax:%26s\n",{mpfr_get_fixed(tax,2,comma_fill:=true)})
printf(1,"           Total:%26s\n",{mpfr_get_fixed(total,2,comma_fill:=true)})
Output:
Total before tax: 22,000,000,000,000,005.72
             Tax:  1,683,000,000,000,000.44
           Total: 23,683,000,000,000,006.16

64 bit

As it happens, 64-bit Phix uses 80-bit floats for atoms, which actually have just enough accuracy for this task:

requires(64)
atom total_price = 4000000000000000*5.5+2.86*2,
     tax = total_price*0.0765,
     total = total_price+tax
printf(1,"Total before tax:%,26.2f\n",{total_price})
printf(1,"             Tax:%,26.2f\n",{tax})
printf(1,"           Total:%,26.2f\n",{total})

with the same output, however (after deleting the first line) under 32-bit (and under p2js) they would incorrectly end in 4.00/0.25/4.00.

PicoLisp

(scl 2)
(let
   (Before
      (+
         (* 4000000000000000 5.50)
         (* 2 2.86) )
      Tax (*/ Before 7.65 100.00)
      Total (+ Before Tax)
      Fmt (17 27) )
   (tab Fmt "Total before tax:" (format Before *Scl "." ","))
   (tab Fmt "Tax:" (format Tax *Scl "." ","))
   (tab Fmt "Total:" (format Total *Scl "." ",")) )
Output:
Total before tax:  22,000,000,000,000,005.72
             Tax:   1,683,000,000,000,000.44
           Total:  23,683,000,000,000,006.16

Python

This uses Pythons decimal module, (and some copying of names from the Raku example).

from decimal import Decimal as D
from collections import namedtuple

Item = namedtuple('Item', 'price, quant')

items = dict( hamburger=Item(D('5.50'), D('4000000000000000')),
              milkshake=Item(D('2.86'), D('2')) )
tax_rate = D('0.0765')

fmt = "%-10s %8s %18s %22s"
print(fmt % tuple('Item Price Quantity Extension'.upper().split()))

total_before_tax = 0
for item, (price, quant) in sorted(items.items()):
    ext = price * quant
    print(fmt % (item, price, quant, ext))
    total_before_tax += ext
print(fmt % ('', '', '', '--------------------'))
print(fmt % ('', '', 'subtotal', total_before_tax))

tax = (tax_rate * total_before_tax).quantize(D('0.00'))
print(fmt % ('', '', 'Tax', tax))

total = total_before_tax + tax
print(fmt % ('', '', '', '--------------------'))
print(fmt % ('', '', 'Total', total))
Output:
ITEM          PRICE           QUANTITY              EXTENSION
hamburger      5.50   4000000000000000   22000000000000000.00
milkshake      2.86                  2                   5.72
                                         --------------------
                              subtotal   22000000000000005.72
                                   Tax    1683000000000000.44
                                         --------------------
                                 Total   23683000000000006.16

Quackery

  [ $ "bigrat.qky" loadfile ] now!

  [ 100 * n->v ]               is dollars      (    n -->  n/d )

  [ n->v v+ ]                  is cents        ( n/d n --> n/d )

  [ rot n->v v* ]              is cost         ( n n/d --> n/d )

  [ $->v drop v* 100 n->v v/ ] is tax          ( n/d $ --> n/d )

  [ 100 n->v v/
    2 point$
    $ "  $" swap join
    ' [ 2 split nip ] ]do[
    dup -3 peek
    char . = if done
    dup -2 peek
    char . = iff
      [ char 0 join ]
      done
    $ ".00" join ]             is currency$    (   n/d --> $   )

    [ currency$ echo$ ]        is echocurrency (   n/d -->     )


  4000000000000000 5 dollars 50 cents cost
                 2 2 dollars 86 cents cost v+

  say "Total price before tax: "  2dup echocurrency cr
 
  2dup $ "7.65" tax
 
  say "Tax:                     " 2dup echocurrency cr
 
  v+
  say "Total price with tax:   "       echocurrency cr
Output:
Total price before tax: $22000000000000005.72
Tax:                     $1683000000000000.44
Total price with tax:   $23683000000000006.16

Racket

Racket can handle fractions. To read the decimals numbers as fractions instead of floating point numbers, they must start with #e. For example #e.3 -> 3/10 and .3 ->3E-1. The main problem is rounding correctly and this part is handled in cents-*, that implements a multiplication that rounds to the cents. The rest of the program is only formatting.

#lang racket
(define (cents-* x y)
  (/ (round (* 100 x y)) 100))

(struct item (name count price))

(define (string-pad-right len . strs)
  (define all (apply string-append strs))
  (string-append  all (make-string (- len (string-length all)) #\space)))

(define (string-pad-left len . strs)
  (define all (apply string-append strs))
  (string-append  (make-string (- len (string-length all)) #\space) all))

(define (show-formated name count price total)
  (printf "~a ~a ~a -> ~a\n"
          (string-pad-right 10 name)
          (string-pad-left 18 count)
          (string-pad-left 8 price)
          (string-pad-left 23 total)
          ))

(define (show-item it)
  (show-formated (item-name it)
                 (~r (item-count it))
                 (string-append "$" (~r (item-price it) #:precision '(= 2)))
                 (string-append "$" (~r (cents-* (item-count it) (item-price it)) #:precision '(= 2)))
          ))

(define (show-total all tax-rate)
  (define net (for/sum ([it (in-list all)])
                       (cents-* (item-count it) (item-price it))))
  (define tax (cents-* net tax-rate))
  (show-formated "" "" "net" (string-append "$" (~r net #:precision '(= 2))))
  (show-formated "" "" "tax" (string-append "$" (~r tax #:precision '(= 2))))
  (show-formated "" "" "total" (string-append "$" (~r (+ net tax) #:precision '(= 2))))
  )
         
(define hamburger (item "hamburger" 4000000000000000 #e5.50))
(define milkshake (item "milkshake" 2 #e2.86))
(define all (list hamburger milkshake))

(for-each show-item all)
(newline)
(show-total all (/ #e7.65 100))
Output:
hamburger    4000000000000000    $5.50 ->   $22000000000000000.00
milkshake                   2    $2.86 ->                   $5.72

                                   net ->   $22000000000000005.72
                                   tax ->    $1683000000000000.44
                                 total ->   $23683000000000006.16

Raku

(formerly Perl 6)

Works with: Rakudo version 2016.01

No need for a special type in Raku, since the Rat type is used for normal fractions. (In order to achieve imprecision, you have to explicitly use scientific notation, or use the Num type, or calculate a result that requires a denominator in excess of 2 ** 64. (There's no limit on the numerator.))

my @check = q:to/END/.lines.map: { [.split(/\s+/)] };
    Hamburger   5.50    4000000000000000
    Milkshake   2.86    2
    END

my $tax-rate = 0.0765;

my $fmt = "%-10s %8s %18s %22s\n";

printf $fmt, <Item Price Quantity Extension>;

my $subtotal = [+] @check.map: -> [$item,$price,$quant] {
    my $extension = $price * $quant;
    printf $fmt, $item, $price, $quant, fix2($extension);
    $extension;
}

printf $fmt, '', '', '', '-----------------';
printf $fmt, '', '', 'Subtotal ', $subtotal;

my $tax = ($subtotal * $tax-rate).round(0.01);
printf $fmt, '', '', 'Tax ', $tax;

my $total = $subtotal + $tax;
printf $fmt, '', '', 'Total ', $total;

# make up for lack of a Rat fixed-point printf format
sub fix2($x) { ($x + 0.001).subst(/ <?after \.\d\d> .* $ /, '') }
Output:
Item          Price           Quantity              Extension
Hamburger      5.50   4000000000000000   22000000000000000.00
Milkshake      2.86                  2                   5.72
                                            -----------------
                             Subtotal    22000000000000005.72
                                  Tax     1683000000000000.44
                                Total    23683000000000006.16

REXX

REXX uses characters to represent everything, including all forms of numbers.   So what is expressed as a literal (characters) is what REXX uses.   Essentially, it can be thought of as decimal.

Programming note:   the tax rate can be expressed with or without a percent   (%)  suffix.

without commas

/*REXX program shows a method of computing the total price and tax  for purchased items.*/
numeric digits 200                                        /*support for gihugic numbers.*/
taxRate= 7.65                                             /*number is:   nn   or   nn%  */
if right(taxRate, 1)\=='%'  then taxRate= taxRate / 100   /*handle plain tax rate number*/
taxRate= strip(taxRate, , '%')                            /*strip the  %   (if present).*/
item.  =;                          items= 0               /*zero out the register.      */
item.1 = '4000000000000000  $5.50  hamburger'             /*the  first  item purchased. */
item.2 = '               2  $2.86  milkshake'             /* "  second    "      "      */
say  center('quantity', 22)          center("item", 22)           center('price', 22)
hdr= center('',         27, "─")     center('',     20, "─")      center('',      27, "─")
say hdr;                             total= 0
         do j=1  while item.j\==''                        /*calculate the total and tax.*/
         parse var item.j   quantity price thing          /*ring up an item on register.*/
         items    = items + quantity                      /*tally the number of items.  */
         price    = translate(price, , '$')               /*maybe scrub out the $ symbol*/
         subtotal = quantity * price                      /*calculate the     sub-total.*/
         total    = total + subtotal                      /*    "      "  running total.*/
         say right(quantity, 27)     left(thing, 20)        show$(subtotal)
         end   /*j*/
say                                              /*display a blank line for separator.  */
say translate(hdr, '═', "─")                     /*display the separator part of the hdr*/
tax= format(total * taxRate, , 2)                /*round the total tax for all the items*/
say right(items  "(items)", 35)     right('total=', 12)            show$(total)
say right('tax at'  (taxRate * 100 / 1)"%=", 48)                   show$(tax)
say
say right('grand total=', 48)                                   show$(total+tax)
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
show$:  return right( '$'arg(1), 27)             /*right─justify and format a number.   */
output   (attempting to mimic a check-out register to some degree):
       quantity                 item                  price
────────────────────── ────────────────────── ──────────────────────
      4000000000000000 hamburger               $22000000000000000.00
                     2 milkshake                               $5.72

══════════════════════ ══════════════════════ ══════════════════════
      4000000000000002 (items)         total=  $22000000000000005.72
                                tax at 7.65%=   $1683000000000000.44

                                 grand total=  $23683000000000006.16

with commas

/*REXX program shows a method of computing the total price and tax  for purchased items.*/
numeric digits 200                                        /*support for gihugic numbers.*/
taxRate= 7.65                                             /*number is:   nn   or   nn%  */
if right(taxRate, 1)\=='%'  then taxRate= taxRate / 100   /*handle plain tax rate number*/
taxRate=strip(taxRate, , '%')                             /*strip the  %   (if present).*/
item.  =;                          items= 0               /*zero out the register.      */
item.1 = '4000000000000000  $5.50  hamburger'             /*the  first  item purchased. */
item.2 = '               2  $2.86  milkshake'             /* "  second    "      "      */
say  center('quantity', 22)          center("item", 22)           center('price', 22)
hdr= center('',         27 ,"─")     center('',     20, "─")      center('',      27, "─")
say hdr;                             total=0
         do j=1  while item.j\==''                        /*calculate the total and tax.*/
         parse var item.j   quantity price thing          /*ring up an item on register.*/
         items    = items + quantity                      /*tally the number of items.  */
         price    = translate(price, , '$')               /*maybe scrub out the $ symbol*/
         subtotal = quantity * price                      /*calculate the     sub-total.*/
         total    = total + subtotal                      /*    "      "  running total.*/
         say right(quantity, 27)        left(thing, 20)               show$(subtotal)
         end   /*j*/
say                                              /*display a blank line for separator.  */
say translate(hdr, '═', "─")                     /*display the separator part of the hdr*/
tax= format(total * taxRate, , 2)                /*round the total tax for all the items*/
say right( commas(items  "(items)"), 35)            right('total=', 12)       show$(total)
say right('tax at'  (taxRate * 100 / 1)"%=", 48)                              show$(tax)
say
say right('grand total=', 48)           show$(total + tax)
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
commas: procedure;  parse arg _;   n= _'.9';      #= 123456789;       b= verify(n, #, "M")
        e= verify(n, #'0', , verify(n, #"0.", 'M') )  - 4       /* [↓]  commatize number*/
           do j=e  to b  by -3;    _= insert(',', _, j);    end  /*j*/;           return _
/*──────────────────────────────────────────────────────────────────────────────────────*/
show$:  return right( commas( '$'arg(1) ), 27)   /*right─justify and format a number.   */
output   with commas in the larger numbers:
         quantity                   item                    price
─────────────────────────── ──────────────────── ───────────────────────────
      4,000,000,000,000,000 hamburger             $22,000,000,000,000,000.00
                          2 milkshake                                  $5.72

═══════════════════════════ ════════════════════ ═══════════════════════════
      4,000,000,000,000,002 (items)       total=  $22,000,000,000,000,005.72
                                   tax at 7.65%=   $1,683,000,000,000,000.44

                                    grand total=  $23,683,000,000,000,006.16

Ring

# Project  : Currency

nhamburger  = "4000000000"
phamburger  = "5.50"
nmilkshakes = "2"
pmilkshakes = "2.86"
taxrate = "0.0765"
price = nhamburger * phamburger + nmilkshakes * pmilkshakes
tax = price * taxrate
see "total price before tax : " + price + nl
see "tax thereon @ 7.65 : " + tax + nl
see "total price after tax  : " + (price + tax) + nl

Output:

total price before tax : 22000000005.72
tax thereon @ 7.65   : 1683000000.44
total price after tax   : 23683000006.16

Ruby

require 'bigdecimal/util'

before_tax = 4000000000000000 * 5.50.to_d + 2 * 2.86.to_d
tax        = (before_tax * 0.0765.to_d).round(2)
total      = before_tax + tax

puts "Before tax: $#{before_tax.to_s('F')}
Tax: $#{tax.to_s('F')}
Total: $#{total.to_s('F')}"
Output:
Before tax: $22000000000000005.72
Tax: $1683000000000000.44
Total: $23683000000000006.16

Rust

extern crate num_bigint; // 0.3.0
extern crate num_rational; // 0.3.0

use num_bigint::BigInt;
use num_rational::BigRational;


use std::ops::{Add, Mul};
use std::fmt;

fn main() {
    let hamburger = Currency::new(5.50);
    let milkshake = Currency::new(2.86);
    let pre_tax = hamburger * 4_000_000_000_000_000 + milkshake * 2;
    println!("Price before tax: {}", pre_tax);
    let tax = pre_tax.calculate_tax();
    println!("Tax: {}", tax);
    let post_tax = pre_tax + tax;
    println!("Price after tax: {}", post_tax);
}

#[derive(Debug)]
struct Currency {
    amount: BigRational,
}

impl Add for Currency {

    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            amount: self.amount + other.amount,
        }
    }    
}

impl Mul<u64> for Currency {
    
    type Output = Self;
    
    fn mul(self, other: u64) -> Self {
        Self {
            amount: self.amount * BigInt::from(other),
        }
    }
}

impl fmt::Display for Currency {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let cents = (&self.amount * BigInt::from(100)).to_integer();
        write!(f, "${}.{:0>2}", &cents / 100, &cents % 100)
    }
}

impl Currency {
    
    fn new(num: f64) -> Self {
        Self {
            amount: BigRational::new(((num * 100.0).round() as i64).into(), 100.into())
        }
    }

    fn calculate_tax(&self) -> Self {
        let tax_val = BigRational::new(765.into(), 100.into());// 7,65 -> 0.0765 after the next line
        let amount = (&self.amount * tax_val).ceil() / BigInt::from(100); 
        Self {
            amount
        }
    }
}
Output:
Price before tax: $22000000000000005.72
Tax: $1683000000000000.44
Price after tax: $23683000000000006.16

Scala

Library: Scala
Locale is manipulated to demonstrate the behavior with other currencies.
import java.text.NumberFormat
import java.util.Locale

object SizeMeUp extends App {

  val menu: Map[String, (String, Double)] = Map("burg" ->("Hamburger XL", 5.50), "milk" ->("Milkshake", 2.86))
  val order = List((4000000000000000L, "burg"), (2L, "milk"))

  Locale.setDefault(new Locale("ru", "RU"))

  val (currSymbol, tax) = (NumberFormat.getInstance().getCurrency.getSymbol, 0.0765)

  def placeOrder(order: List[(Long, String)]) = {
    val totals = for ((qty, article) <- order) yield {
      val (desc, itemPrize) = menu(article)
      val (items, post) = (qty, qty * BigDecimal(itemPrize))
      println(f"$qty%16d\t$desc%-16s\t$currSymbol%4s$itemPrize%6.2f\t$post%,25.2f")
      (items, post)
    }
    totals.foldLeft((0L, BigDecimal(0))) { (acc, n) => (acc._1 + n._1, acc._2 + n._2)}
  }

  val (items, beforeTax) = placeOrder(order)

  println(f"$items%16d\t${"ordered items"}%-16s${'\t' + "  Subtotal" + '\t'}$beforeTax%,25.2f")

  val taxation = beforeTax * tax
  println(f"${" " * 16 + '\t' + " " * 16 + '\t' + f"${tax * 100}%5.2f%% tax" + '\t'}$taxation%,25.2f")
  println(f"${" " * 16 + '\t' + " " * 16 + '\t' + "Amount due" + '\t'}${beforeTax + taxation}%,25.2f")
}
Output:
4000000000000000	Hamburger XL    	руб.  5,50	22 000 000 000 000 000,00
               2	Milkshake       	руб.  2,86	                     5,72
4000000000000002	ordered items   	  Subtotal	22 000 000 000 000 005,72
                	                	 7,65% tax	 1 683 000 000 000 000,44
                	                	Amount due	23 683 000 000 000 006,16

Process finished with exit code 0

Sidef

Translation of: Raku
struct Item {
    name, price, quant
}

var check = %q{
    Hamburger   5.50    4000000000000000
    Milkshake   2.86    2
}.lines.grep(/\S/).map { Item(.words...) }

var tax_rate = 0.0765
var fmt = "%-10s %8s %18s %22s\n"

printf(fmt, %w(Item Price Quantity Extension)...)

var subtotal = check.map { |item|
    var extension = Num(item.price)*Num(item.quant)
    printf(fmt, item.name, item.price, item.quant, extension.round(-2))
    extension
}.sum(0)

printf(fmt, '', '', '', '-----------------')
printf(fmt, '', '', 'Subtotal ', subtotal)

var tax = (subtotal * tax_rate -> round(-2))
printf(fmt, '', '', 'Tax ', tax)

var total = subtotal+tax
printf(fmt, '', '', 'Total ', total)
Output:
Item          Price           Quantity              Extension
Hamburger      5.50   4000000000000000      22000000000000000
Milkshake      2.86                  2                   5.72
                                            -----------------
                             Subtotal    22000000000000005.72
                                  Tax     1683000000000000.44
                                Total    23683000000000006.16

Smalltalk

Using ScaledDecimal numbers ('sX'-suffix).

Works with: Smalltalk/X
check := #(
    " amount         name           price  "
    (4000000000000000 'hamburger'    5.50s2 )
    (2                'milkshakes'   2.86s2 )
).
tax := 7.65s2.
fmt := '%-10s %10P %22P %26P\n'.

totalSum := 0.
totalTax := 0.

Transcript clear.
Transcript printf:fmt withAll:#('Item' 'Price' 'Qty' 'Extension').
Transcript printCR:('-' ,* 72).

check do:[:entry|
    |amount name price itemTotal itemTax|

    amount := entry[1].
    name   := entry[2].
    price  := entry[3].
    itemTotal := (price*amount).
    itemTax   := ((price*amount)*tax/100) roundedToScale.

    totalSum := totalSum + itemTotal.
    totalTax := totalTax + itemTax.
    Transcript printf:fmt
               withAll:{name . price . amount . itemTotal}.
].
Transcript printCR:('-' ,* 72).
Transcript printf:fmt withAll:{'' . '' . 'Subtotal' . totalSum}.
Transcript printf:fmt withAll:{'' . '' . 'Tax' . totalTax}.
Transcript printf:fmt withAll:{'' . '' . 'Total' . (totalSum+totalTax)}.

Transcript cr; printCR:('Enjoy your Meal & Thank You for Dining at Milliways')
Output:
Item            Price                    Qty                  Extension
------------------------------------------------------------------------
hamburger        5.50       4000000000000000       22000000000000000.00
milkshakes       2.86                      2                       5.72
------------------------------------------------------------------------
                                    Subtotal       22000000000000005.72
                                         Tax        1683000000000000.44
                                       Total       23683000000000006.16

Enjoy your Meal & Thank You for Dining at Milliways

Swift

import Foundation

extension Decimal {
  func rounded(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) -> Decimal {
    var result = Decimal()
    var localCopy = self
    NSDecimalRound(&result, &localCopy, scale, roundingMode)
    return result
  }
}

let costHamburgers = Decimal(4000000000000000) * Decimal(5.50)
let costMilkshakes = Decimal(2) * Decimal(2.86)
let totalBeforeTax = costMilkshakes + costHamburgers
let taxesToBeCollected = (Decimal(string: "0.0765")! * totalBeforeTax).rounded(2, .bankers)

print("Price before tax: $\(totalBeforeTax)")
print("Total tax to be collected: $\(taxesToBeCollected)")
print("Total with taxes: $\(totalBeforeTax + taxesToBeCollected)")
Output:
Price before tax: $22000000000000005.72
Total tax to be collected: $1683000000000000.44
Total with taxes: $23683000000000006.16

Tcl

Library: Tcllib (Package: math::decimal)
package require math::decimal
namespace import math::decimal::*

set hamburgerPrice [fromstr 5.50]
set milkshakePrice [fromstr 2.86]
set taxRate [/ [fromstr 7.65] [fromstr 100]]

set burgers 4000000000000000
set shakes 2
set net [+ [* [fromstr $burgers] $hamburgerPrice] [* [fromstr $shakes] $milkshakePrice]]
set tax [round_up [* $net $taxRate] 2]
set total [+ $net $tax]

puts "net=[tostr $net], tax=[tostr $tax], total=[tostr $total]"
Output:
net=22000000000000005.72, tax=1683000000000000.44, total=23683000000000006.16

Unicon

Solution takes advantage of Unicon's FxPt class as well as Unicon's operator overloading extension.

import math

procedure main()
   n_burgers := 4000000000000000
   n_shakes := 2

   price := FxPt(5.50) * n_burgers + FxPt(2.86) * n_shakes
   tax := (price * FxPt(7.65/100)).round(2)
   total := price + tax

   write(left("Price", 10), "$", right(price.toString(),21))
   write(left("Tax",   10), "$", right(tax.toString(),21))
   write(left("Total", 10), "$", right(total.toString(),21))
end
Output:
Price     $ 22000000000000005.72
Tax       $  1683000000000000.44
Total     $ 23683000000000006.16

VBA

Used in locality Euroland. Formatting as currency shows € in stead of $, and thousand separators are "." in stead of "," and the decimail 'point' is "," in stead of "." When run in the USA it will be with dollar sign and so on.

Public Sub currency_task()
    '4000000000000000 hamburgers at $5.50 each
    Dim number_of_hamburgers As Variant
    number_of_hamburgers = CDec(4E+15)
    Dim price_of_hamburgers As Currency
    price_of_hamburgers = 5.5
    '2 milkshakes at $2.86 each, and
    Dim number_of_milkshakes As Integer
    number_of_milkshakes = 2
    Dim price_of_milkshakes As Currency
    price_of_milkshakes = 2.86
    'a tax rate of 7.65%.
    Dim tax_rate As Single
    tax_rate = 0.0765
    'the total price before tax
    Dim total_price_before_tax As Variant
    total_price_before_tax = number_of_hamburgers * price_of_hamburgers
    total_price_before_tax = total_price_before_tax + number_of_milkshakes * price_of_milkshakes
    Debug.Print "Total price before tax "; Format(total_price_before_tax, "Currency")
    'the tax
    Dim tax As Variant
    tax = total_price_before_tax * tax_rate
    Debug.Print "Tax "; Format(tax, "Currency")
    'the total with tax
    Debug.Print "Total with tax "; Format(total_price_before_tax + tax, "Currency")
End Sub
Output:
Total price before tax € 22.000.000.000.000.005,72
Tax € 1.683.000.000.000.000,44
Total with tax € 23.683.000.000.000.006,16

Wren

Library: Wren-big
import "./big" for BigRat

var hamburgers     = BigRat.new("4000000000000000")
var milkshakes     = BigRat.two
var price1         = BigRat.fromFloat(5.5)
var price2         = BigRat.fromFloat(2.86)
var taxPc          = BigRat.fromFloat(0.0765)
var totalPc        = BigRat.fromFloat(1.0765)
var totalPreTax    = hamburgers*price1 + milkshakes*price2
var totalTax       = taxPc * totalPreTax
var totalAfterTax  = totalPreTax + totalTax
System.print("Total price before tax : %((totalPreTax).toDecimal(2))")
System.print("Tax                    :  %((totalTax).toDecimal(2))")
System.print("Total price after tax  : %((totalAfterTax).toDecimal(2))")
Output:
Total price before tax : 22000000000000005.72
Tax                    :  1683000000000000.44
Total price after tax  : 23683000000000006.16

zkl

zkl Ints are 64 bits, so we have 18 digits to play with. So, just multiply bucks by 100 and be careful when doing tax calculations.

Translation of: Python
var priceList=Dictionary("hamburger",550, "milkshake",286);
var taxRate=765;  // percent*M
const M=0d10_000;

fcn toBucks(n){ "$%,d.%02d".fmt(n.divr(100).xplode()) }
fcn taxIt(n)  { d,c:=n.divr(M).apply('*(taxRate)); d + (c+5000)/M; }
fcn calcTab(items){ // (hamburger,15), (milkshake,100) ...
   items=vm.arglist;
   fmt:="%-10s %8s %18s %26s";
   fmt.fmt("Item Price Quantity Extension".split().xplode()).println();

   totalBeforeTax:=0;
   foreach item,n in (items.sort(fcn(a,b){ a[0]<b[0] })){
      price:=priceList[item]; t:=price*n;
      fmt.fmt(item,toBucks(price),n,toBucks(t)).println();
      totalBeforeTax+=t;
   }
   fmt.fmt("","","","--------------------").println();
   fmt.fmt("","","subtotal",toBucks(totalBeforeTax)).println();

   tax:=taxIt(totalBeforeTax);
   fmt.fmt("","","Tax",toBucks(tax)).println();

   fmt.fmt("","","","--------------------").println();
   fmt.fmt("","","Total",toBucks(totalBeforeTax + tax)).println();
}
calcTab(T("milkshake",2),T("hamburger",4000000000000000));
Output:
Item          Price           Quantity                  Extension
hamburger     $5.50   4000000000000000 $22,000,000,000,000,000.00
milkshake     $2.86                  2                      $5.72
                                             --------------------
                              subtotal $22,000,000,000,000,005.72
                                   Tax  $1,683,000,000,000,000.44
                                             --------------------
                                 Total $23,683,000,000,000,006.16