views:

636

answers:

3

I am working on a Database that uses the Float data type to store values that should only be 2 decimal positions (dollars and cents). Using Float appears to work OK, as long as the person updating the Float column does a ROUND. Of course, this does not always happen and then when a SUM is done it is always off a few pennies from what is displayed because the display is formatted to show only 2 decmial positions. This database has hundreds of tables using Float and it would be helpful to be able to automatically ROUND the float columns.

Can this be done?

Can a TRIGGER be used on INSERT and UPDATE to do a ROUND on the Column?

If it can could you show how you would code the TRIGGER and would you recommend it?

Any other ideas?

We are using SQL Server 2005.

Here is a post that asks the question Use Float or Decimal for Accounting Application Dollar Amount? and I like the one responce that said

"You should really consider using some type of fixed point / arbitrary-precision number package (e.g. java BigNum, python decimal module) otherwise you'll be in for a world of hurt".

I feel the pain!

+3  A: 

You can do triggers, this is not an unreasonable use of them at all. A pre-insert and pre-update trigger should work fine.

In addition, you can do a one-shot fix, something like:

update tbl set column = round(column * 100) / 100

The syntax may not be perfect, but you should get the idea.

But I'm not sure I understand your "off by a few pennies" remark. You would have to sum a rather large number of floats for the errors to accumulate to 0.01. What is the column definition you're using?

Of course, one of the money-type decimal column definitions would be better for perfect accuracy but you may lose portability between DBMS'.

paxdiablo
The rounding issue happens normally on reports when the detail column is formatted to show only the cents (i.e. the value is 435.89786 but it is formatted with 2 decimal positions and prints as 435.90). The grand totals at the end SUMs up the non-rounded figure. I guess it was a few hundread records
Gerhard Weiss
That depends on the error value. If your floats truly are accurate to 0.000001, you would have to sum at least 1000 for it to affect the cents place (without taking into account that some would round up and some down, so should take even more).
paxdiablo
Anyway, no matter if you do round them off, there's still going to be inaccuracies in the float that will (eventually) cause a penny error. Decimal data types are the way to go if this errors occurs too soon.
paxdiablo
I would agree Pax. If the programming staff would always used the ROUND (Col,2) then it would take many transactions to to be off a penny. The problem is they didn't and we got to fix the data and the stored procedure(s) that are not rounding. As Dr Smith used to say "The Pain Ooooh the Pain"
Gerhard Weiss
I would then set up the triggers - they will also affect what the stored procs do, forcing them to be rounded as well (this may depend on your DBMS but I would think triggers would sit "under" storred procs). Then update the current faulty entries as already suggested.
paxdiablo
This is why stored procs and triggers exist: to get round the arrogance of developers who don't understand that, to a DBA, a database is a thing of beauty that must be nurtured and protected :-).
paxdiablo
+3  A: 

If you have control over the system, are you able to used the DECIMAL /MONEY/NUMERIC data type instead of a FLOAT?

It is, after all, the point of the question you referenced.

If not, and like many of us you are forced to live in that world of pain, your triggers suggestion would work. I would prefer, however, to control that in a different way...
- Don't allow apps or users direct write access to the tables
- Force access through SPs

I have also seen a system where write access is given to one (input/holding) table. This table then has a trigger on it to copy (and format/validate) the data into the real table.

Dems
+1  A: 

I don't think you can get exactly what you want by rounding. Rounding a float won't work accurately and dependably: whatever you do, you're going to be trying to apply a decimal rounding to a binary number and that just isn't going to be exact for anything but a power of 2.

You can only get accurate database-stored decimal representations of arbitrary sub-integer quantities by using a decimal datatype that's expressly designed for the purpose. These datatypes typically utilise some form of packed-decimal storage (so less efficient than binary) where, for example 2009 is stored as 0x2009.

What happens if you use the CONVERT function? Have you tried querying using something like this?

CONVERT(money, your_float_column)

Long-term, I think I'd consider adding a column to my table that has the desired type and maintaining its value through post-insert/update triggers, then over time switch all my code to use that column instead.

Then I'd hunt down and punish the individuals who thought it was a good idea to store money amounts in a float...

Mike Woodhouse
This is a pretty cleaver clean up idea on adding a second column.
Gerhard Weiss