Version 3 of Doing arithmetic on currency

Updated 2003-03-25 00:21:42

Samy Zafrany wrote:

 >
 > Anyone has an explanation why the following happens?
 > Is it possible to rewrite the int function so it be completely robust?
 >
 >       tcl> expr int(8299.46 * 1000.0)
 >        result => 8299459

KBK The short answer is, "no." The desired result is application dependent.

The form of these numbers looks as if you may be dealing with currency. In this case, contemplate keeping only the cents (pence, centimes, pfennigs, groschen, ...) internally, and convert to floating point only for display. Remember that the floating point engine will represent all sufficiently small integers exactly!

Look at the following code for how to deal with currency properly. Remember that bankers think in integers: integer number of pennies, integer number of percentage points, etc. Scale everything to be an integer and work on it that way. Round to the nearest penny when appropriate.


 # Take a dollar amount, expressed as nnn.nn, and convert to an
 # integer number of cents.

 proc dollarsToCents { dollars } {
     if { [regexp -expanded {
         ^                         # Match the start of the string
         ([-+]?[[:digit:]]*)       # Match the dollars, with an optional sign
         [.]                       # Match the decimal point
         ([[:digit:]][[:digit:]])  # Match the cents
         $                         # Match the end of the string
     } $dollars -> bucks pennies] } {
         return [expr { 100. * $bucks + $pennies }]
     } else {
         return -code error "Dollar amount must be a decimal number with\
                             two digits after the decimal point."
     }
 }

 # Take an integer number of cents and reformat as a dollar amount.

 proc centsToDollars { cents } {
     return [format {%d.%02d} [expr { $cents / 100 }] [expr { $cents % 100 }]]
 }

 # Convert a dollar amount to pennies.

 set amount [dollarsToCents "8299.56"]
 puts "8299.56 dollars are $amount cents"

 # Multiply a dollar amount by 1000.

 set result [expr { round( 1000.0 * $amount ) }]
 set resultString [centsToDollars $result]
 puts "One thousand times 8299.56 is $resultString"

 # Apply a percentage to a dollar amount. Round the result to the
 # nearest cent.

 set result2 [expr { round( 0.01 * $amount ) }]
 set result2String [centsToDollars $result2]
 puts "One percent of 8299.56 is $result2String"

See also A real problem.


Category Mathematics