DOUBLE vs DECIMAL in MySQL - mysql

OK, so I know there are tons of articles stating I shouldn't use DOUBLE to store money on a MySQL database, or I'll end up with tricky precision bugs. The point is I am not designing a new database, I am ask to find way to optimise an existing system. The newer version contains 783 DOUBLE typed columns, most of them used to store money or formula to compute money amount.
So my first opinion on the subject was I should highly recommend a conversion from DOUBLE to DECIMAL in the next version, because the MySQL doc and everybody say so. But then I couldn't find any good argument to justify this recommandation, for three reasons :
We do not perform any calculation on the database. All operations are done in Java using BigDecimal, and MySQL is just used as a plain storage for results.
The 15 digits precision a DOUBLE offers is plenty enough since we store mainly amounts with 2 decimal digits, and occasionaly small numbers wit 8 decimal digits for formula arguments.
We have a 6 years record in production with no known issue of bug due to a loss of precision on the MySQL side.
Even by performing operations on a 18 millons rows table, like SUM and complex multiplications, I couldn't perform a bug of lack of precision. And we don't actually do this sort of things in production. I can show the precision lost by doing something like
SELECT columnName * 1.000000000000000 FROM tableName;
But I can't figure out a way to turn it into a bug at the 2nd decimal digit. Most of the real issues I found on the internet are 2005 and older forum entries, and I couldn't reproduce any of them on a 5.0.51 MySQL server.
So as long as we do not perform any SQL arithmetic operations, which we do not plan to do, are there any issue we should expect from only storing and retreiving a money amount in a DOUBLE column ?

Actually it's quite different. DOUBLE causes rounding issues. And if you do something like 0.1 + 0.2 it gives you something like 0.30000000000000004. I personally would not trust financial data that uses floating point math. The impact may be small, but who knows. I would rather have what I know is reliable data than data that were approximated, especially when you are dealing with money values.

The example from MySQL documentation http://dev.mysql.com/doc/refman/5.1/en/problems-with-float.html (i shrink it, documentation for this section is the same for 5.5)
mysql> create table t1 (i int, d1 double, d2 double);
mysql> insert into t1 values (2, 0.00 , 0.00),
(2, -13.20, 0.00),
(2, 59.60 , 46.40),
(2, 30.40 , 30.40);
mysql> select
i,
sum(d1) as a,
sum(d2) as b
from
t1
group by
i
having a <> b; -- a != b
+------+-------------------+------+
| i | a | b |
+------+-------------------+------+
| 2 | 76.80000000000001 | 76.8 |
+------+-------------------+------+
1 row in set (0.00 sec)
Basically if you sum a you get 0-13.2+59.6+30.4 = 76.8. If we sum up b we get 0+0+46.4+30.4=76.8. The sum of a and b is the same but MySQL documentation says:
A floating-point value as written in an SQL statement may not be the same as the value represented internally.
If we repeat the same with decimal:
mysql> create table t2 (i int, d1 decimal(60,30), d2 decimal(60,30));
Query OK, 0 rows affected (0.09 sec)
mysql> insert into t2 values (2, 0.00 , 0.00),
(2, -13.20, 0.00),
(2, 59.60 , 46.40),
(2, 30.40 , 30.40);
Query OK, 4 rows affected (0.07 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> select
i,
sum(d1) as a,
sum(d2) as b
from
t2
group by
i
having a <> b;
Empty set (0.00 sec)
The result as expected is empty set.
So as long you do not perform any SQL arithemetic operations you can use DOUBLE, but I would still prefer DECIMAL.
Another thing to note about DECIMAL is rounding if fractional part is too large. Example:
mysql> create table t3 (d decimal(5,2));
Query OK, 0 rows affected (0.07 sec)
mysql> insert into t3 (d) values(34.432);
Query OK, 1 row affected, 1 warning (0.10 sec)
mysql> show warnings;
+-------+------+----------------------------------------+
| Level | Code | Message |
+-------+------+----------------------------------------+
| Note | 1265 | Data truncated for column 'd' at row 1 |
+-------+------+----------------------------------------+
1 row in set (0.00 sec)
mysql> select * from t3;
+-------+
| d |
+-------+
| 34.43 |
+-------+
1 row in set (0.00 sec)

We have just been going through this same issue, but the other way around. That is, we store dollar amounts as DECIMAL, but now we're finding that, for example, MySQL was calculating a value of 4.389999999993, but when storing this into the DECIMAL field, it was storing it as 4.38 instead of 4.39 like we wanted it to. So, though DOUBLE may cause rounding issues, it seems that DECIMAL can cause some truncating issues as well.

"are there any issue we should expect from only storing and retreiving a money amount in a DOUBLE column ?"
It sounds like no rounding errors can be produced in your scenario and if there were, they would be truncated by the conversion to BigDecimal.
So I would say no.
However, there is no guarantee that some change in the future will not introduce a problem.

From your comments,
the tax amount rounded to the 4th decimal and the total price rounded
to the 2nd decimal.
Using the example in the comments, I might foresee a case where you have 400 sales of $1.47. Sales-before-tax would be $588.00, and sales-after-tax would sum to $636.51 (accounting for $48.51 in taxes). However, the sales tax of $0.121275 * 400 would be $48.52.
This was one way, albeit contrived, to force a penny's difference.
I would note that there are payroll tax forms from the IRS where they do not care if an error is below a certain amount (if memory serves, $0.50).
Your big question is: does anybody care if certain reports are off by a penny? If the your specs say: yes, be accurate to the penny, then you should go through the effort to convert to DECIMAL.
I have worked at a bank where a one-penny error was reported as a software defect. I tried (in vain) to cite the software specifications, which did not require this degree of precision for this application. (It was performing many chained multiplications.) I also pointed to the user acceptance test. (The software was verified and accepted.)
Alas, sometimes you just have to make the conversion. But I would encourage you to A) make sure that it's important to someone and then B) write tests to show that your reports are accurate to the degree specified.

Related

move decimals sql or divide

I built an administrative system in Laravel for my office, using mysql, one of the main tables is for the price of the products which is a double. The system has been in production for over 2 years now, but the government here changed our currency and removed 5 zeroes, so I need to change all the price values while keeping them with some decimals.
Currently, the lowest price in the list is 500
While the highest is 14985010
I need to perform a query to change ALL the price values in that column on the production DB (backup is done, so should be fine)
So the 500 one should be: 0.005
And the 14985010 should be: 149.8501
I'm thinking that all that is needed is to divide the values by 100000, but i might be wrong.
My SQL skills are super rusty, and I've been searching for a while but couldn't find the right answer. Any pointer would be greatly appreciated. Thanks!
I actually would recommend against just dividing all your currency data by some large factor, e.g. 100K. Instead, I propose adding a new column to your prices table, which is key into another table giving the factor which should be used to determine the current price. So you might have a setup like this:
prices table
price | factorKey
500 | 2
14985010 | 2
factors table
factorKey | value
1 | 1
2 | 0.00001
Then, to generate the actual current prices, you could do a join:
SELECT
p.price * f.value AS price
FROM prices p
INNER JOIN factors f
ON p.factorKey = f.factorKey;
Under this scheme, you would only need to modify the current factor to be used with your price data. You could even maintain another table to keep track of the historical prices. One reason to suggest this is that perhaps your government will change prices again in the future. It is error prone to do such large manual divisions/multiplications on your original data.
You should first fix the column to be sure it can handle the appropriate decimal places. I would suggest something like:
alter table t modify column price decimal(38, 10);
Then just update the value:
update t
price = price / 100000;
Having said that, you might want to check if the values are exactly what you expect:
select cast(cast(price as decimal(38, 10)) / 100000 as decimal(38, 10))
There may be subtle rounding errors that are not to your liking.
First of all, your currency data type should support decimal numbers, not just integers. I would suggest DECIMAL(12,4) for your column.
After you are sure the column data type can hold decimal numbers, you can update all rows in the table.
Here's a demonstration:
mysql> create table MyTable ( MyCurrencyColumn decimal(12,4));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into MyTable values (500), (14985010);
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from MyTable;
+------------------+
| MyCurrencyColumn |
+------------------+
| 500.0000 |
| 14985010.0000 |
+------------------+
2 rows in set (0.00 sec)
mysql> UPDATE MyTable SET MyCurrencyColumn = MyCurrencyColumn / 100000;
Query OK, 2 rows affected (0.02 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from MyTable;
+------------------+
| MyCurrencyColumn |
+------------------+
| 0.0050 |
| 149.8501 |
+------------------+

How to store a decimal calcaulation result in mysql and retrive it back as they were in memory

MySQL documentation says :
The DECIMAL and NUMERIC types store exact numeric data values. These types are used when it is important to preserve exact precision, for example with monetary data.
I did this test on that column decimal_column DECIMAL(31,30).
insert into tests (decimal_column) values(1/3);
then inspecting what has been stored gives this
select * from tests ;
> result : 0.333333333000000000000000000000
then reversing the math operation with this query gives this
select decimal_column*3 from test;
> result: 0.999999999000000000000000000000
I was expecting to get integer "1" as we do it on our calculators and in an excel sheet ! like this
#calculator or excel sheet
>input: 1 / 3
>result: 0.3333333333333333333333333333333333
>input: * 3
>result: 1
1- why MySQL didn't store the exact binary representation of (1 / 3) so I can use that result again in my calculations as they are in memory like a calculator or an excel sheet.
2- How to store in mysql the result of (1/3) as they are in the memory during calculation time, So I can retrieve the exact value back and do something like 3 * $storedValue to result in 1 as integer as we do in a calculator or excel sheet.
The problem is not in storage. In your example, the value was broken before storing it in the table.
Unfortunately, if you write 1/3, that will be calculated (and inserted) using the default representation:
SELECT 1/3
0.333333333
which, as you can see, has insufficient precision.
An additional problem is that when you send a constant (1, or 3) to the server, yo do so using a library or a connector, which can take liberties with the value. For example, it might believe that "1" and "3" are integers and their result is to be treated as an integer. So you get "1/3 = 0", but "1./3 = 0.333333", because the dot in "1." makes the connector realize that it needs to use its default floating point. And you get only six 3's because the "default floating point" of the connector has 6 digits. Then you store it into the database, but it is too late. You're storing with high precision a value that has been truncated to low precision.
You can try casting constants from the beginning. Instead of "1", you use the casting of 1 as a sufficiently large decimal. I'm using your 31,30 here, but check that you don't need to store larger numbers. Possibly, "31,20" would be better.
mysql> SELECT 1/3 UNION SELECT CAST(1 AS DECIMAL(31,30))/CAST(3 AS DECIMAL(31,30));
+----------------------------------+
| 1/3 |
+----------------------------------+
| 0.333333333000000000000000000000 |
| 0.333333333333333333333333333333 |
+----------------------------------+
2 rows in set (0.00 sec)
It is very awkward, but results should be better. Also, I think that it's only necessary to cast one value in an expression; MySQL will then promote all involved quantities as necessary. So, adding CAST(0 AS DECIMAL(x,y)) to sums and CAST(1 AS DECIMAL(x,y)) to multiplications might be enough.
mysql> SELECT 3*CAST(1 AS DECIMAL(31,30))/CAST(3 AS DECIMAL(31,30));
+-------------------------------------------------------+
| 3*CAST(1 AS DECIMAL(31,30))/CAST(3 AS DECIMAL(31,30)) |
+-------------------------------------------------------+
| 1.000000000000000000000000000000 |
+-------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT CAST(1 AS DECIMAL(31,30))*1/3;
+----------------------------------+
| CAST(1 AS DECIMAL(31,30))*1/3 |
+----------------------------------+
| 0.333333333333333333333333333333 |
+----------------------------------+
1 row in set (0.00 sec)
Note that this doesn't work because multiplication has higher precedence:
mysql> SELECT CAST(0 AS DECIMAL(31,30))+1/3;
+----------------------------------+
| CAST(0 AS DECIMAL(31,30))+1/3 |
+----------------------------------+
| 0.333333333000000000000000000000 |
+----------------------------------+
1 row in set (0.00 sec)
It depends on what you need the information for.
If it is stored for calculation and storage, calculate the result (0.33 instead of 1/3) and store that as decimal(1,5). But you can't easily calculate it backwards if you want to display it.
If it is stored to be displayed at a later time but never to be modified again (or at least not fast) you can store it as varchar but that will break sorting.
Or you could store the different elements (positives, negatives, totals, ... whatever) as decimal(5,0) and display / calculate it while using it.
And of course you can combine the above if you want to get the edge out of the computing time while selecting.
MySQL does its internal fractional arithmetic using 64-bit IEEE 754 floating point numbers.
They are generally approximations. It's entirely unreasonable to expect IEEE floating point, or decimal arithmetic for that matter, to achieve exact equality when doing
3*(1/3) == 1
That's just not the way computer arithmetic works.
There's also no way to store an exact representation of the value 1/3, unless you happen to be using an exotic computing system that stores rational (fractional) numbers in the form of (numerator,denominator) pairs. MySQL isn't such a system.
Most calculators implicitly round their results to the number of digits they can display. Excel, too, contains a formatting module. You can choose the format for a cell by pressing -1. The formatting module rounds these floating point numbers. You can achieve the same effect in MySQL using the ROUND() function. That function doesn't change the stored value, but it does render it in a way that conceals the tiny errors inherent in IEEE 754 floating arithmetic.
SELECT ROUND(decimal_column, 2) AS decimal_column
(Don't accounting people have to learn this stuff in school?)

Strange behavior of x * POW(10,y). A very small decimal at end. What's better for simple integers?

When I do this query:
SELECT
12*POW(10,-1),POW(10,-1),12*0.1,
11*POW(10,-1),POW(10,-1),11*0.1;`
I get this result:
1.2000000000000002 0.1 1,2 1.1 0.1 1,1
What the ..?
Not only am I surprised by the very small decimal 2 in the first column and nót in the third column, do you see the comma's there where there should be dots (resp 3d, last column) (or vice versa but not the two mixed)?
The first problem also occurs with 14, 17 and other numbers, but not with all!
My questions
Bug or do I need some explanation?
If it's a float issue, why not give precision error in 2nd and 3d
column?
Why once comma and other times dot?
What's the best solution regarding performance (process time in very big table)? (even if I have to re"type' my columns)
Then I mean for a number that doesn't really need a float column, like this:
X * 10^y
whereby X=integer and y=integer between -4 to +4
EDIT:
Question 4 resolution
Since I probably went a bit too far with my edits, I'll accept the answer below and post my own resolve about question 4 here: I decided to convert my columns to DECIMAL(9,3). Though it meant the storage requirement will go from 3 bytes to 4 bytes, I guess it's the best solution for not having to do a lot of CONVERT() and POW() afterwards.
Yet again - it is about floating point precision. Floating point data types, such as FLOAT in MySQL - store values approximately by definition . And your result will be in float data type since POW() will return floating-point result.
That means, values of that data types are represented with some precision, but not obligatory with exact value. Precision, actually, depends of data type itself (for example, double holds double precision). Thus, such things should not surprise you. You should be aware of that and operate on values if floating-point data types carefully.
Update:
Since you've updated your question, I'll be more specific:
No, it is not a bug - and explanation above fits this
Displaying values has nothing to do with their representation. Your 1-st, 2-nd and 3-rd values are all represented approximately (you just can't always see it)
This is strange. Are you sure that it's exactly the query you've run?:
mysql> select ##version;
+-----------+
| ##version |
+-----------+
| 5.5.27 |
+-----------+
1 row in set (0.02 sec)
mysql> SELECT
-> 12*POW(10,-1),POW(10,-1),12*0.1,
-> 11*POW(10,-1),POW(10,-1),11*0.1;
+--------------------+------------+--------+---------------+------------+--------+
| 12*POW(10,-1) | POW(10,-1) | 12*0.1 | 11*POW(10,-1) | POW(10,-1) | 11*0.1 |
+--------------------+------------+--------+---------------+------------+--------+
| 1.2000000000000002 | 0.1 | 1.2 | 1.1 | 0.1 | 1.1 |
+--------------------+------------+--------+---------------+------------+--------+
1 row in set (0.04 sec)
Best for what? Avoiding approximate representation? But that is how floating-point data types work. However, you can CONVERT your result directly to DECIMAL data type:
SELECT CONVERT(12*POW(10,-1), DECIMAL(4,1))
DECIMAL is fixed-point data type, so you'll avoid problems that you have in your case, but keep in mind that fixed-point data types hold values with predefined precision (that goes from definition) - so you'll have only certain count of signing digits.

MySQL Precison Issues in DECIMAL NUMERIC data type

In writing a function for scientific application, I ran into issues. I traced it back to MySQL's lack of precison.
Here is the page from the official documentation which claims that The maximum number of digits for DECIMAL is 65 - http://dev.mysql.com/doc/refman/5.6/en/fixed-point-types.html . It also describes how the value will be rounded if it exceeds the specified precison.
Here is reproducible code (a mysql stored function) to test it -
DELIMITER $$
DROP FUNCTION IF EXISTS test$$
CREATE FUNCTION test
(xx DECIMAL(30,25)
)
RETURNS DECIMAL(30,25)
DETERMINISTIC
BEGIN
DECLARE result DECIMAL(30,25);
SET result = 0.339946499848118887e-4;
RETURN(result);
END$$
DELIMITER ;
If you save the code above in a file called test.sql, you can run it by executing the following in mysql prompt -
source test.sql;
select test(0);
It produces the output -
+-----------------------------+
| test(0) |
+-----------------------------+
| 0.0000339946499848118900000 |
+-----------------------------+
1 row in set (0.00 sec)
As you can see, the number is getting rounded at the 20th digit, and then five zeroes are being added to it to get to the required/specified precison. That is cheating.
Am I mistaken, or is the documentation wrong?
This happens because mysql treats 0.339946499848118887e-4 as float and treats 0.0000339946499848118887 as fixed point.
mysql> select cast( 0.339946499848118887e-4 as DECIMAL(30, 25));
+----------------------------------------------------+
| cast( 0.339946499848118887e-4 as DECIMAL(30, 25)) |
+----------------------------------------------------+
| 0.0000339946499848118900000 |
+----------------------------------------------------+
1 row in set (0.00 sec)
mysql> select cast( 0.0000339946499848118887 as DECIMAL(30, 25));
+-----------------------------------------------------+
| cast( 0.0000339946499848118887 as DECIMAL(30, 25)) |
+-----------------------------------------------------+
| 0.0000339946499848118887000 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
As described in the mysql documentation on precision math - expression handling -
If any approximate values are present, the expression is approximate and is evaluated using floating-point arithmetic.
Quoting from, the documentation on numerical types,
Two numbers that look similar may be treated differently. For example, 2.34 is an exact-value (fixed-point) number, whereas 2.34E0 is an approximate-value (floating-point) number.
I don't know anything about SQL, but my guess would be this line:
SET result = 0.339946499848118887e-4;
If MySQL is anything like other languages I know, then this will first evaluate the right-hand side, and then assign the value to result. No matter what type result is declared to be or what precision it's declared to have, it wouldn't matter if the right-hand side has already lost precision when being evaluated. This is almost surely what is happening here.
I can reproduce your results, but If I change that line to
SET result = cast('0.339946499848118887e-4' as decimal(30, 25));
(casting from a string instead of from a floating-point constant of unspecified precision) then I correctly get
+-----------------------------+
| test(0) |
+-----------------------------+
| 0.0000339946499848118887000 |
+-----------------------------+
1 row in set (0.00 sec)
as desired. So that's your fix.
BTW, the documentation that scale in DECIMAL(precision, scale) cannot be greater than 30 seems to be in section 12.19.2. DECIMAL Data Type Changes:
The declaration syntax for a DECIMAL column is DECIMAL(M,D). The
ranges of values for the arguments in MySQL 5.6 are as follows:
M is the maximum number of digits (the precision). It has a range of 1
to 65. (Older versions of MySQL permitted a range of 1 to 254.)
D is the number of digits to the right of the decimal point (the
scale). It has a range of 0 to 30 and must be no larger than M.

Difference between float and decimal data type

What difference does it make when I use float and decimal data types in MySQL?.
When should I use which?
This is what I found when I had this doubt.
mysql> create table numbers (a decimal(10,2), b float);
mysql> insert into numbers values (100, 100);
mysql> select #a := (a/3), #b := (b/3), #a * 3, #b * 3 from numbers \G
*************************** 1. row ***************************
#a := (a/3): 33.333333333
#b := (b/3): 33.333333333333
#a + #a + #a: 99.999999999000000000000000000000
#b + #b + #b: 100
The decimal did exactly what's supposed to do on this cases, it
truncated the rest, thus losing the 1/3 part.
So for sums the decimal is better, but for divisions the float is
better, up to some point, of course. I mean, using DECIMAL will not give
you a "fail proof arithmetic" in any means.
A "float" in most environments is a binary floating-point type. It can accurately store base-2 values (to a certain point), but cannot accurately store many base-10 (decimal) values. Floats are most appropriate for scientific calculations. They're not appropriate for most business-oriented math, and inappropriate use of floats will bite you. Many decimal values can't be exactly represented in base-2. 0.1 can't, for instance, and so you see strange results like 1.0 - 0.1 = 0.8999999.
Decimals store base-10 numbers. Decimal is an good type for most business math (but any built-in "money" type is more appropriate for financial calculations), where the range of values exceeds that provided by integer types, and fractional values are needed. Decimals, as the name implies, are designed for base-10 numbers - they can accurately store decimal values (again, to a certain point).
MySQL recently changed they way they store the DECIMAL type. In the past they stored the characters (or nybbles) for each digit comprising an ASCII (or nybble) representation of a number - vs - a two's complement integer, or some derivative thereof.
The current storage format for DECIMAL is a series of 1,2,3,or 4-byte integers whose bits are concatenated to create a two's complement number with an implied decimal point, defined by you, and stored in the DB schema when you declare the column and specify it's DECIMAL size and decimal point position.
By way of example, if you take a 32-bit int you can store any number from 0 - 4,294,967,295. That will only reliably cover 999,999,999, so if you threw out 2 bits and used (1<<30 -1) you'd give up nothing. Covering all 9-digit numbers with only 4 bytes is more efficient than covering 4 digits in 32 bits using 4 ASCII characters, or 8 nybble digits. (a nybble is 4-bits, allowing values 0-15, more than is needed for 0-9, but you can't eliminate that waste by going to 3 bits, because that only covers values 0-7)
The example used on the MySQL online docs uses DECIMAL(18,9) as an example. This is 9 digits ahead of and 9 digits behind the implied decimal point, which as explained above requires the following storage.
As 18 8-bit chars:
144 bits
As 18 4-bit nybbles:
72 bits
As 2 32-bit integers:
64 bits
Currently DECIMAL supports a max of 65 digits, as DECIMAL(M,D) where the largest value for M allowed is 65, and the largest value of D allowed is 30.
So as not to require chunks of 9 digits at a time, integers smaller than 32-bits are used to add digits using 1,2 and 3 byte integers. For some reason that defies logic, signed, instead of unsigned ints were used, and in so doing, 1 bit gets thrown out, resulting in the following storage capabilities. For 1,2 and 4 byte ints the lost bit doesn't matter, but for the 3-byte int it's a disaster because an entire digit is lost due to the loss of that single bit.
With an 7-bit int:
0 - 99
With a 15-bit int:
0 - 9,999
With a 23-bit int:
0 - 999,999
(0 - 9,999,999 with a 24-bit int)
1,2,3 and 4-byte integers are concatenated together to form a "bit pool" DECIMAL uses to represent the number precisely as a two's complement integer. The decimal point is NOT stored, it is implied.
This means that no ASCII to int conversions are required of the DB engine to convert the "number" into something the CPU recognizes as a number. No rounding, no conversion errors, it's a real number the CPU can manipulate.
Calculations on this arbitrarily large integer must be done in software, as there is no hardware support for this kind of number, but these libraries are very old and highly optimized, having been written 50 years ago to support IBM 370 Fortran arbitrary precision floating point data. They're still a lot slower than fixed-sized integer algebra done with CPU integer hardware, or floating point calculations done on the FPU.
In terms of storage efficiency, because the exponent of a float is attached to each and every float, specifying implicitly where the decimal point is, it is massively redundant, and therefore inefficient for DB work. In a DB you already know where the decimal point is to go up front, and every row in the table that has a value for a DECIMAL column need only look at the 1 & only specification of where that decimal point is to be placed, stored in the schema as the arguments to a DECIMAL(M,D) as the implication of the M and the D values.
The many remarks found here about which format is to be used for various kinds of applications are correct, so I won't belabor the point. I took the time to write this here because whoever is maintaining the linked MySQL online documentation doesn't understand any of the above and after rounds of increasingly frustrating attempts to explain it to them I gave up. A good indication of how poorly they understood what they were writing is the very muddled and almost indecipherable presentation of the subject matter.
As a final thought, if you have need of high-precision floating point computation, there've been tremendous advances in floating point code in the last 20 years, and hardware support for 96-bit and Quadruple Precision float are right around the corner, but there are good arbitrary precision libraries out there if manipulation of the stored value is important.
Not just specific to MySQL, the difference between float and decimal types is the way that they represent fractional values. Floating point types represent fractions in binary, which can only represent values as {m*2^n | m, n Integers} . values such as 1/5 cannot be precisely represented (without round off error). Decimal numbers are similarly limited, but represent numbers like {m*10^n | m, n Integers}. Decimals still cannot represent numbers like 1/3, but it is often the case in many common fields, like finance, that the expectation is that certain decimal fractions can always be expressed without loss of fidelity. Since a decimal number can represent a value like $0.20 (one fifth of a dollar), it is preferred in those situations.
decimal is for fixed quantities like money where you want a specific number of decimal places. Floats are for storing ... floating point precision numbers.
mysql> CREATE TABLE num(id int ,fl float,dc dec(5,2));
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO num VALUES(1,13.75,13.75);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO num VALUES(2,13.15,13.15);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM num WHERE fl = 13.15;
Empty set (0.00 sec)
mysql> SELECT * FROM num WHERE dc = 13.15;
+------+-------+-------+
| id | fl | dc |
+------+-------+-------+
| 2 | 13.15 | 13.15 |
+------+-------+-------+
1 row in set (0.00 sec)
mysql> SELECT SUM(fl) ,SUM(dc) FROM num;
+--------------------+---------+
| SUM(fl) | SUM(dc) |
+--------------------+---------+
| 26.899999618530273 | 26.90 |
+--------------------+---------+
1 row in set (0.00 sec)
mysql> SELECT * FROM num WHERE ABS(fl - 13.15)<0.01;
+------+-------+-------+
| id | fl | dc |
+------+-------+-------+
| 2 | 13.15 | 13.15 |
+------+-------+-------+
1 row in set (0.00 sec)
I found this useful:
Generally, Float values are good for scientific Calculations, but should not be used for Financial/Monetary Values. For Business Oriented Math, always use Decimal.
Source: http://code.rohitink.com/2013/06/12/mysql-integer-float-decimal-data-types-differences/
If you are after performance and not precision, you should note that calculations with floats are much faster than decimals
Floating-Point Types (Approximate Value) - FLOAT, DOUBLE
The FLOAT and DOUBLE types represent approximate numeric data values. MySQL uses four bytes for single-precision values and eight bytes for double-precision values.
For FLOAT, the SQL standard permits an optional specification of the precision (but not the range of the exponent) in bits following the keyword FLOAT in parentheses. MySQL also supports this optional precision specification, but the precision value is used only to determine storage size. A precision from 0 to 23 results in a 4-byte single-precision FLOAT column. A precision from 24 to 53 results in an 8-byte double-precision DOUBLE column.
MySQL permits a nonstandard syntax: FLOAT(M,D) or REAL(M,D) or DOUBLE PRECISION(M,D). Here, “(M,D)” means than values can be stored with up to M digits in total, of which D digits may be after the decimal point. For example, a column defined as FLOAT(7,4) will look like -999.9999 when displayed. MySQL performs rounding when storing values, so if you insert 999.00009 into a FLOAT(7,4) column, the approximate result is 999.0001.
Because floating-point values are approximate and not stored as exact values, attempts to treat them as exact in comparisons may lead to problems. They are also subject to platform or implementation dependencies.
For maximum portability, code requiring storage of approximate numeric data values should use FLOAT or DOUBLE PRECISION with no specification of precision or number of digits.
https://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html
Problems with Floating-Point Values
Floating-point numbers sometimes cause confusion because they are approximate and not stored as exact values. A floating-point value as written in an SQL statement may not be the same as the value represented internally. Attempts to treat floating-point values as exact in comparisons may lead to problems. They are also subject to platform or implementation dependencies. The FLOAT and DOUBLE data types are subject to these issues. For DECIMAL columns, MySQL performs operations with a precision of 65 decimal digits, which should solve most common inaccuracy problems.
The following example uses DOUBLE to demonstrate how calculations that are done using floating-point operations are subject to floating-point error.
mysql> CREATE TABLE t1 (i INT, d1 DOUBLE, d2 DOUBLE);
mysql> INSERT INTO t1 VALUES (1, 101.40, 21.40), (1, -80.00, 0.00),
-> (2, 0.00, 0.00), (2, -13.20, 0.00), (2, 59.60, 46.40),
-> (2, 30.40, 30.40), (3, 37.00, 7.40), (3, -29.60, 0.00),
-> (4, 60.00, 15.40), (4, -10.60, 0.00), (4, -34.00, 0.00),
-> (5, 33.00, 0.00), (5, -25.80, 0.00), (5, 0.00, 7.20),
-> (6, 0.00, 0.00), (6, -51.40, 0.00);
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b
-> FROM t1 GROUP BY i HAVING a <> b;
+------+-------+------+
| i | a | b |
+------+-------+------+
| 1 | 21.4 | 21.4 |
| 2 | 76.8 | 76.8 |
| 3 | 7.4 | 7.4 |
| 4 | 15.4 | 15.4 |
| 5 | 7.2 | 7.2 |
| 6 | -51.4 | 0 |
+------+-------+------+
The result is correct. Although the first five records look like they should not satisfy the comparison (the values of a and b do not appear to be different), they may do so because the difference between the numbers shows up around the tenth decimal or so, depending on factors such as computer architecture or the compiler version or optimization level. For example, different CPUs may evaluate floating-point numbers differently.
If columns d1 and d2 had been defined as DECIMAL rather than DOUBLE, the result of the SELECT query would have contained only one row—the last one shown above.
The correct way to do floating-point number comparison is to first decide on an acceptable tolerance for differences between the numbers and then do the comparison against the tolerance value. For example, if we agree that floating-point numbers should be regarded the same if they are same within a precision of one in ten thousand (0.0001), the comparison should be written to find differences larger than the tolerance value:
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
-> GROUP BY i HAVING ABS(a - b) > 0.0001;
+------+-------+------+
| i | a | b |
+------+-------+------+
| 6 | -51.4 | 0 |
+------+-------+------+
1 row in set (0.00 sec)
Conversely, to get rows where the numbers are the same, the test should find differences within the tolerance value:
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
-> GROUP BY i HAVING ABS(a - b) <= 0.0001;
+------+------+------+
| i | a | b |
+------+------+------+
| 1 | 21.4 | 21.4 |
| 2 | 76.8 | 76.8 |
| 3 | 7.4 | 7.4 |
| 4 | 15.4 | 15.4 |
| 5 | 7.2 | 7.2 |
+------+------+------+
5 rows in set (0.03 sec)
Floating-point values are subject to platform or implementation dependencies. Suppose that you execute the following statements:
CREATE TABLE t1(c1 FLOAT(53,0), c2 FLOAT(53,0));
INSERT INTO t1 VALUES('1e+52','-1e+52');
SELECT * FROM t1;
On some platforms, the SELECT statement returns inf and -inf. On others, it returns 0 and -0.
An implication of the preceding issues is that if you attempt to create a replication slave by dumping table contents with mysqldump on the master and reloading the dump file into the slave, tables containing floating-point columns might differ between the two hosts.
https://dev.mysql.com/doc/refman/5.5/en/problems-with-float.html
Hard & Fast Rule
If all you need to do is add, subtract or multiply the numbers you are storing, DECIMAL is best.
If you need to divide or do any other form of arithmetic or algebra on the data you're almost certainly going to be happier with float. Floating point libraries, and on Intel processors, the floating point processor itself, have TONs of operations to correct, fix-up, detect and handle the blizzard of exceptions that occur when doing typical math functions - especially transcendental functions.
As for accuracy, I once wrote a budget system that computed the % contribution of each of 3,000+ accounts, for 3,600 budget units, by month to that unit's consolidation node, then based on that matrix of percentages (3,000 + x 12 x 3,600) I multiplied the amounts budgeted by the highest organizational nodes down to the next 3 levels of the organizational nodes, and then computed all (3,000 + 12) values for all 3,200 detail units from that. Millions and millions and millions of double precision floating point calculations, any one of which would throw off the roll-up of all of those projections in a bottoms-up consolidation back to the highest level in the organization.
The total floating point error after all of those calculations was ZERO. That was in 1986, and floating point libraries today are much, much better than they were back then. Intel does all of it's intermediate calculations of doubles in 80 bit precision, which all but eliminates rounding error. When someone tells you "it's floating point error" it's almost certainty NOT true.
float (and double) represents binary fractions
decimal represents decimal fractions
declare #float as float(10)
declare #Decimal as decimal(10)
declare #Inetger as int
set #float =10.7
set #Decimal =10.7
set #Inetger=#Decimal
print #Inetger
in float when set value to integer print 10
but in decimal 11