• (cs)

    Is the real WTF that they didn't know that number += 0 would produce * 10 in JavaScript, or the fact that JavaScript actually does that?

    While they should have done more testing, what part of x += 0 => x * 10 looks straightforward?

  • Anonymous (unregistered)

    It seems a little hard to grasp how this could possible slip through.

    Customers usually complain when charged tenfold... And by complain, I mean "sue". Did they process every single case of late payments manually?

  • synp (unregistered)

    Payment Amount: 400.97 Late Charge: 49.03 Publishing on thedailywtf: priceless

    (or $4050)

  • synp (unregistered) in reply to Anonymous
    Anonymous:
    It seems a little hard to grasp how this could possible slip through.

    Customers usually complain when charged tenfold... And by complain, I mean "sue". Did they process every single case of late payments manually?

    Or else this is a "tiny cosmetic fix" someone did just that morning that didn't need real testing because it was just a "tiny cosmetic fix"

  • (cs) in reply to pitchingchris
    pitchingchris:
    Is the real WTF that they didn't know that number += 0 would produce * 10 in JavaScript, or the fact that JavaScript actually does that?
    No. I can refer you to an article describing the concept of typeless variables. The code was actually
        total += "0"
    which is quite different --- and justifiable --- compared to
        total += 0
    Clearly you are focussing on the wrong area as the RWTF.
  • Barrett Jacobsen (unregistered) in reply to pitchingchris

    @pitchingchris it's not += 0 that produces * 10, it's += '0' that produces * 10 - the quotes mean treat it as a string, which causes the character '0' to be appended.

  • (cs) in reply to pitchingchris
    pitchingchris:
    Is the real WTF that they didn't know that number += 0 would produce * 10 in JavaScript, or the fact that JavaScript actually does that?

    While they should have done more testing, what part of x += 0 => x * 10 looks straightforward?

    I don't know much about JavaScript, but does this happen for x += 0, or just x += "0"? Knowing that it's not a strongly typed language, the latter actually kinda makes sense. If the first case also produces this behavior, then that is a WTF in its own right.

  • Jim (unregistered) in reply to pitchingchris
    pitchingchris:
    Is the real WTF that they didn't know that number += 0 would produce * 10 in JavaScript, or the fact that JavaScript actually does that?

    number += 0 // == number

    is not the same as

    number += '0' or number += "0"

  • Anonymous Coward (unregistered)

    The error isn't: total += "0"; This simply appends a "0" to numbers that come out something like 123.4 so that they are 123.40.

    The real problem is in the formatCharges() function. The regex is wrong.

    RegExp(/^\d{0,9}.\d{2}$/);

    should be

    RegExp(/^\d{0,9}.\d{1}$/);

    The way it currently is causes 123.40 to become 123.400 and get returned as 123400. That's where the multiply by 10 is happening, not in the string concatenation.

  • Scott Saunders (unregistered)

    It looks like they knew very well what the affect of += "0" is. They just screwed up the singleDecimal regex in the formatCharges() function.

  • Klaus Madsen (unregistered)

    The real WTF is in the formatCharges function.

    If starts by testing if the variable contains a number with two digits after the decimal point (the regex). If this is the case, it adds another zero. Then it removes the decimal point. Thus ending up with the value multiplied by 1000 instead of 100, as wanted.

  • Sumit (unregistered)
    if(zeroDecimal.test(total)) {
         total += ".00";         
    } else if(singleDecimal.test(total)) {
         total += "0";         
    }
    I guess they are checking if the number calculation produced a number with one decimal, then add one more digit.

    And the RegExp they are using to determine if the number has single decimal is:

    var singleDecimal = new RegExp(/^\d{0,9}\.\d{1}$/);
    which seems correct.

    I tested the following javascript in firebug:

    var singleDecimal = new RegExp(/^\d{0,9}\.\d{1}$/);
    console.log(singleDecimal.test(450));
    
    which returns false. So, the total += "0"; should not have executed.

    So, how did it result in the calculation shown on the web page?

  • (cs)

    Overloading the + character to mean both arithmetic addition and string concatenation in any language that doesn't force you to declare variables as either int or string has always stroked me as a really bad idea.

    Perl is one of the few script languages that does this correctly, where string concatenation is done with ..

    (note that the second dot is the end of the sentence)

  • Paula (unregistered) in reply to Gieron
    Gieron:
    Overloading the + character to mean both arithmetic addition and string concatenation in any language that doesn't force you to declare variables as either int or string has always stroked me as a really bad idea.

    Perl is one of the few script languages that does this correctly, where string concatenation is done with ..

    The reason perl doesn't fsck up is that it doesn't overload the + character to mean both operations.
  • (cs)

    This actually looks like a typo or just a mistake in the regular expression.

    They are definitely intending to append a "0" to the end of the string, but only if the singleDecimal regex test is true (so that "450.1" translates to "450.10"). The first time they do it, it looks fine, but in the formatCharges, the regex is actually looking for two decimal places.

    The javascript programmers knew what they were doing, they just made a careless error in the regular expression which should have been caught before it rolled to production. Nothing to be disgusted about, just good old fashioned human error.

  • mr_smith (unregistered)

    I think += '0' was meant for the single decimal place situation (not two) and therefore the regex is wrong and the guy saying this is correct.

  • mr_smith (unregistered) in reply to RHuckster
    RHuckster:
    This actually looks like a typo or just a mistake in the regular expression.

    They are definitely intending to append a "0" to the end of the string, but only if the singleDecimal regex test is true (so that "450.1" translates to "450.10"). The first time they do it, it looks fine, but in the formatCharges, the regex is actually looking for two decimal places.

    The javascript programmers knew what they were doing, they just made a careless error in the regular expression which should have been caught before it rolled to production. Nothing to be disgusted about, just good old fashioned human error.

    what about when there is 0 decimal places or more than 2? This code is horrible.

  • (cs)

    1.5 + "0" == "1.50", what's the problem again? While obviously there's a bug somewhere, this is the wrong place to look for it.

    The problem is here: var singleDecimal = new RegExp(/^\d{0,9}.\d{2}$/); if(singleDecimal.test(charges)) { charges = charges + "0"; }

    This code is clearly intended to pad the value out to exactly two decimal places. The problem is, what it actually does is pads two-decimal-place values to three, and leaves everything else alone. It should be replaced with the correct code from the other function:

    var zeroDecimal = new RegExp(/^\d{0,9}$/); //Try out var singleDecimal = new RegExp(/^\d{0,9}.\d{1}$/);

    if(zeroDecimal.test(total)) { total += ".00"; } else if(singleDecimal.test(total)) { total += "0"; }

  • (cs)

    Ok, WTF??

    1. This is a transaction involving money
    2. Why is the total calculated CLIENT side with javascript?
    3. Why does the word 'float' appear anywhere when dealing with money?
    4. Why the flying fuck are they using string concatenation to do this?

    I mean for fucks sake, just run the number through a regex validator to make sure it's valid, split it on the decimal point, convert to itneger and do an integer multiply by 100 on the first split and add the two together. There you go! Fixed point decimal without the need for all this weird-ass string concatenation.

    sigh

    But really, the way I see it, this thing should just be getting 3 display strings from the server and none of this should be done client-side in the first place. I mean, some people do have javascript turned off...

  • (cs)
    // Javascript doesn't handle decimal numbers very well. So as a 
    // workaraound, we convert the floating points to real numbers, perform 
    // the operation and then convert it again back to floating points
    I like how they're confusing themselves between floating point and real numbers in a way that nobody who knows jack about Fortran or mathematics would. Wonderful trap for future maintainers…
  • Miquel Fire (unregistered)

    I know that site! I go there every month!

  • (cs) in reply to mr_smith
    mr_smith:
    what about when there is 0 decimal places or more than 2? This code is horrible.
    Exact.

    I never programmed javascript, but I think the programmer can just multiply the input by 100, search for a decimal point and chop it off with any remaining decimal values. Or apply some Floor function. Whatever the language has available.

    That would work for any amount of decimal digits and is simpler than string concatenations.

  • Kris (unregistered)

    So was that giant block of code necessary to show one stupid line? I read halfway through before just skipping to the rest of the write up. While you're at it, why don't you just include the entire source code for the whole project!

    Or at the very least highlight the offending line, so I'm not looking at every line trying to find the stupid, which winds up being at the bottom.

  • (cs) in reply to Paula
    Paula:
    The reason perl doesn't fsck up is that it doesn't overload the + character to mean both operations.
    But how does it work in JavaBean?
  • Dlareg (unregistered)
    // Javascript doesn't handle decimal numbers very well. So as a // workaraound, we convert the floating points to real numbers, perform // the operation and then convert it again back to floating points

    So do they also have Imaginary floating points (e.g. Imaginary bills etc..)

  • BluePlateSpecial (unregistered)

    I don't see why they have to do all this in the first place. Even if Javascript is bad at decimal numbers you really only have 2 digits after the decimal point to worry about. Maybe 3 if there is slightly more precision but anything more than that for a monetary value is pointless. So Javascript can't carry 3 decimal digits accurately enough to just add them and spit out the result? I personally have never had a problem with its handling of floating point numbers.

    Not only that, but I agree completely with the person who said this shouldn't be done on the client side. Especially if Javascript really is that bad with decimal numbers. Just use your server side code to add the two together and send the result. I mean seriously, it isn't worth writing all that complexity and risking bugs (which they apparently introduced) just to add two fricking numbers together!

  • (cs)

    The real WTF is we live in a society where customer support is completely unable to a.) help with anything outside their on-rails policies, and b.) there is never any connection, however tenuous, between customer service and the folks that run their computers.

    I recently tried to break through this latter barrier with four major companies that you've heard of. It took about a year for three of them to fail. The only company that succeeded was IBM, and it only took them 9 months.

  • (cs)

    Yes, but the REAL question is, if you pay $400.97 plus $49.03, do they credit you with a payment of $4500? That would be cool. Your car will be paid off soon.

  • (cs) in reply to kmarsh
    kmarsh:
    The real WTF is we live in a society where customer support is completely unable to a.) help with anything outside their on-rails policies, and b.) there is never any connection, however tenuous, between customer service and the folks that run their computers.

    Yes, I have been frustrated with this "barrier" also. The customer service people always start from the assumption that they are there to explain how things work, to YOU the customer, and that it's not possible for you to inform them of anything useful.

    I have tried for two years to get a very large American bank to fix their Javascript window that lets you select which underlying credit card you want to use to generate a temporary "shop safe" credit card number. (One of my 2 credit cards was bought by this bank from another bank, so now I have two cards at this bank.)

    The Java-implemented drop-down box lets me pick one of the two cards... but they are both named "blah blah blah blah ending in xxxx", and only the part that says "blah blah blah blah end" shows up in the drop-down. Therefore, I can't tell which card I'm using. And you can't scroll left to right in the drop-down. And I can't rename the cards.

    I have tried to point this out to the customer service people, and I think a couple of them understood, but they have no way to contact the programmers. I offered to send screen shots, but that wouldn't have helped the customer service people. Aargh.

    The problem is still there.

  • Bim Job (unregistered) in reply to Gieron
    Gieron:
    Paula:
    The reason perl doesn't fsck up is that it doesn't overload the + character to mean both operations.
    But how does it work in JavaBean?
    Pretty much the same way it does in PaulaScript, of course -- brillantly.

    Perl has it's own wonderful fsckisms. Trying to work out whether an operation takes place in scalar or list context can be a PITA; let alone void context, which imho is decidedly non-intuitive for things like print().

    Sometimes I stare at really quite simple bits of Javascript (such as the one we're talking about) and wonder whether it's the language's fault that so many people spend so much time getting it so horribly wrong. It's OK as a toy functional language, but when you combine it with regular expressions and (Ghods forbid) the DOM, it rapidly becomes an invitation to a nightmare.

  • (cs) in reply to BluePlateSpecial
    BluePlateSpecial:
    I don't see why they have to do all this in the first place. Even if Javascript is bad at decimal numbers you really only have 2 digits after the decimal point to worry about. Maybe 3 if there is slightly more precision but anything more than that for a monetary value is pointless. So Javascript can't carry 3 decimal digits accurately enough to just add them and spit out the result? I personally have never had a problem with its handling of floating point numbers.

    You should never use floating point for any kind of money transactions. Even with only 2-3 decimal digits, you cannot guarantee the accuracy necessary for monetary transactions. Inaccuracies will creep in that will eventually cause a transaction to be off by a cent. Multiply that by thousands of transactions and suddenly, that turns into a big chunk of change.

  • (cs) in reply to Kermos
    Kermos:
    You should never use floating point for any kind of money transactions. Even with only 2-3 decimal digits, you cannot guarantee the accuracy necessary for monetary transactions. Inaccuracies will creep in that will eventually cause a transaction to be off by a cent. Multiply that by thousands of transactions and suddenly, that turns into a big chunk of change.

    Agreed. I was a TA for a programming lab for a few years in college and one of the assignments was to program a "cash register" application. There were several numbers that would instantly throw the program off if the students used floating point numbers. Students quickly came to dread that lab and the evil TA that broke their code, but hey, they ignored the warnings about floating point imprecision in the lab specs.

  • msebast (unregistered)

    TRWTF is you didn't just select "Other Amount" and enter the exact amount you want to pay.

  • damnum (unregistered) in reply to Kermos
    Kermos:
    BluePlateSpecial:
    I don't see why they have to do all this in the first place. Even if Javascript is bad at decimal numbers you really only have 2 digits after the decimal point to worry about. Maybe 3 if there is slightly more precision but anything more than that for a monetary value is pointless. So Javascript can't carry 3 decimal digits accurately enough to just add them and spit out the result? I personally have never had a problem with its handling of floating point numbers.

    You should never use floating point for any kind of money transactions. Even with only 2-3 decimal digits, you cannot guarantee the accuracy necessary for monetary transactions. Inaccuracies will creep in that will eventually cause a transaction to be off by a cent. Multiply that by thousands of transactions and suddenly, that turns into a big chunk of change.

    Quite right. In the system I develop for, we guarantee 6 decimals to be correct. >.< its quite a pain sometimes. Especially when our homebrew class for dealing with said numbers doesnt really feel bothered to toss exceptions for division by zero. >.< (yeah, java.)

  • BluePlateSpecial (unregistered)

    @Kermos and Anguirel

    Guess it's good I never work with money transactions then! I didn't think it made that big of difference but I could see how being off by a little could add up over time. I still think the person could have made the code a lot simpler to handle such a simple task (as some of the comments mentioned). I mean I have seen a lot more complex js but given the end result, that has to be a better way.

  • Downfall (unregistered)

    The Real WTF is that Clark is computer savvy enough to track down and determine the cause of the bug, but has apparently never heard of automatic payment.

  • Nomen Nescio (unregistered)

    How dare you be snarky and tell them how to fix the problem! If you know more than the customer service reps about JavaScript, there's something wrong.</sarcasm>

  • Anonymous (unregistered) in reply to BluePlateSpecial
    BluePlateSpecial:
    So Javascript can't carry 3 decimal digits accurately enough to just add them and spit out the result? I personally have never had a problem with its handling of floating point numbers.
    That assumption is a common pitfall, but false.

    Monetary calculations and floating point don't mix because:

    • Monetary calculations are usually required to be exact, no error allowed.
    • All standard floating point implementations use base 2.

    Unfortunately, even 0.1 translates to an infinitely long number in base 2 (pretty much the same that happens with 1/3 in decimal: it goes periodic). So no floating point type, no matter how many bits you throw at it, can guarantee even a SINGLE digit of decimal precision.

    It might go well if you have a) precise inputs, b) a limited number of steps, c) so much accuracy that the combined error of every single operation is still smaller than your output accuracy.

  • samiam (unregistered)

    must be a certian bank.....Fells Wargo? eh?

  • Herby (unregistered)

    Yes, "money math" can be "interesting". A long time ago, I wrote a summary billing program for computer time, billed at $75/hr (it was back in the 70's). The times that were given to me were in seconds, and it all had to be "accurate". My attempt was doing it in Fortran which on that machine had 24 bit accuracy in floating point (single precision) numbers. Needles to say when the bills got about $100 or so, the round off killed the accuracy in the cents column, and the "grand total" was off be pennies (or dimes). I handed the sheets over to a nice accounting person, and they called me back a day later with an adding machine tape attached, showing the error. Not too good. My solution was to use 'REAL*8' numbers and hope that the numbers didn't get too big (they never did), and my rounding was OK.

    Lesson learned: Floating point numbers don't do money well (unless you are the government, and don't deal in cents, or really care!).

  • Henning Makholm (unregistered) in reply to Anonymous Coward
    Anonymous Coward:
    The real problem is in the formatCharges() function. The regex is wrong.
    True, that's the BUG. But what makes it a WTF is something slightly different.

    Ignoring the questionable decision to do this on the client side, the programmer had the right idea: Convert everything to cents before doing arithmetic, to prevent rounding errors. The WTF is how he chose to covert to/from cents.

    From human-readable dollar amounts to cents, they chose string manipulation (and got it wrong), where it would be simpler just to let JavaScript interpret the original string as a float, multiply by 100 and then round to the nearest integer.

    From cents to human-readable dollars, they chose to use floating-point division to insert the decimal point, followed by complex string manipulation to correct for trailing zeroes being lost by the division. Here it would have been much simpler to stick with pure string manipulation to insert the decimal point.

    They used the right two techniques (arithmetic vs. string manipulation), but each for the wrong task!

  • JV (unregistered)

    It's obvious that they chose to implement their Business Layer logic on the client in Javascript and got it wrong.

  • jay (unregistered) in reply to Random832
    Random832:
    1.5 + "0" == "1.50", what's the problem again? While obviously there's a bug somewhere, this is the wrong place to look for it.

    The problem is here: var singleDecimal = new RegExp(/^\d{0,9}.\d{2}$/); if(singleDecimal.test(charges)) { charges = charges + "0"; }

    This code is clearly intended to pad the value out to exactly two decimal places. The problem is, what it actually does is pads two-decimal-place values to three, and leaves everything else alone. It should be replaced with the correct code from the other function:

    var zeroDecimal = new RegExp(/^\d{0,9}$/); //Try out var singleDecimal = new RegExp(/^\d{0,9}.\d{1}$/);

    if(zeroDecimal.test(total)) { total += ".00"; } else if(singleDecimal.test(total)) { total += "0"; }

    That would be closer but not quite. What if someone typed in "405.930"? There would also have to be a test for more than two digits after the decimal and truncate.

    So as far as I can see, the code as given will only work if there are no decimal places. Even in the "normal" case, where someone gives exactly two decimals, it multiplies by 10. Did anyone test this code at all before deploying?

    Seems like an awful lot of work to have to go to just to add two numbers together. Go Javascript!

  • jay (unregistered)

    I wonder if I could get the tenants in my rental unit to use this screen for late charges.

    Nah, wouldn't matter. I can't get them to pay the regular rent, never mind a late fee.

  • Anon (unregistered) in reply to Gieron
    Gieron:
    Overloading the + character to mean both arithmetic addition and string concatenation in any language that doesn't force you to declare variables as either int or string has always stroked me as a really bad idea.

    Perl is one of the few script languages that does this correctly, where string concatenation is done with ..

    (note that the second dot is the end of the sentence)

    Did you get a happy ending?

  • Clark S (unregistered)

    @downfall: I really, really don't trust these guys on the automatic payment front; they just changed their system so that payments-by-phone cost $10 instead of $3 (hooray for 233% increase!), and I didn't get a notice about this until 2 months later; this supposedly happened, too, because they "changed software" to something extremely enterprisey, no doubt. I just don't trust these people with unfettered access to my bank account.

    @samiam: Not "Fells Wargo," as it were. The company is the in-house financing agent for a brand of inexpensive import automobiles that may-or-may-not have an 'S', 'Z' and 'K' in the make.

    As for the JavaScript calculations, I have an attempt at a simple solution:

    for(var j=0; j<inputs.length; j++) {
             if(inputs[j].name != 'totalPayment' && inputs[j].value.trim() != '') {
                charges = parseFloat(inputs[j].value, 10) * 100;
                total += Math.floor(charges);
             }
          }
    total /= 100;
    
    oForm.totalPayment.value = total.toFixed(2);</pre>Now rip me to shreds.
    

    Of course, the lady on the phone really didn't care about any of this, and practically ignored what I had to say, like someone with their hand over their ears yelling "LALALALALALA I CAN'T HEAR YOU." Didn't even bother saying something like "sir, there's no way I can reach the programmers" or what-not.

  • Brian White (unregistered) in reply to Kermos
    Kermos:
    Ok, WTF??
    1. This is a transaction involving money
    2. Why is the total calculated CLIENT side with javascript?
    3. Why does the word 'float' appear anywhere when dealing with money?
    4. Why the flying fuck are they using string concatenation to do this?

    I mean for fucks sake, just run the number through a regex validator to make sure it's valid, split it on the decimal point, convert to itneger and do an integer multiply by 100 on the first split and add the two together. There you go! Fixed point decimal without the need for all this weird-ass string concatenation.

    Yes. I worked someplace that did massive calculations in javascript (the whole app ran client-side), and floating point errors were a constant source of pain until we went to integer calculations with pennies. It is the only sane way to work with money types in javascript - assuming of course you are dealing with something like USD. Integer calculation fails for some things like lira or other currencies that come in massive near-valueless amounts. And dealing with adding on that second 0? So painful in other currencies... some have 4 places, some have 0 places. In this case though - yeah, calc it server side since you're going to be charging their card server side.

  • Brian White (unregistered) in reply to BluePlateSpecial
    BluePlateSpecial:
    So Javascript can't carry 3 decimal digits accurately enough to just add them and spit out the result? I personally have never had a problem with its handling of floating point numbers.

    Yes, I've seen numerous problems with it's floating point math that throw off money calculations. You might ask it to add 2.5 and 3.0 and expect 5.5, but it secretly adds 2.497 and 2.997 (values are examples, not real) behind the scenes and returns 5.49, an entire penny off.

  • BluePlateSpecial (unregistered)

    At least she wasn't trying to sell you something. I called up tech support for my pc and basically was able to fix the problem myself while on the phone with someone whose every other word was "want to try Norton Antivirus?" I mean seriously solve my problem then you can start your sales pitch. My problem wasn't even related to a virus! I really think those hotlines should have an extension where you can reach a representative of the tech team responsible for the code (or management of). That why she could have just told you to hold on, transferred you, and then had you talk to someone who can look into it and maybe fix it. Seems perfectly reasonable to me but apparently they just want script readers. That reminds me of another time I called support for a credit card issue. After being transferred roughly 5 times to different people (none of which actually listened to my problem or else I would have been transferred correctly the first time), I finally told the person on the phone, "stop reading from a script and actually help me with this" (or something to that effect.) I eventually was told that my credit card account was closed, which is what I wanted to make sure of from the start. It took that many people to look up my account on the screen and tell me the status was indeed "closed." Geesh!

  • Henning Makholm (unregistered) in reply to Clark S
    Clark S:
    As for the JavaScript calculations, I have an attempt at a simple solution:

    [code]for(var j=0; j<inputs.length; j++) { if(inputs[j].name != 'totalPayment' && inputs[j].value.trim() != '') { charges = parseFloat(inputs[j].value, 10) * 100; total += Math.floor(charges); } }

    Better add 0.5 before Math.floor. You wouldn't want to take the floor of 4902.999999999999982 due to rounding for an input of "49.03".

    (I'm not sure how likely this is to happen, given that the two rounding steps seeem to tend to cancel each other out for the few examples I tries -- but rounding to nearest has to be much easier than proving rigorously that this can NEVER happen).

Leave a comment on “Late Payment Math”

Log In or post as a guest

Replying to comment #:

« Return to Article