Why not FP for Money?

C

Chris Barker

Hi all,

I promise this is not a troll... Really, it's embarassing, because
this is one Engineer that DID take a Numerical Analysis course, and
from William Kahan, no less, and I still don't really get it. (and
yes, Alex, I could have gotten my degree without it)

I've seen it suggested various times that one should use fixed point
for money, perhaps micro cents represented as integers. However, if
you do that, you need to make sure you do all the rounding correctly.
If you were to use FP, you could have your IEEE 754 hardware do the
rounding for you, and get better performance to boot. If you are
rounding to cents at the end anyway, I can't see that the errors you'd
get with 64 bit FP numbers would ever be an issue.

I understand that when your numbers got large enough, you'd start to
lose the pennies, but how many of us have the luxury of doing
computation with quadrillions of dollars actually care about the
pennies? Besides, you could check for HUGE numbers and do something
special with them if you wanted.

What am I missing here?

By the way has anyone done anything with "Adaptive Precision
Floating-Point Arithmetic" ? It looks pretty handy:

http://www-2.cs.cmu.edu/~quake-papers/robust-arithmetic.abstract

-Chris
 
I

Istvan Albert

Chris said:
I understand that when your numbers got large enough, you'd start to
lose the pennies,

FP rounding errors have less to do with the *size* of the value
rather than the number of operations that are performed.
What am I missing here?

The money.

Or, equally likely, someone willing to pay the missing amounts.

Istvan.
 
C

Carlos Ribeiro

If you were to use FP, you could have your IEEE 754 hardware do the
rounding for you, and get better performance to boot. If you are
rounding to cents at the end anyway, I can't see that the errors you'd
get with 64 bit FP numbers would ever be an issue.

I'm not an expert either, but the best way to handle errors of any
kind is not to have them first place. Although what you're talking
about could possibly work, it would require extra care from the
programmer to make sure that all values would be thorougly checked and
rounded at the end of each and every operation. I'm not sure if this
can be made to work on large scale. The option would be to encapsulate
into a special purpose rounded-floating-point class. But then, I'm not
sure if the possible gains that you get from the use of IEEE754
accelerated hardware are worth the extra layer; in the end,
performance may be end up being similar to the one obtained with a
fixed point library.

There is another potential problem: I think that the biggest benefits
of the IEEE754 hardware would come with complex operations that are
directly supported by the hardware. For simple tasks that make the
bulk of money-handling code -- sums and multiplications -- integer
based code is probably as fast or faster, even with all the checks and
the scaling.


--
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: (e-mail address removed)
mail: (e-mail address removed)
 
P

phansen

Carlos said:
...
There is another potential problem: I think that the biggest benefits
of the IEEE754 hardware would come with complex operations that are
directly supported by the hardware. For simple tasks that make the
bulk of money-handling code -- sums and multiplications -- integer
based code is probably as fast or faster, even with all the checks and
the scaling.

This is a good point, Carlos. Although I don't know the speed
of current floating point hardware versus the integer opcodes,
it was certainly the case in the past that, while the floating
point hardware would give you a performance boost for floating
point math, *it was still slower than the CPU at integer math*.

Even if it doesn't now (which seems unlikely), choosing a path
that has greater performance but which has inherent problems
is a case of premature optimization of the worst kind...

-Peter
 
D

Dennis Lee Bieber

What am I missing here?
Well, some countries have banking laws that specify the exact
precision that must be maintained. Heck, my paycheck (which is a fixed
weekly quantity), has a net-pay that varies by a few pennies from week
to week as the unseen fractional cents add up to the next whole cent.

M$s money type (in Visual BASIC, as I recall) carries to the
hundredth of a cent (obviously $-centric... <G>).

Floating point is probably okay as long as you don't encounter
numbers that cause a shift (with the resulting loss of bits); but
wouldn't pass the strictness of those banking laws...

Then again, most of those banking applications were probably
written in COBOL, and use packed BCD notation.

--
 
J

John Roth

Chris Barker said:
Hi all,

I promise this is not a troll... Really, it's embarassing, because
this is one Engineer that DID take a Numerical Analysis course, and
from William Kahan, no less, and I still don't really get it. (and
yes, Alex, I could have gotten my degree without it)

It seems your course didn't make the one crucial distinction
that should be written in letters of fire at the start of every
discussion of floating point.

Floating point arithmetic is useful for continuous quantities,
or quantities that are measured (as in weighed, etc.) Integers
(and rationals) are useful for things that are counted, which
includes currency units, but also includes anything else that
comes in discrete units (apples and oranges, for instance).

Rounding issues in floating point revolve around preserving
precision, and that can be a very intricate undertaking,
but it's reasonably well understood in the context of things
that are measured.

Rounding issues in fixed point have to do with the fact
that integer division yields rationals, and if you attempt
to approximate it with fixed point fractions, then you
need to do something to preserve precision.

As another poster already said, rounding issues in
currency have a great deal to do with custom and
government regulation, and have very little to do with
anything that makes intuitive sense (unless you're an
accountant, and sometimes not even then [grin].)

Trying to mix the two always gets people in trouble,
over and above trying to use binary fractions in a field
where everyone expects decimal fractions.
I've seen it suggested various times that one should use fixed point
for money, perhaps micro cents represented as integers. However, if
you do that, you need to make sure you do all the rounding correctly.
If you were to use FP, you could have your IEEE 754 hardware do the
rounding for you, and get better performance to boot. If you are
rounding to cents at the end anyway, I can't see that the errors you'd
get with 64 bit FP numbers would ever be an issue.

I understand that when your numbers got large enough, you'd start to
lose the pennies, but how many of us have the luxury of doing
computation with quadrillions of dollars actually care about the
pennies? Besides, you could check for HUGE numbers and do something
special with them if you wanted.

What am I missing here?

Debits equal Credits to the penny, always have, and always will.
(at least since double entry bookkeeping was invented.) Any
imbalance will get the Great Gods of Accounting on your tail.

John Roth
 
P

Paul Rubin

I've seen it suggested various times that one should use fixed point
for money, perhaps micro cents represented as integers. However, if
you do that, you need to make sure you do all the rounding correctly.

Yes, "correctly" means "exactly the way decimal arithmetic does it,
and if your answer is different in the slightest, the auditors are
all over your ass".
If you were to use FP, you could have your IEEE 754 hardware do the
rounding for you

But you'll get different answers than decimal arithmetic would give
you, so the auditors will be all over your ass if you do it that way.
You don't want that. You need decimal arithmetic.
and get better performance to boot.

This is one situation where really, nobody cares about performance.
Accounting programs simply don't spend any significant fraction of
their CPU time doing arithmetic on money amounts. It's all I/O,
database lookups, etc.
 
D

Dan Bishop

John Roth said:
It seems your course didn't make the one crucial distinction
that should be written in letters of fire at the start of every
discussion of floating point.

Floating point arithmetic is useful for continuous quantities,
or quantities that are measured (as in weighed, etc.) Integers
(and rationals) are useful for things that are counted, which
includes currency units, but also includes anything else that
comes in discrete units (apples and oranges, for instance).

And the confusion comes from the fact that money is pretty much the
only thing that uses "continuous quantity" decimal-point notation to
represent a discrete thing.

But what about countries like Japan and (even more so) Turkey where
the currency units are so small that you never use fractions of them?
Do programmers there talk less about floating-point error?
 
T

Tim Peters

J

John Burton

Chris Barker said:
Hi all,

I promise this is not a troll... Really, it's embarassing, because
this is one Engineer that DID take a Numerical Analysis course, and
from William Kahan, no less, and I still don't really get it. (and
yes, Alex, I could have gotten my degree without it)

I've seen it suggested various times that one should use fixed point
for money, perhaps micro cents represented as integers. However, if
you do that, you need to make sure you do all the rounding correctly.


On my version of python:

# I spent £1 and 13 pence
p = 1.13

# How many pence is that?
print int(p*100)
112

# Oops

You don't need quadrillions of dollars before you run into a value which
can't be represented in a floating point value to such a degree that the
rounding comes out wrong. Yes you could probably "fix" the rounding to make
it work in practice but the problems is that it is simply not possible to
represent the value 1.13 in a floating point variable exactly and when
dealing with money you want to represent it exacctly, particularly as it's
so easy to do.
 
P

Paul Rubin

John Burton said:
Yes you could probably "fix" the rounding to make it work in
practice but the problems is that it is simply not possible to
represent the value 1.13 in a floating point variable exactly and
when dealing with money you want to represent it exacctly,
particularly as it's so easy to do.

The Decimal Floating Point pages that Tim linked to are really fantastic.
I remember going to a talk by some of the MIPS CPU designers a long time
ago, and they explained they left decimal arithmetic out of their RISC
design because they'd instrumented a bunch of Cobol programs and found
that arithmetic simply wasn't where financial applications spent their
computation time. However, the DFP site shows some pretty interesting
benchmarks.
 
P

Peter Hansen

John said:
On my version of python:

# I spent £1 and 13 pence
p = 1.13

# How many pence is that?
print int(p*100)
112

# Oops

You don't need quadrillions of dollars before you run into a value which
can't be represented in a floating point value to such a degree that the
rounding comes out wrong. Yes you could probably "fix" the rounding to make

At the risk of sounding like I might be supporting the idea of
using floats (which I'm not):

c:\>python112


int() doesn't do rounding, it truncates. If you actually *do* round,
it tends to work just fine. If all operations ended with a call to
round(), the floating point idea would not really be as bad as you
suggest here... but it's still a bad idea. :)

-Peter
 
A

Al Christians

I've done plenty of numerical work for financial institutions using
floating point, and, as far as I have ever experienced, you are
correct. My work has involved insurance (including getting actuarial
calculations approved by the pickiest insurance regulators), doing a
data systems conversion on over a $billion in customer accounts without
losing a cent, lending (programming calculation of terms of consumer
loans), and accounting work (preparing financial statements audited by
the major accounting firms). This work, however, has all been in the
USA. None of the numbers, in dollars, are big enough to make it very
likely that you will ever lose a penny using floating point numbers of
64-bits (~16 digits), but using the 80-bit (~18 digits) extended
precision numbers that the Intel architecture and a few programming
languages support is a little bit better.

If it's illegal(!) to overcharge on a loan by $0.01, you have a floating
point algorithm that is right 99.999999% of the time, and you are
programming for an operation that writes 10,000 loans a year, are you
OK? Of course, the knotty problems like this all involve compound
interest calculations including expressions with non-integer exponents.
And everyone who exponentiates anymore trusts a machine at some stage in
their calculations. Which do you trust more, the Intel hardware doing
floating point with a trivially small chance of losing a bit that would
flip the answer by a penny, or a fixed-point exponentiation routine in
someone's COBOL runtime library that works who-knows-how?

Two possible drawbacks:

1. You have to remember to re-round after every calculation. Instead of
"a = a + b" you have to write "x = round_money(a + b)". When just about
every statement is decorated like this, you can see that your
programming language is missing something at a low level that you really
might want it to have. OTOH, the rounding rules are very often
domain-specific, so having one particular set of rules built into the
language might be more of a nuisance than a help.

2. I've heard that there are domains where floating point accuracy is
not acceptable, although I've not encountered them. I don't know what
rules apply to settlement calculations in the U.S. securities markets
and if there are big penalties for losing a penny when you settle on a
$10 million bond. It seems crazy, but IDK. Similarly, I've seen posts
somewhere that said that rules for currency conversions (in Europe?)
specifically require something like 26-digit accuracy.

3. There are cases where (numerous) transaction between separate
entities are handled by some kind of electronic interchange, where the
arrangements between the entities are spelled out by contract, and
where systems will choke when the odd penny difference between firm A
and firm B occurs. It surely is easier to spell out things in terms of
ordinary decimal math, and that makes it easier to get everyone singing
off the same sheet.


Al
 
C

Carlos Ribeiro

1. You have to remember to re-round after every calculation. Instead of
"a = a + b" you have to write "x = round_money(a + b)". When just about
every statement is decorated like this, you can see that your
programming language is missing something at a low level that you really
might want it to have. OTOH, the rounding rules are very often
domain-specific, so having one particular set of rules built into the
language might be more of a nuisance than a help.

When I programmed in Delphi, that's what we had to do. Early versions
of Turbo Pascal had BCD libraries, and later versions added support
for currency types. But most of the time, we would use extended
(80-bit) numbers, and round after every calculation. To forget to
round would cause problems pretty quickly -- for example, when running
a report, it's fairly easy to have the wrong sum. If I remember it
well, at some point Delphi's standard library could be customized to
choose between two or three different rounding algorithms, including
one called "banker's rounding", where exact half numbers would be
converted to the closest even number. The goal was to make sure that
the end result of several roundings would not exhibit any tendency,
however small, towards the highest value:

Normal rounding:
0.5 -> 1
1.5 -> 2
2.5 -> 3
3.5 -> 4
sum of the rounded numbers -> 10

Banker's rounding:
0.5 -> 0
1.5 -> 2
2.5 -> 2
3.5 -> 4
sum of the banker's rounded numbers -> 8

sum of the original numbers -> 8


--
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: (e-mail address removed)
mail: (e-mail address removed)
 
C

Christos TZOTZIOY Georgiou

I've seen it suggested various times that one should use fixed point
for money, perhaps micro cents represented as integers. However, if
you do that, you need to make sure you do all the rounding correctly.
If you were to use FP, you could have your IEEE 754 hardware do the
rounding for you, and get better performance to boot. If you are
rounding to cents at the end anyway, I can't see that the errors you'd
get with 64 bit FP numbers would ever be an issue.

See simple example at end.
I understand that when your numbers got large enough, you'd start to
lose the pennies, but how many of us have the luxury of doing
computation with quadrillions of dollars actually care about the
pennies? Besides, you could check for HUGE numbers and do something
special with them if you wanted.

If you lose pennies, you better hope auditors don't read this newsgroup.
What am I missing here?

The following function imitates real life: sending quotes for a part to
a customer. We order in USD from SGI, we sell in EUR. An Excel sheet
(written by someone in the QA dept!) does the dirty job (and thankfully,
when I noticed the inconsistency, I corrected things, even if these
quotes won't ever be audited... but I felt embarassed nonetheless):

def quote(tp):
def _calc_vat(amount):
amount= tp(repr(amount))
vat= amount * tp('0.18')
total= amount + vat
return amount, vat, total
eur_to_usd_rate= 1.184 # the actual rate used April 22, 2004
amount_usd, vat_usd, total_usd= _calc_vat(2025)
amount_eur, vat_eur, total_eur= _calc_vat(2025/eur_to_usd_rate)
gfmt= "%-6.6s: USD %10.2f EUR %10.2f"
print gfmt % ("Amount", amount_usd, amount_eur)
print gfmt % ("Vat", vat_usd, vat_eur)
print gfmt % ("Total", total_usd, total_eur)

import decimal
quote(decimal.Decimal)
quote(float)

Run it with Python 2.4. See what goes wrong? Put the numbers in an
Excel sheet too.

Of course, one can take extra precautions to always have correct numbers
when working with floats and monetary amounts (eg. I did that for the
Excel sheet the sales dpt used). Why bother, though, if one has the
Decimal type?
 
C

Christos TZOTZIOY Georgiou

But what about countries like Japan and (even more so) Turkey where
the currency units are so small that you never use fractions of them?
Do programmers there talk less about floating-point error?

This used to be the case in Greece too; however, there was and still is
VAT (amount * 0.08 or 0.18), discounts, distribution of a "gross"[1]
discount ("your invoice is over 200000 drachmae, so I'll give you a
20000 drs discount") over the items of an invoice (and the "gross"
discount should equal the sum of qty*discount_per_item) etc.

So you still have floating point errors...

[1] don't know the english term, could be 'monetary discount', not a
percentage
 
C

Carlos Ribeiro

But what about countries like Japan and (even more so) Turkey where
the currency units are so small that you never use fractions of them?
Do programmers there talk less about floating-point error?

This used to be the case in Greece too; however, there was and still is
VAT (amount * 0.08 or 0.18), discounts, distribution of a "gross"[1]
discount ("your invoice is over 200000 drachmae, so I'll give you a
20000 drs discount") over the items of an invoice (and the "gross"
discount should equal the sum of qty*discount_per_item) etc.

Beware! Floating point errors may bite you *even for non-fractional
numbers*. Once the number of significant digits is bigger than the
precision of the mantissa, there will be automatic scaling of values,
dropping the least significant digit(s). Depending on the floating
point representation (single, double or extended, just to mention the
most common ones), this error will happen with numbers of different
magnitudes (from ~7 to ~19 significant digits, depending upon the
internal representation, if I remember it well). This error is
particularly nasty if you're repeatedly summing large quantities with
small ones.


--
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: (e-mail address removed)
mail: (e-mail address removed)
 
A

Alex Martelli

Christos TZOTZIOY Georgiou said:
amount= tp(repr(amount))
vat= amount * tp('0.18')
total= amount + vat ...
import decimal
quote(decimal.Decimal)
quote(float)

Run it with Python 2.4. See what goes wrong? Put the numbers in an
Excel sheet too.

The problem is pretty clear, but it shows up with decimal just as well
as with float: each of 'amount' and 'vat' (in Euro) has a fraction of a
eurocent that is less than 0.5 (about 0.4 eurocents each), and you don't
round them to eurocents before summing, so the total ends up with a
fraction of a eurocent that is _more_ than 0.5. So the final numbers
show, identically badly with both decimals and floats:

Amount: USD 2025.00 EUR 1710.30
Vat : USD 364.50 EUR 307.85
Total : USD 2389.50 EUR 2018.16

where the 1-eurocent discrepancy between 'Total' and the easily
eyeballed 'Amount'+'Vat' shows up quite nastily. (A case of "2+2 DOES
equal 5, for sufficiently large values of 2", to quote Anna [presumably
quoting somebody else...]).

Of course, one can take extra precautions to always have correct numbers
when working with floats and monetary amounts (eg. I did that for the
Excel sheet the sales dpt used). Why bother, though, if one has the
Decimal type?

It seems to me that you just showed that decimal is no panacea: you
still have to know where to round and to which precision. decimal is a
great tool but is not and cannot be a magic wand...


Alex
 
C

Christos TZOTZIOY Georgiou

The problem is pretty clear, but it shows up with decimal just as well
as with float: each of 'amount' and 'vat' (in Euro) has a fraction of a
eurocent that is less than 0.5 (about 0.4 eurocents each), and you don't
round them to eurocents before summing, so the total ends up with a
fraction of a eurocent that is _more_ than 0.5. So the final numbers
show, identically badly with both decimals and floats:

Oops! yes, this shows my inexperience with the decimal type; in my
session, the results were correct since I had earlier issued (after
trial and error) a

decimal.getcontext().prec=6

which I didn't include my post (forgetting about its importance).
Hence, you are correct saying that my confidence in the Decimal type
should not be absolute.
 
A

Alex Martelli

Christos TZOTZIOY Georgiou said:
...
session, the results were correct since I had earlier issued (after
trial and error) a

decimal.getcontext().prec=6

which I didn't include my post (forgetting about its importance).
Hence, you are correct saying that my confidence in the Decimal type
should not be absolute.

We do agree on this, particularly because the 'prec=6' happens to work
only depending on how many digits are in play BEFORE the decimal point.
What we'd _really_ want to specify is 'keep exactly two digits AFTER the
decimal point, no matter how many there are BEFORE', but that's not
decimal's job -- it's still a floating point type, albeit decimal, NOT a
fixed-point one. Maybe we do need a fixed-point-decimal for 2.5...?


Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,208
Messages
2,571,082
Members
47,683
Latest member
AustinFairchild

Latest Threads

Top