• loose (disco)

    I think the Dev should get a +1

    There are times when you have to do stuff like this. Once upon a time there was a certain version of a certain programming language that was used for a multi-currency e-commerce site.

    A User (as in somebody paying to use the very customised Product) complained that a certain price was wrong.

    Long story short: Certain numbers when rounded and forced into 2 decimal places for the proposes of VAT calculation got the rounding "wrong".

    In the end the Dev "found" all these wrong numbers, and built a special function to deal with them. A function that would, out of context be a considerable :wtf:

    Incidentally, and not related to this story, the Dev concerned would comment his code with the phrase "This is not a WTF" when required.

  • Khudzlin (disco) in reply to loose

    You shouldn't use floats (of any precision) to deal with currency amounts. You should use decimals instead (or do fixed-point with integers), so exact decimal amounts do not become approached binary amounts and you can implement the rounding rules relevant to your application.

  • Dogsworth (disco)
    higher precision **then** a float

    Somehow this is what I zeroed in on.

  • accalia (disco) in reply to loose
    loose:
    A function that would, out of context be a considerable

    in context it's a pretty good :wtf: too,

    worse actually because you should not use floats or doubles for currency.

    Repeat after me:

    #The only correct type for currency is decimal. Any other type will cause trouble in the future.

    and that included integer types, because you will eventually have to change your precision and you can't do that sanely with integer stored decimals.

  • Maciejasjmj (disco) in reply to Dogsworth
    Dogsworth:
    this is what I zeroed0.000000000014634ed in on.

    FTFY

  • loose (disco) in reply to Khudzlin
    Khudzlin:
    You shouldn't use floats (of any precision) to deal with currency amounts.

    My point wasn't about using floats or not. It was about rounding. Most system either round up or round down when confronted with the dreaded 0.5 of a penny, or 0.005 of a £. What happened here and I must stress these are pure examples, was certain numbers like 12.375 would round down to 12.37 rather than the expected round up to 12.38

    When you are dicking about with e-commerce, minimum box / pallet quantities, VAT Inc / VAT Exc prices. And the Customer wants £13.99inc VAT price, from his given exc VAT price. You need to get it right. Not only that, you would be surprised just how many customers bitch about getting overcharged by a penny. Or the loss of trust that such errors engender in your website.

    But you are right, if you have decimal / currency type numbers you should use them. Or, as I do in a loosely type language multiply by 100, do everything as INT Math, and divide by 100. However, VAT is a whole new world of HELL

    In connection with the Article: I was just saying, at least the Dev did explain, and maybe he had a reason for doing it that way

  • loose (disco) in reply to accalia
    accalia:
    The only correct type for currency is decimal
    loose:
    My point wasn't about using floats or not. It was about rounding. Most system either round up or round down when confronted with the dreaded 0.5 of a penny, or 0.005 of a £. What happened here *and I must stress these are pure examples*, was certain numbers like 12.375 would round down to 12.37 rather than the expected round up to 12.38

    When you are dicking about with e-commerce, minimum box / pallet quantities, VAT Inc / VAT Exc prices. And the Customer wants £13.99inc VAT price, from his given exc VAT price. You need to get it right. Not only that, you would be surprised just how many customers bitch about getting overcharged by a penny. Or the loss of trust that such errors engender in your website.

    But you are right, if you have decimal / currency type numbers you should use them. Or, as I do in a loosely type language multiply by 100, do everything as INT Math, and divide by 100. However, VAT is a whole new world of HELL

    In connection with the Article: I was just saying, at least the Dev did explain, and maybe he had a reason for doing it that way

    CBA to type all that in again :stuck_out_tongue:

  • accalia (disco) in reply to loose
    loose:
    My point wasn't about using floats or not

    And my point was a naieve reading of your post would seem to have you asserting, or at least accepting, that Double is an acceptable data type for currency. It is not, so for clarity I "corrected*" your post.

    *posted a post of my own with the "correction"

  • loose (disco) in reply to accalia
    accalia:
    naieve reading

    I will have none of this sort of behaviour with my posts. :laughing: :manically:

    But you response is appreciated. But I do have to point this little gem out: loosely typed language - Afterthought :hanzo: edit

  • Gaska (disco) in reply to loose
    Comment held for moderation.
  • gleemonk (disco)
    Comment held for moderation.
  • Khudzlin (disco) in reply to loose

    The thing is you cannot expect floats to work, because an exact decimal amount will be rounded in binary (12.375 happens to be exact in binary - the fractional part is 3/8 - but it's the exception rather than the rule). To achieve correct rounding, you need to start from an exact amount instead of one that is already rounded in a way you cannot control (which is what happens with binary floating point).

  • Steve_The_Cynic (disco)

    Has anybody noticed the other :wtf: in the original code?

    The control flag (seriously, dude, control coupled? Constantine and Yourdon would have kittens!) is badly named, regardless of any of the disvirtues of the types chosen.

    The name is accurate, but should really be normalised - the via-string conversion will have a specific effect on the not-exactly-representable value of e.g. 4.70 when seen in the lens of the higher precision value, that a naked cast does not (zero-fill is quite likely for the result of the cast, and it certainly won't be the same as doing the to-double conversion on the string).

    And indeed, I don't see what kind of fault one could get from a precision-boosting conversion.

  • PJH (disco) in reply to gleemonk
    Comment held for moderation.
  • dkf (disco) in reply to loose
    loose:
    I think the Dev should get a +1

    Maybe, but the dev ought to also get a slap upside the head for the quantity of magical thinking involved in that code. Rounding might be important sometimes, but going from float to double via a string is not a winning proposition, especially as the precision of the conversion to string isn't specified.

    Also, most float-to-string conversion code is awful anyway. Because doing it right is hard

  • dkf (disco) in reply to PJH
    PJH:
    A compiler warning perhaps?

    It's Java. I think that widens transparently.

  • loose (disco) in reply to Khudzlin
    Comment held for moderation.
  • loose (disco) in reply to loose
    loose:
    In connection with the Article: I was just saying, at least the Dev did explain, and maybe he had a reason for doing it that way

    ...sniff..

  • Khudzlin (disco) in reply to loose

    Let's take your example. 12.5 is exact in binary, but 1.15 is not (it happens to be rounded down using both floats and doubles, according to the IEEE 754 specification), so you do not get 14.375 when multiplying them, but a slightly lower number, which will then round to 14.37 instead of the 14.38 you expect (and is mandated by HRMC VAT if I understand correctly).

    Using decimals, both 12.5 and 1.15 are exact, so the product is exactly 14.375, which then rounds to 14.38 according to the rules set by HRMC VAT.

  • kupfernigk (disco) in reply to loose
    loose:
    Long story short: Certain numbers when rounded and forced into 2 decimal places for the proposes of VAT calculation got the rounding "wrong".

    This is a long time ago now but when doing this stuff I understood that owing to the needs of multicurrency triangulation, currencies within the EU should always be handled to 3dp for consistent results. (Except for the lira, which at the time was several thousand to the Euro and so could be conveniently handled with no dp at all.) There was a lot of argument at the time about whether, when buying n items at price x, the VAT should be calculated as (VAT on x)*n, or VAT on (nx).

  • powerlord (disco) in reply to accalia
    Comment held for moderation.
  • accalia (disco) in reply to powerlord

    .... right, because Java isn't a big enough :wtf: on its own....

    yes, if there is no native time third party alternatives are acceptable.

  • loose (disco) in reply to Khudzlin
    Comment held for moderation.
  • loose (disco) in reply to kupfernigk

    Nice answer :heart:, but this was in the days before 3Dp, Stackoverflow, single currency, and legion other sources of "help".

    kupfernigk:
    There was a lot of argument at the time about whether, when buying n items at price x, the VAT should be calculated as (VAT on x)*n, or VAT on (nx).

    Please, I have only just gotten out of therapy over this sort of thing. :)

  • accalia (disco) in reply to loose
    loose:
    But for some numbers it would unexpectedly round in the OTHER direction.

    no, looking at that linked (and C&Pd ) chain of emails the behavior is CORRECT

    what's "unexpected*" is that the multiplication there is performed with floating point math which is inaccurate ad so had a "rounding error" when converted back to base10.

    this is why you need to use decimal types for money.

    *to some

  • gleemonk (disco) in reply to PJH
    Comment held for moderation.
  • powerlord (disco) in reply to accalia
    accalia:
    .... right, because Java isn't a big enough on its own....

    yes, if there is no native time third party alternatives are acceptable.

    BigDecimal isn't third party1. It's shipped as part of Java and has been for decades. I linked to the Java 8 documentation for it.

    1Although there was a rumor that IBM actually wrote the code for BigDecimal and BigInteger and not Sun.

  • accalia (disco) in reply to powerlord

    and yet my comment is no less valid. ;-)

    besides IIRC it was third party until.... java 1.3?

  • ashkante (disco) in reply to accalia
    accalia:
    Repeat after me:

    #The only correct type for currency is decimal. Any other type will cause trouble in the future.

    and that included integer types, because you will eventually have to change your precision and you can't do that sanely with integer stored decimals.

    While I agree with your assertion, I also note that the article never mentioned anything to do with currency. Floats and doubles work well (and fast) for quite a lot of different usage scenarios and sometimes converting from one to the other is a perfectly valid operation.

  • loose (disco) in reply to ashkante
    ashkante:
    While I agree with your assertion, I also note that the article never mentioned anything to do with currency.Floats and doubles work well (and fast) for quite a lot of different usage scenarios and sometimes converting from one to the other is a perfectly valid operation.

    They are all having a go at me, 'cos I happened to mention a currency example when explaining why you may need to roll your own :WTF: to resolve an issue. And in time honoured fashion resulting from generations of thinking like a shark (i.e. literally linear) that results in everybody fighting over scraps........

  • accalia (disco) in reply to ashkante
    Comment held for moderation.
  • ashkante (disco) in reply to accalia
    Comment held for moderation.
  • loose (disco) in reply to accalia
    Comment held for moderation.
  • WernerCD (disco) in reply to loose
    Comment held for moderation.
  • loose (disco)

    Look what you made me do with all this talk about currency:

    if (!isset($current_password)) {
    	$currentPassword = '';
    }
    else {
    	$currencyPassword = $currency_password;
    }
    

    Note: this is "quick hack" code and any attempts to discuss it's WTFy will be studiously ignored.

  • loose (disco) in reply to WernerCD

    I like this, especially when it comes to something like the Lottery. However, in very simplistic terms I would expect round([float]) to be very deterministic.

    For some languages rounding up or down is a compile feature, for others it is an optional parameter, or runtime configuration. Some have both. Either way you know what the result is going to be for whatever you put in.

    sheeeeesh.....

  • WernerCD (disco) in reply to loose

    It is very deterministic... Round up? Round down? Round even/odd? Round towards zero? Away from zero? Figure out that which benefits The Company and it's Overlords. Simple really.

  • powerlord (disco) in reply to accalia
    Comment held for moderation.
  • accalia (disco) in reply to powerlord
    Comment held for moderation.
  • safarty (disco) in reply to accalia
    accalia:
    The only correct type for currency is decimal. Any other type will cause trouble in the future.

    Untrue generalization!

    You may use a float where the value it represents is an estimate, or represents a continuous variable that is denominated in currency.

    A perfect example is suggested by this wtf. You make a model to predict stock prices:

    1. The model uses derived quantities such as the time derivative of price. This value can have huge dynamic range, so a float is called for. Another example is the model uses a discrete-time filter. The units are in currency but, again, there may be large dynamic range.
    2. The model needs to feedback upon itself. Here, to prevent quantization error each step, you need to save the output, which is currency, as a quasi-continuous value.
    3. The output of the model will be a float, in which case you need to cast that back into a decimal or whatever.

    Another example is you're calculating the average selling price of a product over a large number of transactions. Again, it's an estimate, so non-exact data types are acceptable.

  • powerlord (disco) in reply to accalia

    Java only has 7 native types and hasn't added any in 20 years.

    I'll also take a moment to point out that .NET doesn't have native types in user code... they're all structs that box the native types.

  • Wesley_Long (disco) in reply to dkf

    ... but the dev ought to also get a slap upside the head for the quantity of magical thinking involved in that code.

    I'd need to see more context. From the comments, it almost comes off as sarcastic, passive-aggressive parroting. My "Spidey Sense" is telling me that the dev was forced to do this, and isn't happy about it.

    Again, would need to see more from this dev to get a good read on it.

  • accalia (disco) in reply to safarty
    safarty:
    You may use a float where the value it represents is an estimate, or represents a continuous variable that is denominated in currency.

    I still assert that it is better to use a decimal type there. because even if estimate at least then you don't get Base2/Base10 conversion errors.

    powerlord:
    Java only has 7 native types and hasn't added any in 20 years.
    wait, now you are asserting that BigDecimal isn't a native/firstparty type?
  • boomzilla (disco) in reply to accalia
    accalia:
    wait, now you are asserting that BigDecimal isn't a native/firstparty type?

    He misspelled primitive, I think.

  • accalia (disco) in reply to boomzilla
    boomzilla:
    He misspelled primitive, I think.

    ah. yes. if that's primitive types then yes. but i said native not primitive.

    of course if you can primitive is perferable to non-primitive types

  • powerlord (disco) in reply to boomzilla

    Yeah, whoops.

    I'm behind on my caffeine intake this morning.

  • safarty (disco) in reply to accalia
    accalia:
    I still assert that it is better to use a decimal type there. because even if estimate at least then you don't get Base2/Base10 conversion errors.

    I agree that you minimize conversion errors, but the other factor is that non-binary data types, on anything that's not IBM Big Iron, is going to be painfully slow versus binary floating point.

  • accalia (disco) in reply to safarty
    safarty:
    I agree that you minimize conversion errors, but the other factor is that non-binary data types, on anything that's not IBM Big Iron, is going to be painfully slow versus binary floating point.

    Given recent improvements in processor speed i categorize that argument as Premature OptimizationTM

    Code correctly first, then measure performance. Only apply optimizations once you've measured performance. then measure performance again after optimizing. if there's no significant improvement rollback the optimizations and try a different tack.

  • Khudzlin (disco) in reply to loose

    In Java, rounding is completely deterministic: it follows the IEEE 754 exactly. What you fail to take into account is that merely converting a string written in decimal to a binary floating-point number causes rounding in the general case. That conversion is also deterministic, by the way.

    According to the IEEE754 specification, 1.15 converts to about 1.149999976 as a float (expect more 9's with a double). If you multiply 12.5 by 1.149999976, you don't get 14.375, but 14.3749997 (which rounds to 14.37 because it's closer than 14.38). Note that converting the VAT-exclusive price will also introduce error (unless you're lucky to have a price that is a fraction with a power of 2 as the denominator). That error might go the opposite way to the VAT-rate error (in which case you're lucky, since it will decrease the overall error and possibly give you the expected result) or the same way (in which case, you're unlucky and might get a result that is several pence off instead of just one).

    What you need to fix is not the rounding (which works exactly as specified), but the conversion, and you can do it only by using a decimal floating-point type instead of a binary one (getting rid of the conversion, in other words). Once you are operating on exact values, you can implement whatever rounding rules you need.

  • loose (disco)

    :facepalm:

Leave a comment on “We All Float Down Here…”

Log In or post as a guest

Replying to comment #:

« Return to Article