Fun With Numbers

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

  1. 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
    #1
    1. Advertisements

  2. Gene Wirchenko

    Tom de Neef Guest

    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
    #2
    1. Advertisements

  3. 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
    #3
  4. Gene Wirchenko

    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:
    Nice to read that threaad again with those familiar names:
    <http://bytes.com/topic/javascript/answers/447636-show-hex-numbers>
     
    Evertjan., Feb 3, 2012
    #4
  5. 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
    #5
  6. 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
  7. 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
  8. 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
    ill-conditioned quadratic equation, concerning
    -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
    right answer, it means getting the same answer.


    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
    #8
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.