13 ms·
0.30000000000000004
- mc3 7y agoThis is a good thing to be aware of. Also the "field" of floating point numbers is not commutative†, (can run on JS console:) x=0;for (let i=0; i<10000; i++) { x+=0.0000000000000000001; }; x+=1 --> 1.000000000000001 x=1;for (let i=0; i<10000; i++) { x+=0.0000000000000000001; }; --> 1 Although most of the time a+b===b+a can be relied on. And for most of the stuff we do on the web it's fine!†† † edit: Please s/commutative/associative/, thanks for the comments below. †† edit: that's wrong! Replace with (a+b)+c === a+(b+c)
- kstrauser 7y agoYep. The TL;DR of a numerical analysis class I took is that if you're going to sum a list of floats, sort it by increasing numeric value first so that the tiny values aren't rounded to zero every time.
- thaumasiotes 7y agoReally? It wasn't to use Kahan summation? https://en.wikipedia.org/wiki/Kahan_summation_algorithm https://en.wikipedia.org/wiki/Kahan_summation_algorithm
- kstrauser 7y agoHah! Well, yeah, that too. But if there's a gun to your head, sorting the list before adding will get you most of the way there with the least amount of work.
- thaumasiotes 7y ago> Also the "field" of floating point numbers is not commutative, (can run on JS console:) OK. >> x = 0; 0 >> for (let i=0; i<10000; i++) { x+=0.0000000000000000001; }; 1.0000000000000924e-15 >> x + 1 1.000000000000001 >> 1 + x 1.000000000000001 You've identified a problem, but it isn't that addition is noncommutative.
- paholg 7y agoYeah, what is demonstrated here is that floating point addition is nonassociative.
- gus_massa 7y agoNote that the addition is commutative [1], i.e. a+b==b+a always. What is failing is associativity, i.e. (a+b)+c==a+(b+c) For example (.0000000000000001 + .0000000000000001 ) + 1.0 --> 1.0000000000000002 .0000000000000001 + (.0000000000000001 + 1.0) --> 1.0 In your example, you are mixing both properties, (.0000000000000001 + .0000000000000001) + 1.0 --> 1.0000000000000002 (1.0 + .0000000000000001) + .0000000000000001 --> 1.0 but the difference is caused by the lack of associativity, not by the lack of commutativity. [1] Perhaps you must exclude -0.0. I think it is commutative even with -0.0, but I'm never 100% sure.
- thaumasiotes 7y agoI tried to determine how to perform IEEE 754 addition (in order to see whether it's commutative) by reading the standard: https://sci-hub.tw/10.1109/IEEESTD.2019.8766229 https://sci-hub.tw/10.1109/IEEESTD.2019.8766229 (Well, it's a big document. I searched for the string "addition", which occurs just 41 times.) I failed, but I believe I can show that the standard requires addition to be commutative in all cases: 1. "Clause 5 of this standard specifies the result of a single arithmetic operation." (§10.1) 2. "All conforming implementations of this standard shall provide the operations listed in this clause for all supported arithmetic formats, except as stated below. Unless otherwise specified, each of the computational operations specified by this standard that returns a numeric result shall be performed as if it first produced an intermediate result correct to infinite precision and with unbounded range, and then rounded that intermediate result, if necessary, to fit in the destination’s format" (§5.1) Obviously, addition of real numbers is commutative, so the intermediate result produced for addition(a,b) must be equal to that produced for addition(b,a). I hope, but cannot guarantee, that the rounding applied to that intermediate result would not then depend on the order of operands provided to the addition operator. 3. "The operation addition(x, y) computes x+y. The preferred exponent is min(Q(x), Q(y))." (§5.4.1). This is the entire definition of addition, as far as I could find. (It's also defined, just above this statement, as being a general-computational operation. According to §5.1, a general-computational operation is one which produces floating-point or integer results, rounds all results according to §4, and might signal floating-point exceptions according to §7.) 4. The standard encourages programming language implementations to treat IEEE 754 addition as commutative (§10.4): > A language implementation preserves the literal meaning of the source code by, for example: > - Applying the properties of real numbers to floating-point expressions only when they preserve numerical results and flags raised: > -- Applying the commutative law only to operations, such as addition and multiplication, for which neither the numerical values of the results, nor the representations of the results, depend on the order of the operands. > -- Applying the associative or distributive laws only when they preserve numerical results and flags raised. > -- Applying the identity laws (0 + x and 1 × x) only when they preserve numerical results and flags raised. This looks like a guarantee that, in IEEE 754 addition, "the representation of the result" (i.e. the sign/exponent/significand triple, or a special infinite or NaN value - §3.2) does not "depend on the order of the operands". §3.2 specifically allows an implementation to map multiple bitstrings ("encodings") to a single "representation", so it's possible that the bit pattern of the result of an addition may differ depending on the order of the addends. 5. "Except for the quantize operation, the value of a floating-point result (and hence its cohort) is determined by the operation and the operands’ values; it is never dependent on the representation or encoding of an operand." "The selection of a particular representation for a floating-point result is dependent on the operands’ representations, as described below, but is not affected by their encoding." (both from §5.2) HOWEVER... 6. §6, dealing with infinite and NaN values, implicitly contemplates that there might be a distinction between addition(a,b) and addition(b,a): > Operations on infinite operands are usually exact and therefore signal no exceptions, including, among others, > - addition(∞, x), addition(x, ∞), subtraction(∞, x), or subtraction(x, ∞), for finite x (§6.1)
- teraflop 7y agoYour example shows that floating-point addition isn't associative, not that it isn't commutative.
- mike_hock 7y agoIsn't that more of an associativity problem than a commutativity problem, though? 1.0 + 1e-16 == 1e-16 + 1.0 == 1.0 as well as 1.0 + 1e-15 == 1e-15 + 1.0 == 1.000000000000001 however (1.0 + (1e-16 + 1e-16)) == 1.0 + 2e-16 == 1.0000000000000002, whereas ((1.0 + 1e-16) + 1e-16) == 1.0 + 1e-16 == 1.0
- mark-r 7y agoAlso the subject of one of the most popular questions on StackOverflow: https://stackoverflow.com/q/588004/5987 https://stackoverflow.com/q/588004/5987
- deleted 7y ago[deleted]
- Ididntdothis 7y agoI still remember when I encountered this and nobody else in the office knew about it either. We speculated about broken CPUs and compilers until somebody found a newsgroup post that explained everything. Makes me wonder why we haven't switched to a better floating point model in the last decades. It will probably be slower but a lot of problems could be avoided.
- dragontamer 7y ago> Makes me wonder why we haven't switched to a better floating point model in the last decades. The opposite. Decimal floating points have been available in COBOL from the 1960s, but seem to have fallen out of favor in recent days. This might be a reason why bankers / financial data remains on ancient COBOL systems. Fun fact: PowerPC systems still support decimal-floats natively (even the most recent POWER9). I presume IBM is selling many systems that natively need that decimal-float functionality.
- goosehonk 7y agoDecimal floats are a lot older than COBOL. Many early relay computers (to the extent there were many such machines) used floating-point numbers with bi-quinary digits in the mantissa. https://en.wikipedia.org/wiki/Bi-quinary_coded_decimal https://en.wikipedia.org/wiki/Bi-quinary_coded_decimal
- deleted 7y ago[deleted]
- anchpop 7y agoMany languages have types for infinite-precision rational numbers, for example Rational in Haskell.
- throwaway2048 7y agoFloating point is fundamentally a trade off between enumerable numbers (precision) and range between minimum/maximum numbers, it exists because fast operations on numbers are not possible with arbitrary precision constructs (you can easily have CPU/GPU operations where floating point numbers fit in registers, arbitrary precision by its very nature is arbitrarily large). With many operations this trade off makes sense, however its critical to understand the limitations of the model.
- goosehonk 7y agoWhen did RFC1035 get thrown under the bus? According to it, with respect to domain name labels, "They must start with a letter" (2.3.1).
- jdnenej 7y agoAges ago I guess. 1password doesn't start with a letter either.
- knome 7y agoThe same document defines `in-addr.arpa` domains that have numeric labels. The mandate of a starting letter was for backwards compatibility, and mentions it in light of keeping names compatible with email servers and HOSTS files it was replacing. Taking a numeric label risks incompatibility with antiquated systems, but I doubt it will effect any modern browser.
- jlv2 7y agoLong, long ago. 3com.com wanted to exist.
- yellowapple 7y agoAmazingly, 3.com apparently didn't want to exist.
- jwilk 7y agoAll-digit host names have been allowed since 1989. https://tools.ietf.org/html/rfc1123#page-13 https://tools.ietf.org/html/rfc1123#page-13 One aspect of host name syntax is hereby changed: the restriction on the first character is relaxed to allow either a letter or a digit. Host software MUST support this more liberal syntax.
- goosehonk 7y agoHuh. Thanks! I really missed the memo there. I wonder why 1035 doesn’t mention that it is updated-by 1123.
- deleted 7y ago[deleted]
- alberth 7y agoTL;DR - 0.1 in Base 2 (binary) is the equivalent of 1/3 in Base 10 meaning, it’s a repeating decimal that causes rounding issues (0.333333 repeating) This is why you should never do “does X == 0.1” because it might not evaluate accurately
- _bxg1 7y agoI remember in college when we learned about this and I had the thought, "Why don't we just store the numerator and denominator?", and threw together a little C++ class complete with (then novel, to me) operator-overloads, which implemented the concept. I felt very proud of myself. Then years later I learned that it's a thing people actually use: https://en.wikipedia.org/wiki/Rational_data_type https://en.wikipedia.org/wiki/Rational_data_type
- miketuritzin 7y agoReminds me of this Inigo Quilez article on experimenting with rendering using rational numbers: https://iquilezles.org/www/articles/floatingbar/floatingbar.htm https://iquilezles.org/www/articles/floatingbar/floatingbar....
- _bxg1 7y agoI actually ran into a bug recently while implementing my first raytracer, where the point calculated from the sphere-intersect test would just occasionally end up inside the sphere due to floating point imprecision, so the diffuse sample rays would have their origins completely in the dark, leading to randomly black pixels. Solved it by bumping every intersection out by 0.01 in the direction of its normal. And then of course there have been several other "x.abs() < 0.01" cases for various purposes. So I could definitely see that being an interesting experiment.
- munchbunny 7y agoThat's really interesting - hadn't thought of that before. To fix that, would you be able to do a square of the magnitude comparison with the radius and just bump the borderline cases, or is it more efficient without the extra branching?
- _bxg1 7y agoI just did it across the board; since the error is in the floating-point noise I don't know if I'd even trust a comparison on that. Plus, the discrepancy between "bumped" and "unbumped" samples might cause some visible artifacts.
- mcv 7y agoThe big issue here is what you're going to use your numbers for. If you're going to do a lot of fast floating point operations for something like graphics or neural networks, these errors are fine. Speed is more important than exact accuracy. If you're handling money, or numbers representing some other real, important concern where accuracy matters, most likely any number you intend to show to the user as a number, floats are not what you need. Back when I started using Groovy, I was very pleased to discover that Groovy's default decimal number literal was translated to a BigDecimal rather than a float. For any sort of website, 9 times out of 10, that's what you need. I'd really appreciate it if Javascript had a native decimal number type like that.
- umanwizard 7y agoDecimal numbers are not conceptually any more or less exact than binary numbers. For example, you can't represent 1/3 exactly in decimal, just like you can't represent 1/5 exactly in binary. When handling money, we care about faithfully reproducing the human-centric quirks of decimal numbers, not "being more accurate". There's no reason in principle to regard a system that can't represent 1/3 as being fundamentally more accurate because it happens to be able to represent 1/5.
- DannyB2 7y agoThe real lesson is, no matter what base (radix) you use, floating point math is inexact. The value of floating point is that it can represent extremely huge or extremely infinitesimal values. If you're working with currency / money, floating point is the wrong thing to use. For the entire history of human civilization, currency has always been an integer type, possibly with a fixed decimal point. Money has always been integers for as long as commerce has existed, and long before computers. If you're building games, or AI, or navigating to Pluto, then floating point is the tool to use.
- seppel 7y ago> The real lesson is, no matter what base (radix) you use, floating point math is inexact. This is just not true. If you add 1.5 + 4.25 with IEEE754, there is nothing inexact or rounded. That you cannot exactly represent 0.1 in base2 FP is a problem of base2, not FP. You get inexact results with FP math for underflows, overflows, or if you don't have enough precision for the result (or an intermediate result). But the same is true for normal integer types.
- ufo 7y agoOne small tip about printf for floating point numbers. In addition to "%f", you can also print them using "%g". While the precision specifier in %f refers to digits after the decimal period, in %g the precision refers to the number of significant digits. The %g version is also allowed to use exponential notation, which often results in more pleasant-looking output than %f. printf("%.4g", 1.125e10) --> 1.125e+10 printf("%.4f", 1.125e10) --> 11250000000.0000
- kps 7y agoAnd %e always uses exponential notation. Then there's %a, which can be exact for binary floats.
- mytailorisrich 7y agoFixed-point calculations seem to be somewhat of a lost art these days. It used to be widespread because floating point processors were rare and any floating point computation was costly. That's not longer the case and everyone seems to immediately use floating point arithmetic without being fully aware of the limitations and/or without considering the precision needed.
- jonny_eh 7y ago> It's actually pretty simple The explanation then goes on to be very complex. e.g. "it can only express fractions that use a prime factor of the base". Please don't say things like this when explaining things to people, it makes them feel stupid if it doesn't click with the first explanation. I suggest instead "It's actually rather interesting".
- headmelted 7y agoDitto as I now feel stupid. I read the rest of your reply but I also haven’t let go of the possibility that we’re both (or precisely 100.000000001% of us collectively) are as thick as a stump.
- tomca32 7y agoThe problem is that almost everything is simple once you understand it. Once you understand something, you think it's pretty simple to explain it. On the other hand, people say "it's actually pretty simple" to encourage someone to listen to the explanation rather than to give up before they even heard anything, as we often do.
- Dylan16807 7y agoI understand prime factors just fine, but I'd never think it's "simple" to bring them up when I'm explaining how decimal points work.
- hyperpape 7y ago
- gowld 7y agoThis is a great shibboleth for identifying mature programmers who understand the complexity of computers, vs arrogant people who wonder aloud how systems developers and language designers could get such a "simple" thing wrong.
- hutzlibu 7y ago" vs arrogant people who wonder aloud how systems developers and language designers could get such a "simple" thing wrong." I never heard anyone complain that it would be simple to fix. But complaining? Yes - and rightfully so. Not every webprogrammer need to know the hw details and don't want to, so it is understandable that this causes irritation.
- lelf 7y agoThat’s only formatting. The other (and more important) matter, — that is not even mentioned, — is comparison. E. g. in “rational by default in this specific case” languages (Perl 6), > 0.1+0.2==0.3 True Or, APL (now they are floats there! But comparison is special) 0.1+0.2 0.3 ⎕PP←20 ⋄ 0.1+0.2 0.30000000000000004 (0.1+0.2) ≡ 0.3 1
- Athas 7y agoExactly what are the rules for the "special comparison" in APL? That sounds horrifying to me.
- lizmat 7y agoPlease note that Perl 6 has been renamed to "Raku" (https://raku.org https://raku.org using #rakulang as a tag for social media). In Raku, the comparison operator is basically a subroutine that uses multiple dispatch to select the correct candidate for handling comparisons between Rat's and other numeric objects.
- amyjess 7y agoOne of my favorite things about Perl 6 is that decimal-looking literals are stored as rationals. If you actually want a float, you have to use scientific notation. Edit: Oh wait, it's listed in the main article under Raku. Forgot about the name change.
- dunham 7y agoInteresting, I searched for "1.2-1.0" on google. The calculator comes up and it briefly flashes 0.19999999999999996 (and no calculator buttons) before changing to 0.2. This happens inconsistently on reload.
- threatofrain 7y agoUse Int types for programming logic.
- saagarjha 7y agoExcept when, you know, you can’t.
- umanwizard 7y agoCurious, when can't you? My mental model of floating-point types is that they are useful for scientific/numeric computations where values are sampled from a probability distribution and there is inherently noise, and not really useful for discrete/exact logic.
- saagarjha 7y agoRight; for the former integer arithmetic won't do.
- umanwizard 7y agoYep, absolutely (and increasingly often people are using 16-bit floats on GPUs to go even faster). But the person you replied to said programming logic, not programming anything. Honestly I think if you care about the difference between `<` and `<=`, or if you use `==` ever, it's a red flag that floating-point numbers might be the wrong data type.
- pmarreck 7y agoIEEE floating-point is disgusting. The non-determinism and illusion of accuracy is just wrong. I use integer or fixed-point decimal if at all possible. If the algorithm needs floats, I convert it to work with integer or fixed-point decimal instead. (Or if possible, I see the decimal point as a "rendering concern" and just do the math in integers and leave the view to put the decimal by whatever my selected precision is.)
- saagarjha 7y agoIEEE is deterministic and (IMO) quite well thought-out. What specifically do you not like about it?
- pmarreck 7y agoThe fact that the most trivial floating-point addition of 0.1 + 0.2 = 0.300000000000004 was insufficient to make this seem HUMAN-nondeterministic to you? (I mean sure, if you thoroughly understood the entire spec, you might not be surprised by this result, but many people would be! Otherwise the original post and website would not exist, no?) It’s kind of a hallmark of bad design when you have to go into a long-winded explanation of why even trivial use-case examples have “surprising” results.
- saagarjha 7y agoYou don't need to thoroughly understand the entire spec, nor do you need to know that 0.1 + 0.2 = 0.300000000000004. "Computers can't really represent floating point numbers exactly" is generally good enough. (Also: you added "human" as a qualifier; you didn't have that before so I responded to your statement as it was written.)
- jcranmer 7y ago⅓ to 3 decimal places is 0.333. 0.333 + 0.333 = 0.666, which is not ⅔ (to 3 decimal places, that is 0.667). That is all that is happening with the 0.1 + 0.2. The word you're looking for is "surprising," which is a far cry from non-deterministic. IEEE 754 is so thoroughly deterministic that there exists compiler flags whose sole purpose is to say "I don't care that my result is going to be off by a couple of bits from IEEE 754."
- ChuckMcM 7y agoThat is why I only used base 2310 for my floating point numbers :-). FWIW there are some really interesting decimal format floating point libraries out there (see http://speleotrove.com/decimal/ http://speleotrove.com/decimal/ and https://github.com/MARTIMM/Decimal https://github.com/MARTIMM/Decimal) and the early computers had decimal as a native type (https://en.wikipedia.org/wiki/Decimal_computer#Early_computers https://en.wikipedia.org/wiki/Decimal_computer#Early_compute...)
- ergfdseragf 7y agoThe multiplication of the first 5 primes ;)
- dang 7y agoA thread from 2017.00000000000: https://news.ycombinator.com/item?id=14018450 https://news.ycombinator.com/item?id=14018450 2015.000000000000: https://news.ycombinator.com/item?id=10558871 https://news.ycombinator.com/item?id=10558871
- umanwizard 7y agoFWIW, both of those can be expressed exactly by floating-point numbers ;)
- IshKebab 7y agoRight all integers up to 2^53 (or something like that) can be (in double precision). I assume that's the reason they made the mantissa linear, even though having the whole thing logarithmic makes more sense.
- Iburinoc 7y agoAddition/subtraction are also much simpler/cheaper than they would be in an entirely logarithmic model. If floats were just 2^x with some 64 bit fixed point x, it's not clear to me how to do addition efficiently.
- nine_k 7y agoWhat. Mantissa is already logarithmic, bit number n has value 2^(n - N-1) for an N-bit mantissa. This is how positional number systems work at all.
- IshKebab 7y agoThe mantissa is linear. It's unrelated to how positional number systems work. A floating point value is split into two numbers - the exponent and the mantissa. Normally they are used to represent a final number like: x = 2^e * (1 + m) Where e is the exponent and m is the mantissa (varying linearly from 0 to 1). But you could have a fully exponential number format: x = 2^(m + o) As pointed out though, it makes addition much more complicated, you can't exactly represent integers, and someone told me it makes quantisation noise worse too. Bad idea.
- YeGoblynQueenne 7y agoSwi-Prolog (listed int he article) also supports rationals: ?- A is rationalize(0.1 + 0.2), format('~50f~n', [A]). 0.30000000000000000000000000000000000000000000000000 A = 3 rdiv 10.
- tus88 7y agoMods: Can we have a top level menu option called "Floating point explained"?
- skohan 7y agoThis is part of the reason Swift Numerics is helping to make it much nicer to do numerical computing in Swift. https://swift.org/blog/numerics/ https://swift.org/blog/numerics/
- enriquto 7y agowhat is the number representation in swift? Looking at your link it seems to be plain ieee floats. In that case, would't it have the same behavior?
- mvelie 7y agoSwift also has decimal (so does objective-c) which handles this properly. See https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20161219/004220.html https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20... to see how swift's implementation of decimal differs from obj-c.
- bluetwo 7y agoHappy to see ColdFusion doing it right. Also, good for Julia for having the support for fractions.
- GuB-42 7y agoThat's one of the worst domain name ever. When the topic comes along, I always remember about "that single-serving website with a domain name that looks like a number" and then take a surprisingly long time searching for it. I have written a test framework and I am quite familiar with these problems, and comparing floating point numbers is a PITA. I had users complaining that 0.3 is not 0.3. The code managing these comparisons turned out to be more complex than expected. The idea is that values are represented as ranges, so, for example, the IEEE-754 "0.3" is represented as ]0.299~, 0.300~[ which makes it equal to a true 0.3, because 0.3 is within that range.
- LeanderK 7y agojust add 0.1 and 0.2 in fp32 (?) accuracy if you can't remember the name :)
- jacobolus 7y agoThis is the double-precision IEEE sum. A single-precision result would have (slightly less than) half as many digits.
- mynameisvlad 7y agoIt's the first result for "floating point site" on Google. Sure the domain itself is impossible to remember, but you don't have to remember the actual number, just what it stands for.
- usr1106 7y agoRemember filter bubble. My first result is not your first result. (although in this case it happens to be, but we both probably search a lot on programming)
- mynameisvlad 7y agoAlso did it in an InPrivate window to confirm, which is still somewhat targeted but far less so than on my actual account. It's still first. And, at the end of the day, even if there's a filter bubble and it's the reason I see it first, then so what? The people looking for this site are likely going to fit into the same set of targeted demographics as you and me and most people on this site. So unless you also want to cater to 65-year old retirees that don't care about computer science and what floating numbers are, then why does the filter bubble even matter?
- gumby 7y agoNot surprisingly Common Lisp gets it right. I don’t mean this is snark (I don’t mean to imply you are a weenie if you don’t use lisp) but just to show that it picked a different kind of region in the language design domain.
- 0xDEEPFAC 7y agoWhoo go Ada, one of the few to get it right. Must be the goto for secure programming for a reason. Take that Rust and C ; )
- deleted 7y ago[deleted]
- adamc 7y agoThose Babylonians were ahead of their time.
- DonHopkins 7y agoThe runner up for length is FORTRAN with: 0.300000000000000000000000000000000039 And the length (but not value) winner is GO with: 0.299999999999999988897769753748434595763683319091796875
- povik 7y agoIn the Go example, can someone explain the difference between the first and the last case?
- ehsankia 7y agoThere's a link right below. It seems like 1. Constants have arbitrary precision 2. When you assign them, they lose precision (example 2) 3. You can format at as a arbitrary precision in a string (example 3) In that last example, they are getting 54 significant digits in base 10.
- povik 7y agoThanks. What I didn’t realize is that although the sum is done precisely, the resulting 0.3 will be represented approximately once converted to float64. In the first case formatting hides that, in the last it doesn’t.
- ehsankia 7y agoI think in the last example, it's going straight from arbitrary precision to 54 significant digit, bypassing float64 entirely, hence why it looks different from the middle example.
- combatentropy 7y agoIn JavaScript, you could use a library like decimal.js. For simple situations, could you not just convert the final result to a precision of 15 or less? > 0.1 + 0.2; < 0.30000000000000004 > (0.1 + 0.2).toPrecision(15); < "0.300000000000000" From Wikipedia: "If a decimal string with at most 15 significant digits is converted to IEEE 754 double-precision representation, and then converted back to a decimal string with the same number of digits, the final result should match the original string." --- https://en.wikipedia.org/wiki/Double-precision_floating-point_format https://en.wikipedia.org/wiki/Double-precision_floating-poin...
- deleted 7y ago[deleted]
- okennedy 7y agoThis specific issue nearly drove me insane trying to debug a SQL -> C++/Scala/OCaml transpiler years ago. We were using the TPC-H benchmark as part of our test suite, and (unbeknownst to me), the validation parameters for one of the queries (Q6) triggered this behavior (0.6+0.1 != 0.7), but only in the C/Scala targets. OCaml (around which we had built most of our debugging infrastructure) handled the math correctly... Fun times.
- lordnacho 7y agoWhile it's true that floating point has its limitations, this stuff about not using it for money seems overblown to me. I've worked in finance for many years, and it really doesn't matter that much. There are de minimis clauses in contracts that basically say "forget about the fractions of a cent". Of course it might still trip up your position checking code, but that's easily fixed with a tiny tolerance.
- dionian 7y agowhen the fractions actually dont matter... its so painless just to just store everything in pennies rather than dollars (multiply everything by 100)
- wruza 7y agoIt’s not painless. E.g. dividing $100.00 by 12 month in integer cents requires 11 $8.33 and one $8.37 (or better 4x(2x8.33+8.34), depending on definition of ‘better’). You can forget this $0.04, but it will jump around in reports until you get rid of it – it requires someone’s attention anyway, no matter how small it is. Otoh, in unrounded floating point that will lead to a mismatch between (integer) payments and calculations. In rounded fp it’s the same problem, except when you’re trying very hard for error bits to accumulate (like cross-multiplying dataset sums with no intermediate rounding, which is nonsense in financial calc and where regular fixpoint integers will overflow anyway). What I’m trying to show here is that both integers and floating point are not suitable for doing ‘simple’ financial math. But we get used to this Bresenhamming in integers and do not perceive it as solving an error correction problem.
- Waterluvian 7y agoThis struck home with me when one day a friend and I bought the same thing and he paid a penny more. I realized something I didn't ever notice or appreciate in 20+ years: oh yeah, they can't just round off the penny in their favour every time. And the code that handles tracking when to charge someone an extra penny must be irritating to have developed and manage. All of a sudden you've got state.
- dspillett 7y agoMS Excel tries to be clever and disguise the most common places this is noticed. Give it =0.1+0.2-0.3 and it will see what you are trying to do and return 0. Give it anything slightly more complicated such as =(0.1+0.2-0.3) and this won't trip, in this example displaying 5.55112E-17 or similar.
- piadodjanho 7y agoAre you sure it is not showing the exact answer because the the the cell precision set to a single decimal digit?
- deleted 7y ago[deleted]
- zingmars 7y agoYup: https://i.imgur.com/VuawaE1.png https://i.imgur.com/VuawaE1.png, on Excel v1911 (Build 12228.20332).
- FabHK 7y agoKahan (architect of IEEE 754) has a nice rant on it: https://people.eecs.berkeley.edu/~wkahan/Mind1ess.pdf https://people.eecs.berkeley.edu/~wkahan/Mind1ess.pdf (and plenty of other rants...: https://people.eecs.berkeley.edu/~wkahan/ https://people.eecs.berkeley.edu/~wkahan/ )
- maxdamantus 7y agoI feel like it should really be emphasised that the reason this occurs is due to a mismatch between binary exponentiation and decimal exponentiation. 0.1 = 1 × 10^-1, but there is no integer significand s and integer exponent e such that 0.1 = s × 2^e. When this issue comes up, people seem to often talk about fixing it by using decimal floats or fixed-point numbers (using some 10^x divisor). If you change the base, you solve the problem of representing 0.1, but whatever base you choose, you're going to have unrepresentable rationals. Base 2 fails to represent 1/10 just as base 10 fails to represent 1/3. All you're doing by using something based around the number 10 is supporting numbers that we expect to be able to write on paper, not solving some fundamental issue of number representation. Also, binary-coded decimal is irrelevant. The thing you're wanting to change is which base is used, not how any integers are represented in memory.
- Akababa 7y agoIf you only use decimals in your application, it actually is a fix because you can store the numbers you care about in exact precision. Of course it's not really a fix if you're being pedantic but for a lot of simple UI stuff it's good enough.
- lopmotr 7y agoAgree. All of these floating point quirks are not actually problems if you think of them as being finite precision approximations to real numbers, not in any particular base. Just like physical measurements of continuous quantities. You wouldn't be surprised to find an error in the 15th significant figure of some measurement or attempt to compare them for equality or whatever. So don't do it with floating point numbers either and everything will work perfectly. Yes, there are some exceptions where you can reliably compare equality or get exact decimal values or whatever, but those are kind of hacks that you can only take advantage of by breaking the abstraction.
- idonotknowwhy 7y agoThis has been posted here many times before. It even got mocked on n-gate in 2017 http://n-gate.com/hackernews/2017/04/07/ http://n-gate.com/hackernews/2017/04/07/
- cellular 7y agoWhy is D different than the rest?!
- thanatropism 7y agoComputer languages should default to fixed precision decimals and offer floats with special syntax (eg “0.1f32”). The status quo is that even Excel defaults to floats and wrong calculations with dollars and cents are widespread.
- Waterluvian 7y agoThe thing that surprised me the most (because I never learned any of this in school) was not just the lack of precision to represent some numbers, but that precision falls off a cliff for very large numbers.
- xkriva11 7y agofor Smalltalk, the list is not complete, it has scalled decimals and fractions too: 0.1s + 0.2s = 0.3s . (1/10) + (2/10) = (3/10)
- deleted 7y ago[deleted]
- garyclarke27 7y agoPostgresql figured this out many years ago with their Decimal/Numeric type. It can handle any size number and it performs fractional arithmetic perfectly accurately - how amazingly for the 21st Century! Is comically tragic to me that all of the mainstream programming languages are still so far behind, so primitive that they do not have a native accurate number type that can handle fractions.
- josefx 7y ago> how amazingly for the 21st Century! Most languages have classes for that, some had them for decades in fact. Hardware floating point numbers target performance and most likely beat any of those classes by orders of magnitude.
- qwerty456127 7y agoAs soon as I've started developing real-life business apps I've started to dream about a POWER which is said to have hardware decimal type support. Javs's BigDecimal solves the problem on x86 but it is at least an order of magnitude more slow than FPU-accelerated types.
- ernst_klim 7y agoWell, if your decimals are fixed-point decimals, which is the case in finance, decimal calculations are very cheap integer calculations (with simple additional scaling in multiplication/division). I just use Zarith (bignum library) in OCaml for decimal calculation, and pretty content with performance. I don't think much domains needs decimal floating point that much, honestly, at least in finance and scientific calculations. But I could be wrong, and would be interested in cases where decimal floating-point calculations are preferable over these done in decimal fixed-point or IEEE floating-point ones.
- qwerty456127 7y agoWhy doesn't everybody do it this way then? We would probably have a transparent built-in decimal type in every major language by now if there were no problems with this.
- ernst_klim 7y ago> Why doesn't everybody do it this way then? Why? Fintech uses decimal fixed-point all the way, there are libraries for them for any major language. Apps like GnuCash or ledger use them as well.
- qwerty456127 7y agoBut Java has BigDecimal in its standard library and it's soooo slow I doubt it is implemented this way.
- beckerdo 7y agoPlease check some of the online papers on Posit numbers and Unum computing, especially by John Gustafson. In general, Unums can represent more numbers, with less rounding, and fewer exceptions than floating points. Many software and hardware vendors are starting to do interesting work with Posits.
- StefanKarpinski 7y agoProbably one of the more in depth technical discussions of the pros and cons of the various proposals that John Gustafson has made over the years: https://discourse.julialang.org/t/posits-a-new-approach-could-sink-floating-point-computation/26176 https://discourse.julialang.org/t/posits-a-new-approach-coul...
- dec0dedab0de 7y agoI wish high level languages (specifically python) would default to using decimal, and only use a float when cast specifically. From what I understand that would make things slower, but as a higher level language you're already making the trade of running things slower to be easier to understand. That said, it's one of my favorite trivia gotchas.
- mttpgn 7y agobc actually computes this correctly, and returns 0.3 for 0.1 + 0.2
- cogburnd02 7y agoI love how Awk, bc, and dc all DTRT. I wonder what postscript(/Ghostscript?) does.
- edisonjoao 7y agolol what