# Fun With Numbers

Discussion in 'Javascript' started by Gene Wirchenko, Feb 3, 2012.

1. ### Gene WirchenkoGuest

Dear JavaScripters:

I need to do work with fixed-decimal quantities (mainly dollar
amounts but others as well). I need to be able to do reliable
arithmetic with them.

0.1 + 0.1 + 0.1 equals 0.15 + 0.15 mathematically, but not with
floating point. I need to have it equal.

So I am cheating. I am storing fixed-decimal amounts internally
as integers. When I need to output a value, I scale it, but any
arithmetic or comparison operations will be on the integers.

My first thought was that I was safe for nine digits worth,
because ECMAScript does a lot of 32-bit operations. That is on the
edge of what I need. The non-JavaScript system that I maintain now
has reports that generate nine digits worth in some reports.

I did some experimenting, and it appears that I might be able to
get 15 digits of precision, but not 16. After determining this, I
referred to the ECMAScript standard (ECMA-262 5.1 Edition of June
2011) to see if this matched. That standard says that it uses the
IEEE 754 floating point format, and some documentation on that says
that it is good for 15.95 digits of precision.

So far, so good.

But am I safe?

Can I count on exact arithmetic with integers of up to 15 digits
of precision?

If yes, can you please point to a reference? If no, please give
me a counterexample.

I would rather not have to mess around like this, but one of
JavaScript's nasty bits is only one number type.

Sincerely,

Gene Wirchenko

Gene Wirchenko, Feb 3, 2012

2. ### Tom de NeefGuest

Just wondering: how do you calculate 3,62% of (\$4.56 + \$14.13) and equal it
to 3,62% of \$4.56 plus 3,62% of \$14.13 ?
Or go from euro to dollar, etc ?
Tom

Tom de Neef, Feb 3, 2012

3. ### Gene WirchenkoGuest

I have so far tested with addition, but multiplication is only a
bit more complicated.

3.62% = 0.0362 requires a precision of (4,4) [meaning total
digits and decimal digits]. The dollar amounts are (3,2) and (4,2).
Whenever a dollar amount and a percentage are multiplied, the result
requires 6 decimal places. When it matters for it to be converted to
a dollar value, I will round and rescale the value.
\$4.56 + \$14.13 = \$18.69 then * 0.0362 = \$0.676578
0.0362 * \$4.56 = \$0.165072
0.0362 * \$14.13 = \$0.511506
sum: \$0.676578
So the amounts are equal. I would round after completing all
operations and rescale to get \$0.68. (If you round partway through,
yes, you can get rounding errors.)

Internally, the above is done with integers with the parened
numbers indicating the number of decimal digits:
\$456(2) + \$1413(2) = \$1869(2) then * 362(4) = \$676578(6)
362(4) * \$456(2) = \$165072(6)
362(4) * \$1413(2) = \$511506(6)
sum: \$676578(6)
Round and scale to \$68(2) which is \$0.68.

Sincerely,

Gene Wirchenko

Gene Wirchenko, Feb 3, 2012
4. ### Evertjan.Guest

Gene Wirchenko wrote on 03 feb 2012 in comp.lang.javascript:

integer multiplication [20 items sole at ...] of currency needs to be
exact, so do this multiplication on integer cents.
And do not convert from and to binary floating point dollars at all,
use strings.

Percentage or currency conversion needs to be rounded anyway,
so here the problem of binary math does not really exist.

============

We used to have Basic implementations with BNC [Binary Coded Decimal]-math,
those where the days!

I wrote here 20 Jan 2006:

Evertjan., Feb 3, 2012
5. ### Thomas 'PointedEars' LahnGuest

You store the floating-point number n as an integer value (i.e., this.value
% 1 = 0) and store the number of significant decimal digits of n
(this.scale) along with it in an object. Then

n = this.value Ã— 10^(âˆ’this.scale).

Of course, in order to do basic arithmetic with the object, you need special
methods that account for the scale. That is,

a.b + c.de
~ ab:scale1 + cde:scale2
~ (ab Ã— 10^(scale2 âˆ’ scale1) + cde)):max(scale1, scale2)
~ (ab Ã— 10^(scale2 âˆ’ scale1) + cde) Ã— 10^(âˆ’max(scale1, scale2)),

and

a.b + c.de
~ ab:scale1 Ã— cde:scale2
~ (ab Ã— 10^(scale2 âˆ’ scale1) Ã— cde) scale1 Ã— scale2)
~ (ab Ã— 10^(scale2 âˆ’ scale1) Ã— cde) Ã— 10^(âˆ’scale1 Ã— scale2)

where scale1 <= scale2 (here: scale1 = 1, scale2 = 2).

The idea is anything but new. For example, Java has had this for many
years:
<http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html>

The precision of an implementation of this in an ECMAScript implementation
is limited by the precision for integer values (number values `i' with i % 1
== 0), as the unscaled value and the scale are still IEEE-754 floating-point
values. But the precision of computation is indeed better than with plain
floating-point values, that is 1:1 + 1:1 + 1:1 ~ 3:1 ~ 0.3.

PointedEars

Thomas 'PointedEars' Lahn, Feb 4, 2012
6. ### Thomas 'PointedEars' LahnGuest

You store the floating-point number n as an integer value (i.e., this.value
% 1 = 0) and store the number of significant decimal digits of n
(this.scale) along with it in an object. Then

n = this.value Ã— 10^(âˆ’this.scale).

Of course, in order to do basic arithmetic with the object, you need special
methods that account for the scale. That is,

a.b Ã— c.de
~ ab:scale1 + cde:scale2
~ (ab Ã— 10^(scale2 âˆ’ scale1) + cde)):max(scale1, scale2)
~ (ab Ã— 10^(scale2 âˆ’ scale1) + cde) Ã— 10^(âˆ’max(scale1, scale2)),

and

a.b + c.de
~ ab:scale1 Ã— cde:scale2
~ (ab Ã— 10^(scale2 âˆ’ scale1) Ã— cde) scale1 Ã— scale2)
~ (ab Ã— 10^(scale2 âˆ’ scale1) Ã— cde) Ã— 10^(âˆ’scale1 Ã— scale2)

where scale1 <= scale2 (here: scale1 = 1, scale2 = 2).

The idea is anything but new. For example, Java has had this for many
years:
<http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html>

The precision of an implementation of this in an ECMAScript implementation
is limited by the precision for integer values (number values `i' with i % 1
== 0), as the unscaled value and the scale are still IEEE-754 floating-point
values. But the precision of computation is indeed better than with plain
floating-point values, that is 1:1 + 1:1 + 1:1 ~ 3:1 ~ 0.3.

PointedEars

Thomas 'PointedEars' Lahn, Feb 4, 2012
7. ### Thomas 'PointedEars' LahnGuest

You store the floating-point number n as an integer value (i.e., this.value
% 1 = 0) and store the number of significant decimal digits of n
(this.scale) along with it in an object. Then

n = this.value Ã— 10^(âˆ’this.scale).

Of course, in order to do basic arithmetic with the object, you need special
methods that account for the scale. That is,

a.b + c.de
~ ab:scale1 + cde:scale2
~ (ab Ã— 10^(scale2 âˆ’ scale1) + cde)):max(scale1, scale2)
~ (ab Ã— 10^(scale2 âˆ’ scale1) + cde) Ã— 10^(âˆ’max(scale1, scale2)),

and

a.b Ã— c.de
~ ab:scale1 Ã— cde:scale2
~ (ab Ã— 10^(scale2 âˆ’ scale1) Ã— cde) scale1 Ã— scale2)
~ (ab Ã— 10^(scale2 âˆ’ scale1) Ã— cde) Ã— 10^(âˆ’scale1 Ã— scale2)

where scale1 <= scale2 (here: scale1 = 1, scale2 = 2).

The idea is anything but new. For example, Java has had this for many
years:
<http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html>

The precision of an implementation of this in an ECMAScript implementation
is limited by the precision for integer values (number values `i' with i % 1
== 0), as the unscaled value and the scale are still IEEE-754 floating-point
values. But the precision of computation is indeed better than with plain
floating-point values, that is 1:1 + 1:1 + 1:1 ~ 3:1 ~ 0.3.

PointedEars

Thomas 'PointedEars' Lahn, Feb 4, 2012
8. ### Dr J R StocktonGuest

In comp.lang.javascript message <v3rmi7l1i7ji615ltsn7mncfsh2tf6ip2k@4ax.
Dollars are integers. No problem, up to and including 2^53.
You only get assured exact results if all numbers, including
intermediates, can be expressed in fixed-point binary with the most
significant and least significant bits no more than about 53 buts apart.

But the PC FPU, and maybe others, can work with 64-bit mantissa numbers,
so "internal" intermediates could be more precise - which might or might
not be standards-compliant.

Aside : we should be able to think of a test for that/

If you want to do exact addition, subtraction, multiplication,
comparison with dollar-cent prices, work in cents.

The 32-bit operations are logical, not arithmetic. They are not needed
in calculating finance.
Not entirely so. A single non-integer addition operation is good to
about that, depending on by how much the answer is below the nearest
power of two above. But subtraction of two inexactly-represented
quantities of similar magnitude loses precision.

Have you had the lesson on how to code the solutions of a potentially
-b +- root(b^2-4ac) / 2a // ???
Only if you really understand what you are doing. And not always then,
since most arithmetic expressions involving division have ideal results
which cannot be exactly represented in an IEEE Double.

And if you're not using a P60's FPU.
You could read my Web site, to reduce the frequency with which you
"discover" quite well-known wheels.

If it is necessary to reproduce the results which would be obtained by a
professional accountant using pre-computer equipment, then you are only
safe if you follow his methods exactly. That does not mean getting the

Happily, modern computers are so fast, and accounting is so simple, that
you^H^H^Hone can write an exact arbitrary-length decimal arithmetic
package without too much difficulty.
Via <http://www.merlyn.demon.co.uk/programs/00index.htm longcalc, for
example.

Dr J R Stockton, Feb 4, 2012