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.




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)