• PLasmab (unregistered)

    im lost. But then it is friday afternoon!

  • Wojtek (unregistered)

    Actually there's something like banker's rounding: http://en.wikipedia.org/wiki/Rounding

    3.045 -> 3.04 but 3.055 -> 3.06

    took me some time in teradata to understand what's going on

  • Kit (unregistered) in reply to PLasmab

    Ah, I know what it is! It's all to do will retail. $11.995, may aswell be $12. But from experience where I work, things at $39.99 sell alot quicker than if it were $40. Of course this isn't the case. I many would rather me not waste the time posting this. But it's (for lack of a better word on the spot) slightly ironical. ^^

  • (cs) in reply to PLasmab

    Obviously 39.995 * 100 + 0.5 = 4000.0 - ulp < 4000.0. The real WTF is people using Visual Basic.

  • notzed (unregistered)

    he's using floats ... well wtf do you expect?

  • hpeg (unregistered)

    And just wait for the screams once someone tries to round a negative number and gets even weirder results...

  • hpeg (unregistered)

    And just wait for the screams once someone tries to round a negative number and gets even weirder results...

  • Santa's Little Helper (unregistered)

    He's on an old Pentium, circa 1994.

    :)

  • NotoriousPSU (unregistered)

    I would imagine this wouldn't work all the time because of rounding errors associated with floating point math. I'm pretty sure that you couldn't ever write something that works all the time without changing the number's representation from IEEE floating point to a string, BCD, or some other purely decimal format.

  • aaron (unregistered)

    What went wrong is that he's programming in Visual Basic.

    He should re-write it in Pascal, now THERE'S a REAL language.

    (captcha: muhahaha -- diabolical?)

  • anonymous (unregistered)

    VB uses banker's rounding http://en.wikipedia.org/wiki/Banker%27s_rounding , a well understood WTF. There's even a Microsoft KB article, can't find the id at the moment.

  • imma (unregistered)

    is this on an old version of VB or VBA? gives me 40 here (on VB6)

  • Cyberwizzard (unregistered)

    I had to code a project for my boss which calculated VAT for invoices. It had to be done in Java and it required some Black Magic (tm) to make all the calculations add up. At a certain point we released a test version and within a day received bug reports stating that a sanity check failed when adding it all up: 40.00 == 40.00 failed and thus the whole program would stall as this was theoretically not possible. Eventually we wound up using my patched floating util library which says that 40.00 == 40.00 should be interpreted as (40.00 <= 40.00 + 0.0005 and 40.00 >= 40.00 - 0.0005)... And a bit more Black Magic was added to the inner workings...

  • Cyberwizzard (unregistered)

    I had to code a project for my boss which calculated VAT for invoices. It had to be done in Java and it required some Black Magic (tm) to make all the calculations add up. At a certain point we released a test version and within a day received bug reports stating that a sanity check failed when adding it all up: 40.00 == 40.00 failed and thus the whole program would stall as this was theoretically not possible. Eventually we wound up using my patched floating util library which says that 40.00 == 40.00 should be interpreted as (40.00 <= 40.00 + 0.0005 and 40.00 >= 40.00 - 0.0005)... And a bit more Black Magic was added to the inner workings...

  • Slepnir (unregistered)

    Visual basic is like alcohol. When used responsibly, it's ok. When abused, bad things (like this guy) happen.

    temp = number * factor + 0.5

    should be

    return INT(number + (factor * 0.5))

  • composer777 (unregistered)

    factor should probably be declared as an int....

  • tuomas aaltio (unregistered) in reply to Cyberwizzard

    it's a generally known fact that == operation fails in all programming languages, if the operands are of type float. == works only with integers.

  • MyKey_ (unregistered)

    Real programmers scorn floating point arithmetic. The decimal point was invented for pansy bedwetters who are unable to "think big."


    CAPTCHA: smile (I do)

  • (cs) in reply to Slepnir

    I think the problem is that he's mixing floats and doubles. I had a similar thing happen in C#. The 0.5 is instantiated as a float and then cast to a double. Sometimes, funny things can happen when casting between the two.

    Unless I don't understand VB (possible) then it has nothing to do with baker's rounding. Casting to an int should truncate, not round.

  • Simon Bradley (unregistered)

    Real number: 39.995

    Representation is: SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM 0100 0010 0001 1111 1111 1010 1110 0001 = 421f fae1

    Sign is Positive Exponent is 132 biased is 5 Mantissa is 1.2498437166213989000000000000000000000000 Value = 1.2498437166213989000000000000000000000000 x 2^5 = 39.9949989318847660000000000000000000000000 = 39.9949989318847660000000000000000000000000

    So, yes, an IEEE 754 representation of 39.995 to 2DP is 39.99.

  • Jim Lang (unregistered)

    Yep. Been There. Done That. Banker's rounding sucks when you expect Round() to actually round. I needed real rounding (dumb-ass rounding), so we have dRound():

    Public Function dRound(ByVal dblOrig As Double, _
                            Optional ByVal lngPlaces As Long = 0) As Double
    ' Purpose:  Provide a rounding routine that follows 'conventional' rounding
    '           i.e., round 0.5 to 1.0, 1.5 to 2.0, etc.  VB's round routine goes
    '           to the nearest even number.  so 0.5 would go to 0, 1.5 -> 2. etc.
    ' written:  A long time ago
    ' by:       jim lang
    ' modified: not really.
    
       If dblOrig = 0 Then
          dRound = 0   ' if it's zero, just make it 1.
       Else
          dRound = (Int(Abs(dblOrig) * 10 ^ lngPlaces + 0.500001) / 10 ^ lngPlaces) * _
                                                             (dblOrig / Abs(dblOrig))
       End If
    
    End Function ' dRound - a "Better" rounding routine
    

    Note that the final division/multiplier deals with the negative number rounding as well.

    VB. It's a job. And knowing how to handle the WTF imposed by the environment is the real trick.

    btw, that was taught to me by an old coworker when we were working in Cobol on an AS400.

  • Dolphin (unregistered)

    Floating point numbers are inherently inaccurate. The tradeoff is basically among three things: accuracy, speed, and range of representation (i.e. how small and how big can you go).

    The IEEE standard for floating point numbers is fast and has a wide range of representation, with the tradeoff being that you get a very small inaccuracy in your calculations, especially when doing addition. Floating point numbers are optimized for multiplication and division, not addition and subtraction, as it is multiplication and division that are the more expensive operations. Furthermore, it's not possible to represent every number entirely accurately, so the numbers going in to the function are probably off as well.

    If you looked at the parameters under the debugger with no rounding set, you'd probably see something like this for the first one:

    number = 39.99499999999999937

    and this for the second one:

    number = 11.99500000000000004

    The solution is usually to make the addition slightly more than 0.5. If I know that the number of digits in the original number will never be more than, say, three greater than the number of digits I require, modifying the addition as follows would solve the problem:

    temp = number * factor + 0.5001

    Where "001" is used to take care of any inaccuracies that show up after the maximum three extra digits. If you could have at most four extra digits, add another zero before the trailing one, and so on.

    As another poster mentioned, negative numbers are another issue. They need to be flipped to positive, rounded, then flipped to negative again.

  • (unregistered) in reply to tuomas aaltio

    == doesn't "fail" when used with floats; the problem is that programmers don't understand how floating point works. I refer you to http://docs.sun.com/source/806-3568/ncg_goldberg.html. If you know what you're doing (emphasize "if") you may very well have situations where using "==" is meaningful and doesn't "fail".

    Using floating point in financial calculations is just asking for trouble.

  • Rob (unregistered)

    Here's the KB article which talks about banker's rounding: http://support.microsoft.com/default.aspx?scid=kb;en-us;196652

    It doesn't say so, but VB.net's Math.Round does banker's rounding too.

  • (cs) in reply to Wojtek
    Wojtek:
    Actually there's something like banker's rounding: http://en.wikipedia.org/wiki/Rounding

    3.045 -> 3.04 but 3.055 -> 3.06

    But in the given example both numbers end with .995 so if you apply the rule increase the rounded digit if it is currently odd; leave it if it is already even, the result should be the same (considering that the rounded digit is the 2nd decimal)

    Well .... let's use the reply i've so often heard :

    It's not a bug. it's a feature !

  • vern (unregistered)

    OK, all you VB experts. I'm not sure how banker's rounding has anything to do with this, because you'd probably get the same result in any language using single precision floats.

    Furthermore, the example isn't all that clear what the # of digits is supposed to be. The code looks like it only coincidentally works if we assume 'digits' = '2'.

    Anywho, what it looks like we are supposed to conclude from this example is that when you store 11.995 as a single precision float, you actually are storing 11.995. Other numbers like 39.995 get stored in such a way that you can't represent the number exactly using a finite number of binary digits. Compare this to how our decimal number system fails to represent 1/3: 0.3333333 != 1/3

    If we were using a number system that was say, 6-based instead of 10-based, we could show 1/3 with a finite number of digits after the '.' <-- we probably wouldn't call the '.' a decimal anymore either... anyone know the name of a 6-based number system? 8 is octal. Anyway... Convert 39.995 to float and you're actually storing the value as 39.99499999999.... (but with a finite amount of space for repeating digits) Do all that strange math the code is doing, and you end up adding 0.5 (which happens to be stored exactly as is) and the result is 39.9999999.... (not paying attention to position of decimal - the code is just changing the position during all of this, and not really doing anything important. We all know what INT(3999.999) / 100.0 is likely to give.

  • Botia (unregistered) in reply to Dolphin

    I have to agree. You need to convert the number to binary to see what the actual value is What I don't get is why the Round() function is not used and why Decimal is not used for the type instead of Double (Decimal uses base-10 instead of base-2).

  • Anon (unregistered) in reply to vern
    vern:
    If we were using a number system that was say, 6-based instead of 10-based, we could show 1/3 with a finite number of digits after the '.' <-- we probably wouldn't call the '.' a decimal anymore either... anyone know the name of a 6-based number system? 8 is octal.

    Hexal?

  • Doug (unregistered) in reply to Slepnir

    Slepnir wrote:

    return INT(number + (factor * 0.5))

    WTF? INT(39.995 + (100 * 0.5)) = 89.995

    Now that is some fancy rounding!

  • M Harris (unregistered)

    The problem is not using Java's BigDecimal function, which allows you to use any of the 8 built-in rounding functions.

  • M Harris (unregistered) in reply to M Harris

    I meant BigDecimal Class.

  • Ownage Personified (unregistered)

    I had a point for this post but I think it floated away.

  • (cs) in reply to Cyberwizzard
    Cyberwizzard:
    I had to code a project for my boss which calculated VAT for invoices. It had to be done in Java and it required some Black Magic (tm) to make all the calculations add up. At a certain point we released a test version and within a day received bug reports stating that a sanity check failed when adding it all up: 40.00 == 40.00 failed and thus the whole program would stall as this was theoretically not possible. Eventually we wound up using my patched floating util library which says that 40.00 == 40.00 should be interpreted as (40.00 <= 40.00 + 0.0005 and 40.00 >= 40.00 - 0.0005)... And a bit more Black Magic was added to the inner workings...

    I hope you used BigDecimal and not double.

  • J-P (unregistered) in reply to tuomas aaltio
    tuomas aaltio:
    it's a generally known fact that == operation fails in all programming languages, if the operands are of type float. == works only with integers.
    It doesn't exactly fail, it just works in a different way that most people would expect at first :-)

    Correct me if I'm wrong, but doesn't == just basically check if (a XOR b) == 0? (checking if each bit matches in both values)

    So basically float a= float b=1.0f; if(a==b) //this shouldn't fail

    I've used floats a lot with OpenGL, and such comparisons usually work, as long as you're using constant values. Calculations also tend to work, as long as they're calculated from exactly same values, in the same order. But still I wouldn't count on my code seeing them as equal - I only use such comparisons on some optimizations I've made. "Should it fail when it shouldn't", it would just make the code just a little bit slower. On the other hand, checking if the values are "close enough" would make that part of code much slower :-)

  • John Cowan (unregistered)

    It's not possible to predict "the" name of a base-6 system, as the existing and well-established names reflect multiple Latin and Greek derivations.

    "Decimal" is from the Latin adjective for "a tenth part of", and if we had stuck to that, we'd have "dimidial" for base 2. Instead, we went with "binary", from the Latin for "in two parts". That would lead us to "octonary" for base 8, or if we stuck with the pattern of "decimal" we'd get "octonal", but no, we have "octal", apparently from a lame analogy with "decimal". "Ternary" is more often seen now for base 3, in strict analogy to "binary", but "trinary" is perfectly good Latin too.

    As for "hexadecimal", it's half Greek half Latin, and was probably imposed by higher-ups at IBM for the engineers' original "sexadecimal" (though "sextidecimal" would have been better Latin all around).

    So use "senary" or "sextenary", or "sexal" if you must, or replace the "s" with "h" in any of these if you feel you have to. Or do something else. Or say "base six" in plain English; it's short.

  • deathbydesignflaw (unregistered)

    "CINT(number + (factor * 0.5))"

    is real close.

    The 'easiest' I think is to do CINT(number + (.5 / factor)).

    What's happening is the final INT cast isn't applied to the whole thing, so turning around and dividing by factor means you could very likely end up with a decimal again depending on how many decimals you had to start with and how many you were rounding to.

  • hpeg (unregistered) in reply to M Harris

    Which then makes people do:

    public float round(float f, int prec) {
        BigDecimal big = new BigDecimal(f);
        big.setScale(2, BigDecimal.ROUND_HALF_UP);
        return big.floatValue();
    }
  • hpeg (unregistered) in reply to hpeg

    (or actually use

    prec
    , but you get the idea)

  • Look at me! I'm on the internets! (unregistered)

    Bankers rounding has nothing to do with it. How could bankers rounding have anything to do with it if he never calls the Round function?

    It's more likely to do with the fact that the parameters to the cos function have to be in radians.

    39.999 is actually stored internally as 39.9949...

    3999.49... + .5 = 3999.99... int(39.9999...) = 39.99.

  • pipTheGeek (unregistered) in reply to Botia
    What I don't get is why the Round() function is not used and why Decimal is not used for the type instead of Double
    If I remember rightly VB6 does not have a decimal type. You either used currency (fixed precision at 4 decimal places) or you use Double (the most accurate non integer type that vb6 supports) with all the already talked about oddness.
  • Charles Perreault (unregistered) in reply to Cotillion
    Cotillion:
    I think the problem is that he's mixing floats and doubles. I had a similar thing happen in C#. The 0.5 is instantiated as a float and then cast to a double. Sometimes, funny things can happen when casting between the two.

    In fact nothing funny can happen if you're using a IEEE754 cpu (like x86 or Sparc). Casting from a float to double only adds zero padding to the exposant and mantice for increased precision, no rounding whatsoever happens. Programmers using floating point should learn the IEEE754 floating point internal representation in binary :

    float : 1 bit for sign S 8 bits for exposant E (positive or negative) 23 bits for mantice M total 32 bits

    double : 1 bit for sign 11 bits for exposant 52 bits for mantice total 64 bits

    value = S * M ^ E

    Also since floating point has a base-2 mantice and esposant, a lot of values can't be represented perfectly but are only approximated to their nearest binary representation. I won't detail here that even the mantice has a normalized and normal form, for augmented precision around 0.

    I also recall to everyone that Java VM is not IEEE754 compliant and use its own floating point standard, but IIRC Visual Basic uses the cpu's (x86) floating point unit, therefore is IEEE754 compliant. So, when doint serious scientific calculus, one should rather use VB than Java (ouch, never thought I would say VB is better that something else). But a real scientist would rather use C/C++ or Fortran, or Maple / Matlab / Scilab. Any software may be used, but the floating point implementation must be fully known. For example, programming in floating point on IBM mainframes is not at all the same as on x86 and IBM made several big mistakes in the past implementing their FPU.

    I'd like also to remind programmers that none of us are omniscient. It's not because you can write hundreds of line of codes that you know everything about computer science. Some of you may hate floating points because they think the result is random, but it's not because you don't understand something that you must not respect it. For example, floating point was not made to represent money (for which fixed point precision is enough). If floating point values give bad result it's often more a design flaw than an IEEE754 issue. The fact is that IEEE754, even with all its limitation, is one of the best floating point representation to this day, and is very very usefull to everyone on this planet even though they don't realize it. Try running a nuclear plant simulation using fixed point or integer values. Floating point calculus is teached in Universities through the world and a lot of courses are on this single subject. Myself I have done 4 different courses specifically on floating point calculus in order to program correct optimization and simulation routines (Fourier transform, gradient descent, expectation-minimization aka EM, matrix operations). I know the IEEE754 standard by heart. Never forget the word "Science" in Computer Science. Computers are not only meant to design / browse web pages.

  • Joeri (unregistered)

    Like other people have mentioned, IEEE 754 floating point numbers work correctly in all languages, for the purpose they were designed: fast scientific calculations with predictable margins of error. They should NOT be used for financial calculations. They should also NOT be used if you don't understand how they work, because their "magic" will eventually bite you in the rear end. All decent programming languages have decimal arithmetic libraries (like the aforementioned BigDecimal) that you should use instead of floating points under one of these two scenario's.

  • (cs)

    I love how people keep going on about various types of rounding, even though they make no sense to the actual problem here.

    Here's what his roundTo function does: -Inputs are a number to round and the number of digits to round it to. -First, it figures out a multiplier. This will be 10 ^ digits. It calls this 'factor'. -It multiplies the number by factor in order to move the digits to the left of the decimal point. -Adding .5 at this point is supposed to do the round operation. If it already had .5 or more in the fractional part, this would cause the integer to round up. This is where the failure happens, of course. -Finally, it truncates off the fractional part (that's what Int() does), and divides by the factor to move the decimal back up.

    The failure is that it's doing this not to actual decimal numbers, but to floating point numbers. As has been pointed out already, 39.995 cannot be represented exactly in that limited space, and you actually get 39.994998931884766. When you do the math on that, you get: 39.9949989 * 100 = 3999.49989 3999.49989 + 0.5 = 3999.99989 Int(3999.999) = 3999 3999 / 100 = 39.99

    So your rounding fails for certain cases. This can't actually be completely fixed using floating point numbers. Limited size floating point numbers simply cannot represent some parts of the number line in the same way that limited size decimals can't either (0.333... = 1/3). Yes, adding more than .5 (like .501) would work, but it would make the answers wrong for some other chunk of the numbers.

    If you're doing your math in base 10, use a different representation. Visual Basic has a Decimal data type for this very reason (which I think uses a BCD format internally). Most other languages have something similar.

  • aikii (unregistered) in reply to Cotillion
    Cotillion:
    Unless I don't understand VB (possible) then it has nothing to do with baker's rounding. Casting to an int should truncate, not round.
    Double true.

    Must be related to a baker's dozen, which is another strange rounding habit.

    http://en.wikipedia.org/wiki/Baker%27s_dozen

  • chmod755 (unregistered)

    In the Microsoft world, they call this "Midpoint Rounding". You have to tell the rounding mechanism what you want to do if the digit you're using to round is exactly in the middle. In this case, one could argue that you could either round down or round up.

    The Math class in the .Net framework provides a Round method, and most people normally use it with either one parameter (the number to be rounded) or two (the number of digits to round to). It can take a third, which is an enum called "MidpointRounding", and it's possible values are "AwayFromZero" or "ToEven". Here's a link to MSDN's documentation about Midpoint Rounding.

    A buddy of mine <a rel="nofollow" href="http://www.codedig.com/blog/2007/01/09/rounding-in-c/"" target="_blank" title="http://www.codedig.com/blog/2007/01/09/rounding-in-c/""> blogged about this a few days ago, and the posting has a code snippet to demonstrate usage (in C#).

  • chmod755 (unregistered)

    In the Microsoft world, they call this "Midpoint Rounding". You have to tell the rounding mechanism what you want to do if the digit you're using to round is exactly in the middle. In this case, one could argue that you could either round down or round up.

    The Math class in the .Net framework provides a Round method, and most people normally use it with either one parameter (the number to be rounded) or two (the number of digits to round to). It can take a third, which is an enum called "MidpointRounding", and it's possible values are "AwayFromZero" or "ToEven". Here's a link to MSDN's documentation about Midpoint Rounding.

    A buddy of mine blogged about this a few days ago, and the posting has a code snippet to demonstrate usage (in C#).

  • chmod755 (unregistered)

    Whoops - sorry...started happy-clicking there...

  • anonymous (unregistered) in reply to Dolphin
    The solution is usually to make the addition slightly more than 0.5. If I know that the number of digits in the original number will never be more than, say, three greater than the number of digits I require, modifying the addition as follows would solve the problem:

    temp = number * factor + 0.5001

    Where "001" is used to take care of any inaccuracies that show up after the maximum three extra digits. If you could have at most four extra digits, add another zero before the trailing one, and so on.

    As another poster mentioned, negative numbers are another issue. They need to be flipped to positive, rounded, then flipped to negative again.

    Gah! I know you're being sarcastic, butI just can't take it! Math.Round! Math.Round! It works in VB, too! Argh!

    captcha: gygax (as in gary gygax, inventor of dungeons and dragons and Vice-Presidential Action Ranger charged with protecting the space-time continuum? of course, i only know this because he was on futurama, which is not at all nerdy, so i'll give you the benefit of the doubt, alex.)

  • UltimApe (unregistered) in reply to vern

    0.333 != 1/3

    however, 0.333... (repeating ad infinitum) == 1/3

    going with thta. 1 != .999, but 1 == .999... (repeating ad infinitum)

  • Marcin (unregistered) in reply to UltimApe
    UltimApe:
    0.333 != 1/3

    however, 0.333... (repeating ad infinitum) == 1/3

    going with thta. 1 != .999, but 1 == .999... (repeating ad infinitum)

    True, but I fail to see the relevance.

Leave a comment on “Round and Round”

Log In or post as a guest

Replying to comment #:

« Return to Article