- Feature Articles
- CodeSOD
- Error'd
- Forums
-
Other Articles
- Random Article
- Other Series
- Alex's Soapbox
- Announcements
- Best of…
- Best of Email
- Best of the Sidebar
- Bring Your Own Code
- Coded Smorgasbord
- Mandatory Fun Day
- Off Topic
- Representative Line
- News Roundup
- Editor's Soapbox
- Software on the Rocks
- Souvenir Potpourri
- Sponsor Post
- Tales from the Interview
- The Daily WTF: Live
- Virtudyne
Admin
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.
Admin
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.
Admin
Somehow this is what I zeroed in on.
Admin
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.
Admin
FTFY
Admin
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
Admin
CBA to type all that in again :stuck_out_tongue:
Admin
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"
Admin
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
Admin
Admin
Accurate? Accurate? String conversion of floats is not accurate.
It's precise. I give you that. So they should have written
What could they mean by "this result in a fault since a double has a higher precision than a float."? How could this conversion possibly result in a fault? WTF language is this?
Admin
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).
Admin
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 benormalised
- 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.
Admin
Yup. Time to dig this out again it seems:
http://cdn.antarcticglaciers.org/wp-content/uploads/2013/11/precision_accuracy.png
A compiler warning perhaps? The comment comes across as ESL.
Though the warnings are normally when possible truncation happens going from double to (single) float (though I did find this
printf()
error on some obscure platform.)Admin
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
todouble
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…
Admin
It's Java. I think that widens transparently.
Admin
As quantified in my post, this was a purely random number on my part. As it happens there is a whole argument about something very similar HERE, which actually cites £14.375 (not bad for a 7+ year recall although I did get the Up / Down bit wrong. But Meh! who ever worries about that, it's consistency that counts.), and strangely enough the calculation involves multiplying by 1.15 (15% VAT at that time).
For the record, this e-commerce site was tied in to the Accounts data / prices etc so the books had to balance as well. There are some horrors you do not want to visit you, a HMRC VAT inspection ranks pretty high on that list.
I will see, if the Dev still has the hack he used to find the "wrong" numbers
Admin
...sniff..
Admin
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.
Admin
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).
Admin
Or in this case,
BigDecimal
since Java doesn't have a built-indecimal
type.Admin
.... right, because Java isn't a big enough :wtf: on its own....
yes, if there is no native time third party alternatives are acceptable.
Admin
fuck this pissant editor ##YES! ABSOLUTELY!| TOTALLY! COULD NOT AGREE WITH YOU MORE!!!!!!!!!!!!!!!! But for some numbers it would unexpectedly round in the OTHER direction.
To save you a mouse click (If it is Too Lond and you Don't want to Read, please look at the highlighted / broken format section about a 1/3 of the way through. Taking a special note of the previous poster's parting shot.
AGAIN I am not claiming that this was the case as in that I described, but very similar.
> [2006-01-14 15:55 UTC] adi at rogers dot com I just confirmed this issue to still exist on the following systems:
> Windows XP Pro SP2 [IIS 5.1 ISAPI] (PHP 5.1.2 / php.ini precision: 12) > Windows XP Pro SP2 [IIS 5.1 CGI] (PHP 5.1.0RC1 / php.ini precision: 12) > Windows Server 2003 Enterprise x64 Edition SP1 [IIS 6.0 CGI] (PHP 5.1.2 / php.ini precision: 12)
> PHP was installed on all three systems manually (not using the installer).
> Hope that helps in isolating this issue.
Admin
Nice answer :heart:, but this was in the days before 3Dp, Stackoverflow, single currency, and legion other sources of "help".
Please, I have only just gotten out of therapy over this sort of thing. :)
Admin
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
Admin
Oh, ok. That scenario is remotely plausible. The alternate scenario would be a badly mangled submission, and we all know that doesn't happen.
I'm just confused because in order for a flag to avoid this warning, wouldn't it have to be a run-time warning? I'd expect static analysis to warn about the possibility of the conversion even if that flag could be determined to be set always. I couldn't imagine a language to have a run-time warning of this. And I can't imagine a language where having the alternate path would void the warning. Both would be positively insane. Even having the warning in the first place is dubious.
Maybe this function is the result of rote behaviour trained by warnings of double to float conversion? So because in my experience I had to work around warnings of double-to-float, I'm now guarding against warnings of float-to-double? Mistakenly applied tradition is a major source of WTF.
Admin
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.
Admin
and yet my comment is no less valid. ;-)
besides IIRC it was third party until.... java 1.3?
Admin
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.
Admin
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........
Admin
the article might not have, but @loose did.
i would have left well enough alone if he hadn't mentioned currency conversion.
Admin
Ah, so it was my reading comprehension that threw a cog. Well, doesn't change that I agree with you re. floats and currency :)
Admin
I was going to mention string conversion (in this reply), but my code threw an INT_OVERFLOW when it tried to count them.
:p :trolleybus: :barrier: :plus: :any: :other: :number: :of: :similar: :themed: :emojis:
Admin
And, you realize, that some rounding methods, this is intentional? There are reasons to change the default of ".5 rounds up" to remove the fact that 10% of fractions would round up into someone else' favor?
"This method treats positive and negative values symmetrically, and is therefore free of sign bias." https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
Using this knowledge, you can "Superman 3" your company a fraction of a penny per transaction and eventually take over the world - choosing the method that will best pad your bank account for each transaction.
https://www.mathsisfun.com/numbers/rounding-methods.html
Admin
Look what you made me do with all this talk about currency:
Note: this is "quick hack" code and any attempts to discuss it's WTFy will be studiously ignored.
Admin
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.....
Admin
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.
Admin
Buttuming I'm parsing what you said correctly (hint: use commas) it looks like you've claimed it's not in native time despite not actually testing it.
Then again, it is a bignumber format, so it probably is slower than one that isn't multi-precision.
There's no @since in the class documentation, which implies its been in core since Java 1.0.
Admin
ah... i see where the confusion happened
almost guaranteed. ;-) well that wouldn't be the first time my memory was faulty.Admin
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:
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.
Admin
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.
Admin
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.
Admin
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.
wait, now you are asserting that BigDecimal isn't a native/firstparty type?Admin
He misspelled primitive, I think.
Admin
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
Admin
Yeah, whoops.
I'm behind on my caffeine intake this morning.
Admin
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.
Admin
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.
Admin
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.
Admin
:facepalm: