1

I need a little help in javascript. I am using a simple js file for a little online store. The url of the demo page is: http://www.k-prim.biz/Esther/Store/proba.html The javascript that I am using is called orderform04.js. There is a bug: when entering q-ty 3 for product1 - at price 4.8 the result is 14.399999999999999 instead of 14.4!

Can anybody help with this issue?

the javascript is here too:

function OrderForm(prefix, precision, firstChoice) {

this.prefix = prefix ? prefix : '';

// ****************************
// Configure here
// ****************************
// names - Set these according to how the html ids are set
this.FORM_NAME = this.prefix + 'frmOrder';
this.BTN_TOTAL = this.prefix + 'btnTotal';
this.TXT_OUT = this.prefix + 'txtTotal';

// partial names - Set these according to how the html ids are set
this.CHK = this.prefix + 'chk';
this.SEL = this.prefix + 'sel';
this.PRICE = this.prefix + 'txtPrice';

// precision for the decimal places
// If not set, then no decimal adjustment is made
this.precision = precision ? precision : -1;

// if the drop down has the first choice after index 1
// this is used when checking or unchecking a checkbox
this.firstChoice = firstChoice ? firstChoice : 1;
// ****************************

// form
this.frm = document.getElementById(this.FORM_NAME);

// checkboxes
this.chkReg = new RegExp(this.CHK + '([0-9]+)');
this.getCheck = function(chkId) {
    return document.getElementById(this.CHK + chkId);
};

// selects
this.selReg = new RegExp(this.SEL + '([0-9]+)');
this.getSelect = function(selId) {
    return document.getElementById(this.SEL + selId);
};

// price spans
this.priceReg = new RegExp(this.PRICE + '([0-9]+)');

// text output
this.txtOut = document.getElementById(this.TXT_OUT);

// button
this.btnTotal = document.getElementById(this.BTN_TOTAL);

// price array
this.prices = new Array();

var o = this;
this.getCheckEvent = function() {
    return function() {
        o.checkRetotal(o, this);
    };
};

this.getSelectEvent = function() {
    return function() {
        o.totalCost(o);
    };
};

this.getBtnEvent = function() {
    return function() {
        o.totalCost(o);
    };
};

/*
 * Calculate the cost
 * 
 * Required:
 *  Span tags around the prices with IDs formatted
 *  so each item's numbers correspond its select elements and input checkboxes.
 */
this.totalCost = function(orderObj) {
    var spanObj = orderObj.frm.getElementsByTagName('span');
    var total = 0.0;
    for (var i=0; i<spanObj.length; i++) {
        var regResult = orderObj.priceReg.exec(spanObj[i].id);
        if (regResult) {
            var itemNum = regResult[1];
            var chkObj = orderObj.getCheck(itemNum);
            var selObj = orderObj.getSelect(itemNum);
            var price = orderObj.prices[itemNum];
            var quantity = 0;
            if (selObj) {
                quantity = parseFloat(selObj.options[selObj.selectedIndex].text);
                quantity = isNaN(quantity) ? 0 : quantity;
                if (chkObj) chkObj.checked = quantity;
            } else if (chkObj) {
                quantity = chkObj.checked ? 1 : 0;
            }
            total += quantity * price;
        }
    }
    if (this.precision == -1) {
        orderObj.txtOut.value = total
    } else {
        orderObj.txtOut.value = total.toFixed(this.precision);
    }
};

/*
 * Handle clicks on the checkboxes
 *
 * Required:
 *  The corresponding select elements and input checkboxes need to be numbered the same
 *
 */
this.checkRetotal = function(orderObj, obj) {
    var regResult = orderObj.chkReg.exec(obj.id);
    if (regResult) {
        var optObj = orderObj.getSelect(regResult[1]);
        if (optObj) {
            if (obj.checked) {
                optObj.selectedIndex = orderObj.firstChoice;
            } else {
                optObj.selectedIndex = 0;
            }
        }
        orderObj.totalCost(orderObj);
    }
};

/*
 * Set up events
 */
this.setEvents = function(orderObj) {
    var spanObj = orderObj.frm.getElementsByTagName('span');
    for (var i=0; i<spanObj.length; i++) {
        var regResult = orderObj.priceReg.exec(spanObj[i].id);
        if (regResult) {
            var itemNum = regResult[1];
            var chkObj = orderObj.getCheck(itemNum);
            var selObj = orderObj.getSelect(itemNum);
            if (chkObj) {
                chkObj.onclick = orderObj.getCheckEvent();
            }
            if (selObj) {
                selObj.onchange = orderObj.getSelectEvent();
            }
            if (orderObj.btnTotal) {
                orderObj.btnTotal.onclick = orderObj.getBtnEvent();
            }
        }
    }
};
this.setEvents(this);

/*
 *
 * Grab the prices from the html
 * Required:
 *  Prices should be wrapped in span tags, numbers only.
 */
this.grabPrices = function(orderObj) {
    var spanObj = orderObj.frm.getElementsByTagName('span');
    for (var i=0; i<spanObj.length; i++) {
        if (orderObj.priceReg.test(spanObj[i].id)) {
            var regResult = orderObj.priceReg.exec(spanObj[i].id);
            if (regResult) {
                orderObj.prices[regResult[1]] = parseFloat(spanObj[i].innerHTML);
            }
        }
    }
};
this.grabPrices(this);

}

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
ljubo
  • 11
  • 1
  • 3
    It's not a bug. Don't use floating point values for financial calculations: [Precise Financial Calculation in JavaScript. What Are the Gotchas?](http://stackoverflow.com/questions/2876536/precise-financial-calculation-in-javascript-what-are-the-gotchas). – Felix Kling Sep 08 '11 at 14:04
  • When asking question, please try to isolate the problem as goog as possible. In this case you could have started from where the value is displayed, looked then how it is calculated, and then reduced the problem to 'why is 3 * 4.8 != 14.4?' – keppla Sep 08 '11 at 14:16

2 Answers2

3

Have a look at the Floating Point Guide - this is simply caused by how computers represent decimal numbers, as opposed to being a bug in your program per se.

The solution is to use some sort of decimal data type rather than a floating point one. Javascript doesn't seem to have one natively; see https://stackoverflow.com/questions/744099/javascript-bigdecimal-library for discussion and some proposed workarounds.

There's a link on the Javascript-specific page of the guide, to a BigDecimal library for Javascript that will solve your problem.

Community
  • 1
  • 1
Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • Thanks a lot for the quick reply, but I am not familiar in java. I just use this script and I am not sure how to modify it. – ljubo Sep 08 '11 at 14:17
  • 2
    @ljubo: JavaScript is not Java. You should at least try to understand the things you use. – Felix Kling Sep 08 '11 at 14:20
  • I would like to learn too many things (Java, PHP, ASP, Perl...) unfortunately there is not enough time for all that :) – ljubo Sep 08 '11 at 14:26
  • @ljubo: One modification would have to happen in the `totalCost` function - `parseFloat` needs to be replaced by something that creates a decimal type instead. I'm afraid that there is no shortcut around understanding - the only alternative is writing code that you don't understand, and that's not an option in my book. – Andrzej Doyle Sep 08 '11 at 16:46
0

The complex and technically correct way to solve this is to use some sort of decimal library that doesn't use floating point internally, but uses all integers as others have suggested.

Depending upon what you are using the numbers for, you can sometimes make it all work by using floats and just rounding everything to two digits. For example, in your case:

14.399999999999999.toFixed(2) = 14.40 which is what you want.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • thanks but how can i modify the script to achieve that? I do not understand the java language... :( – ljubo Sep 08 '11 at 14:19
  • I don't know your code or have a way to run a modified version, but the general idea is that any place that you store a final result or display a result, you would use toFixed(2) on it to round it to two decimal places. – jfriend00 Sep 08 '11 at 14:28
  • 1
    You should realize that if you do a lot of financial math on floats, it's possible for you to get a small error (usually only $0.01). An accounting package, for example, would never use floats. They'd do everything with integer math in cents or use a library that does something similar. That type of error may not show in your usage or it may not cause any issues, but you would have to determine that. – jfriend00 Sep 08 '11 at 14:43