[HN Gopher] Why does 0.1 and 0.2 = 0.30000000000000004?
       ___________________________________________________________________
        
       Why does 0.1 and 0.2 = 0.30000000000000004?
        
       Author : soheilpro
       Score  : 210 points
       Date   : 2023-02-08 14:50 UTC (8 hours ago)
        
 (HTM) web link (jvns.ca)
 (TXT) w3m dump (jvns.ca)
        
       | rafaelturk wrote:
       | For everyone working on tech... When your friends mock you, and
       | make funny comments how tech is hard and often unpleasant to use
       | this a perfect example. This thread have multiple `techy`
       | examples, how binary numbers works, etc. Thurth is the year is
       | 2023 we have CPUs with billions of transistors and we still have
       | fundamentaly basic bugs like math. I really don't understand why
       | languages dont default to decimals for literals..
        
         | zokier wrote:
         | All number formats have tradeoffs, nothing can robustly
         | represent all real numbers. At least floats are standardized
         | and their probmems relatively well characterized and
         | understood.
        
         | ClumsyPilot wrote:
         | because some developers hate anything that makes thongs easier
        
       | worik wrote:
       | Computers, as we use them, to operations on finite sets.
       | 
       | Real numbers are simulated using a finite set of bits.
       | 
       | So equality comparisons are not useful for floating point numbers
       | 
       | This is computing 101
        
       | raldi wrote:
       | The entire intrigue of the opening paragraph is the claim:
       | 
       | > there's a floating point number that's closer to 0.3 than
       | 0.30000000000000004!
       | 
       | But then you read to the end and find out this wasn't true, and
       | the answer to the clickbait headline is just, "Because it's the
       | closest floating-point value to the correct answer".
        
         | topaz0 wrote:
         | You're mistaken. In fact 0.3 is closer to the next-smaller
         | float than it is to the next-larger one which results. But the
         | result of float(0.1) + float(0.2) is not any of those things,
         | but rather halfway between the previous and next floats from
         | 0.3, so it gets rounded up instead of down.
        
       | dgudkov wrote:
       | Both Excel and Google Sheets return FALSE for this expression:
       | 2.03 - 2 - 0.03 = 0
       | 
       | The vast majority of data transformation and BI tools (Power BI,
       | PowerQuery, Tableau, etc.) return FALSE for this expression:
       | 0.1 + 0.2 = 0.3
       | 
       | That's because they use floats instead of decimals and that
       | introduces subtle errors in data. These errors usually never get
       | noticed because everyone doesn't expect errors in basic math.
       | It's a mystery to me why most commercial software intended for
       | business and financial calculations don't use fixed point
       | decimals. My post about this:
       | https://www.linkedin.com/feed/update/urn:li:activity:7028101...
       | 
       | PS. If you design software that works with money amounts, always
       | use fixed point decimals. Don't use floats, it's just wrong!
        
         | t8sr wrote:
         | The really surprising* thing is that Google Sheets uses floats
         | for everything. Back in the day, I was using Sheets to do some
         | statistics about (IIRC) kernel ASLR on macOS, and I was
         | surprised to see kernel pointers ending in impossible digits.
         | Of course only after I'd wasted 2 hours on it.
         | 
         | Boy, did I file a pissy bug with the Sheets team that day, and
         | then requested an Excel install I never let go of since that
         | day.
         | 
         | * I guess it's maybe not surprising to js developers, but don't
         | most modern browsers have integers by now?
        
           | ElectricalUnion wrote:
           | > Google Sheets uses floats for everything (...) and then
           | requested an Excel install
           | 
           | But Excel also uses floats (as in, Single-precision floating-
           | point format, aka float32), because of "compatibility with
           | Lotus 1-2-3".
           | 
           | https://softwarerecs.stackexchange.com/questions/53292/any-s.
           | ..
           | 
           | https://learn.microsoft.com/en-
           | us/office/troubleshoot/excel/...
        
           | altruios wrote:
           | besides bigInt...
           | 
           | What numbers in javascript are not actually doubles (what js
           | uses under the hood for every number)?
           | 
           | You can convert them from doubles to 32-ints apparently by
           | some bitwise hacks like (I'm pretty sure they still are
           | 'doubles' though - and it just does some rounding tricks)
           | 
           | ``` |0 //signed >>>0 //unsigned ```
           | 
           | So any webapp (google sheets) will likely have problems
           | related to floating point math.
        
             | ElectricalUnion wrote:
             | > You can convert them from doubles to 32-ints
             | 
             | You can use the native BigInt arbitrary-precision integers
             | and ignore all "fits under this arbitrary limited bit
             | slice" problems.
             | 
             | You can use doubles as a integer directly, in a safe way,
             | until you touch the 53 bits barrier
             | (Number.MIN_SAFE_INTEGER === -9007199254740991 or
             | Number.MAX_SAFE_INTEGER === 9007199254740991).
             | 
             | None of those really are (modern) webapp issues.
        
           | jkaptur wrote:
           | How do Sheets and Excel differ in this regard? How did using
           | floats cause some number to be odd?
        
         | hinkley wrote:
         | I learned about binary coded decimal in school and it was the
         | weirdest thing but is pretty good for money.
        
         | SnowHill9902 wrote:
         | The equality operator should not be implemented for the float
         | type to being with. Money amounts should use integer.
        
           | svachalek wrote:
           | A true decimal type is better than integer, if your language
           | supports it.
        
             | Salgat wrote:
             | That's assuming it's implemented as a fixed-type, while my
             | understanding is most follow the IEEE 754 standard that
             | uses floating-point types.
        
             | t8sr wrote:
             | Decimals are notoriously hairy to implement and it's not
             | obvious how they should behave when they run out of
             | precision. Integers are almost always the better choice
             | when the decimal is fixed, such as with currency.
             | 
             | (I guess it depends on what you mean by "true" decimal. If
             | you meant BigNum, then sure.)
        
               | tremon wrote:
               | _Decimals are notoriously hairy to implement_
               | 
               | Only floating-point decimal is hairy. Fixed-point decimal
               | is hardly more difficult than integer math.
        
               | t8sr wrote:
               | If you simply look at any of the real world
               | implementations, you'll see it's not so easy. Rust has a
               | few readable and educational crates that implement
               | decimals, if you want to take a look.
        
               | ElectricalUnion wrote:
               | > Decimals are notoriously hairy to implement
               | 
               | That's because the binary is implemented in hardware for
               | you; and those days you can usually (but not always)
               | trust the hardware to do the correct thing; if you have
               | to implement it youself (say, deterministic, side-effect
               | free math), it's also hairy.
        
               | int_19h wrote:
               | Decimal floating point is fine so long as you constrain
               | the exponent such that delta between two consecutive
               | valid numbers can never be greater than 1 (that is, there
               | should never be any implicit trailing zeroes). .NET
               | Decimal type is a good example.
        
         | ezfe wrote:
         | Numbers (from Apple) returns TRUE for both expressions you list
        
           | Kon-Peki wrote:
           | Objective-C has a really nice decimal math library. Last I
           | looked (it's been a while), Swift didn't. It might have one
           | by now.
           | 
           | Years ago, I had a few apps in the App Store that made
           | extensive use of it. It really was very nice to work with.
        
             | arcticbull wrote:
             | Swift has the same functionality in the Decimal type.
        
               | Kon-Peki wrote:
               | Thanks! It looks like that was added in Swift 3. That was
               | a long time ago. I guess I'm just really old ;)
        
           | sheetjs wrote:
           | Under the hood, modern Numbers stores values in Decimal128
           | (16 bytes)
        
             | [deleted]
        
         | gorgoiler wrote:
         | Paging Colin Percival to talk about picodollars. The medium
         | strength advice on this topic is to use integer math with cents
         | as your unit. Colin's advice is to choose the smallest possible
         | unit you can that will avoid overflow (I think) hence why he
         | prices tarsnap in picodollars: https://www.tarsnap.com/picoUSD-
         | why.html
        
         | roxgib wrote:
         | Even if you set the data format to 'currency' it still returns
         | false (in Google Sheets). I realise they probably want
         | consistency but it's weird they don't have an option to use a
         | decimal type.
        
           | codetrotter wrote:
           | It is my impression of most spreadsheet software that the
           | "data format" is more about display, and not about actually
           | strongly typed representations of the data.
        
         | vba616 wrote:
         | > If you design software that works with money amounts, always
         | use fixed point decimals. Don't use floats, it's just wrong!
         | 
         | I find it funny the gap between what computer people think
         | finance requires and actual practice.
         | 
         | The tax people in the US generally aren't interested in pennies
         | any more! And when you use tax software that throws away the
         | pennies _before_ the final results, then your sums may very
         | well not match the forms whose information is independently
         | reported to the IRS, by _well_ over a dollar. But nobody cares!
        
           | sowbug wrote:
           | No need to invent a divide between "computer people" and "tax
           | people," whoever they are. Maybe the IRS allows small errors
           | because bugs attributable to floating-point precision are too
           | hard to fix.
        
             | jdmichal wrote:
             | AFAIK the IRS has always been accepting of truncating /
             | rounding values to full dollars and discarding cents. It's
             | certainly been that way for the decades that I've been
             | doing taxes.
        
         | Kon-Peki wrote:
         | > If you design software that works with money amounts, always
         | use fixed point decimals. Don't use floats, it's just wrong!
         | 
         | Don't write with such certainty! Decimal math is great advice
         | for many/most situations, but what if you have a LOT of numbers
         | and not a lot of time? That big number crunching GPU is not
         | available if you take this approach.
         | 
         | Numerical Methods was the most difficult CS course I took in
         | university and also the one I did the worst in. And of course
         | it was an elective, or else they wouldn't have graduated many
         | people at all. If you're doing a lot of number crunching stuff,
         | maybe you should ask people that know how to number crunch to
         | design your system so it has the smallest errors :)
         | 
         | PS - I'm not the person for that job!
        
           | int_19h wrote:
           | We should be optimizing for correctness over performance by
           | default, though. People who need binary floating point for
           | perf reasons should already know the tradeoffs.
        
           | t8sr wrote:
           | I am almost completely certain that neither Google Sheets,
           | nor Excel use that "big number crunching GPU" for anything
           | float-related.
           | 
           | Just getting the data to the GPU for a compute shader to run
           | on it would take longer than just doing on the CPU in almost
           | every case.
        
         | CamperBob2 wrote:
         | _PS. If you design software that works with money amounts,
         | always use fixed point decimals. Don 't use floats, it's just
         | wrong! _
         | 
         | Eh, doubles are fine for (most) currencies. Just don't do
         | comparisons without appropriate epsilons.
         | 
         | People who compoare floating-point numbers for equality are
         | going to make other fundamental mistakes with whatever data
         | type you force them to work with.
        
           | radu_floricica wrote:
           | I thought that until literally two days ago. Turns out that
           | if you sum a bunch of 0.37 (not even huge numbers, just
           | around a few thousand) you end up with differences on the
           | order of 10-20. Both in mysql and in Java. No, this doesn't
           | makes sense to me either - the differences should be a LOT
           | farther than 3 digits. And yet.
           | 
           | You should have seen my face when debugging this.
        
             | zeven7 wrote:
             | I'm curious about this. Could you provide an example?
             | 
             | Here's what I'm seeing:
             | 
             | ---
             | 
             | JavaScript                   (function() {           let
             | inc = 0.37;           let times = 5000;           let total
             | = 0;           for (let i = 0; i < 5000; i++) {
             | total += inc;           }
             | console.log('expected:', inc \* times);
             | console.log('actual:', total);         })();
             | 
             | expected: 1850
             | 
             | actual: 1849.9999999997679
             | 
             | ---
             | 
             | Java                   public class MyClass {
             | public static void main(String args[]) {
             | double inc = 0.37;                 double times = 5000;
             | double total = 0;                 for (double i = 0; i <
             | 5000; i++) {                     total += inc;
             | }                 System.out.println("expected: " + (inc \*
             | times));                 System.out.println("actual: " +
             | total);             }         }
             | 
             | expected: 1850.0
             | 
             | actual: 1849.9999999997679
             | 
             | ---
             | 
             | update: Changing `double` to `float` in Java yields:
             | 
             | expected: 1850.0
             | 
             | actual: 1849.9778
             | 
             | and maybe that lines up with what you meant by "the
             | differences should be a LOT farther than 3 digits", though
             | it's hard to tell what you mean by "differences on the
             | order of 10-20".
        
               | HNDen21 wrote:
               | same in SQL Server                  declare @i float
               | =0.37,@times int = 0,@total float = 0             while
               | @times < 5000        begin        set  @total+=  @i
               | set @times +=1        end             select @total
               | 
               | 1849.99999999977
               | 
               | or                  select sum(t) from(        select top
               | 5000 convert(float, 0.37) t         from sysobjects a
               | cross join sysobjects b) z
               | 
               | 1849.99999999977
        
           | worik wrote:
           | Yes
           | 
           | But actually, since fixed point decimals are not all ways
           | available use the smallest unit of account, not smallest
           | legal tender, and integer arithmetic
           | 
           | And learn how to round
        
             | adamwk wrote:
             | Smallest unit also has issues. For instance the Indonesian
             | rupiah technically is made up of 100 sen, but the currency
             | is so inflated nobody uses it and currency libraries behave
             | differently (even different versions of the same library).
             | We had a bug where different OS versions provided different
             | values when normalizing it to a smallest unit integer.
             | 
             | If you really don't have access to a decimal type I think
             | the best solution is to convert it to micro-units (price *
             | 10^6). This is what Android does in its billing library.
        
           | wyager wrote:
           | Currency quantities are inherently exact. If you're doing
           | epsilon comparisons on currency quantities, you are making a
           | fundamental ontological error.
        
             | CamperBob2 wrote:
             | Nobody gives a hoot about 0.000001 cents. Round to the
             | nearest 1/100 cent after adding or subtracting doubles, and
             | you will be fine in 99.99999% of applications.
             | 
             | The cardinal sin isn't using doubles for currency; it's
             | using them without understanding either the tool or the job
             | that you're asking the tool to perform.
        
           | stevoski wrote:
           | Somebody is wrong on the Internet today. Very wrong.
        
             | Kon-Peki wrote:
             | If I want to know the monthly payment on a $500,000
             | mortgage for 30 years at 6%, should I use:
             | 
             | 1. Decimal math
             | 
             | 2. Binary floating point math
             | 
             | 3. Domain knowledge trumps all; it makes no difference
        
               | gibspaulding wrote:
               | Then of course you'll have to work back to compute an APR
               | for that by a crazy iterative formula outlined by the US
               | gov. Which would you use for that process?
        
       | [deleted]
        
       | tarotuser wrote:
       | Because .3 cannot be described by a float exactly.
       | 
       | And C/C++ doesn't have binary coded decimal natively. If it did,
       | then much of what we use decimals and fractions for would be
       | easier to show in decimal.
        
         | peteri wrote:
         | I'm sure multiple variants have been implemented, Borland C++
         | V2.0 from 1991 (version chosen as it's on bitsavers.org) has a
         | bcd type which IIRC uses the 80 bit 8087 packed decimal type
         | but it's been a long time since I looked at this deeply.
        
           | virgulino wrote:
           | I remember we had Borland Turbo Pascal 3.0 with BCD in the
           | mid 1980s.
        
       | adunk wrote:
       | I really like this quote from the article as way of explaining
       | this whole perceived anomaly:
       | 
       | > To me,
       | 0.1000000000000000055511151231257827021181583404541015625 +
       | 0.200000000000000011102230246251565404236316680908203125 =
       | 0.3000000000000000444089209850062616169452667236328125 feels less
       | surprising than 0.1 + 0.2 = 0.30000000000000004.
        
         | lifthrasiir wrote:
         | And in my opinion, this is why `0.1 + 0.2 =
         | 0.30000000000000004` is a bad meme. It cements a very wrong
         | perception about floating point numbers. If we denote a
         | rounding operation as `f64(...)`, this is `f64(f64(0.1) +
         | f64(0.2)) = f64(0.30000000000000004) != f64(0.1 + 0.2)` which
         | can obviously happen. In particular, `f64(0.1) != 0.1` etc. but
         | we happened to choose 0.1 as a _representative_ for `f64(0.1)`
         | for various reasons. Nothing inaccurate, nothing meme-worthy,
         | just implied operations.
        
           | hgsgm wrote:
           | The meme worthy thing is thinking that, given 3 sigfig
           | inputs, 20 sigfigs is more desirable than 3. It's failing to
           | distinguish noise from data.
        
           | crdrost wrote:
           | Yes, this is the best way to explain it. Your number literals
           | are "snapping to a grid" that is not base-10 and then we
           | choose the shortest base-10 decimal that snaps to the
           | appropriate grid point when we stringify the number.
           | 
           | The other thing that I would mention is that I see some
           | really gnarly workarounds to try to get around this... Just
           | bump up to integers for a second! People have this mistaken
           | idea that the best way to understand these rounding "errors"
           | is that floating point is just unpredictably noisy for
           | everything, and that's not true.
           | 
           | Floating point has an exact representation of all integers up
           | to 2^53 - 1. If you are dealing with dollars and cents that
           | clients are getting billed or whatever, okay, the best thing
           | to do is to have a decimal library. But if you don't have a
           | decimal library and it's just some in-game currency that you
           | don't want to get these gnarly decimals on, 3/10 will always
           | give 0.3. 4/100 will always give 0.04. Just use the fact that
           | the integer arithmetic is exact: multiply by the base, round
           | to nearest integer, do your math, and then divide out the
           | base in the end: and you'll be good.
        
             | ClumsyPilot wrote:
             | > If you are dealing with dollars and cents that clients
             | are getting billed or whatever, okay, the best thing to do
             | is to have a decimal library.
             | 
             | C# has decimal in the base library. We are doing a new
             | project with financial data, and decided we willvhave
             | everything in decimal - no floats at all.
             | 
             | There is no point of dealing with these issues to save
             | irrelevant amount of CPU
        
             | toast0 wrote:
             | A reasonable, but not always available, choice is to use
             | integer quantities of the divided quantity. If you're
             | dollars and cents, express things in cents. If you need
             | tenths of a cent, express things in milliDollars. If you
             | need 1/8th dollars, use those. Have a conversion to pretty
             | values when displayed.
             | 
             | Sometimes you really do need to have a pretty good estimate
             | of pi dollars, but often not.
        
             | int_19h wrote:
             | And it's very bad UX that when you write "0.1" in the code
             | or feed it to the standard string parser at runtime, what
             | you get back is not actually 0.1. It's effectively silent
             | data corruption. If you want "snapping to a grid" for perf
             | reasons, it should be opt-in, not opt-out.
        
         | zokier wrote:
         | I wonder how much confusion could have been avoided if
         | compilers/interpreters emitted warnings for inexact float
         | literals. It is bit surpirising pitfall, you generally expect
         | the value of a literal to be obvious, but with floats its
         | almost unpredictable. Similarly functions like strtod/atof
         | could have some flags/return values indicating/preventing
         | inexact conversions. Instead we ended up on this weird
         | situation where values are quietly converted to something that
         | is close to the desired value
        
           | ordu wrote:
           | _> you generally expect the value of a literal to be obvious,
           | but with floats its almost unpredictable_
           | 
           | Fractions in positional notations are not exact as a rule.
           | There are some exceptions, but mostly they are not exact.
           | 1/3, 1/6, 1/7, 1/9 cannot be represented by decimals exactly
           | (or they can, but using infinite amount of digits in their
           | representation). There are exceptions of course, for example
           | for decimals you need denominator with no prime factors
           | except 2 and 5. For binary it can be only 2.
        
           | hgsgm wrote:
           | Why bother? Almost every float literal is inexact. floats are
           | inexact by design. That's why you can fit over 2^(2^53)
           | values into 64 bits.
           | 
           | And compiler can't help you on the application UI layer.
        
       | insane_dreamer wrote:
       | an aside (to Julia's always excellent explanations) - it sure
       | would be nice if python had first class support for decimals :/
        
       | whitten wrote:
       | This result is strictly a result of floating point math.
       | 
       | You never get this kind of result with fixed point or binary
       | coded decimal math.
       | 
       | The computer Language M never has this kind of problem.
        
         | edflsafoiewq wrote:
         | Fixed point behaves well under addition and multiplication by
         | integers. Otherwise it isn't very good. There are far more
         | numbers in the range (1,infinity) than (0,1) for example, so
         | 1/x loses catastrophic amounts of information.
        
         | dragonwriter wrote:
         | > This result is strictly a result of floating point math.
         | 
         | This _specific_ result is a result of _binary_ floating point
         | math with a particular precision. More precision, or decimal
         | floating point, will fix that, but have similar kinds of errors
         | for the same operation on different numbers. fixed-size BCD
         | /fixed-point math has other limitations, its not a general
         | solution.
         | 
         | The _general_ solution is to have a numeric tower where
         | representations and operations meet the following rules:
         | 
         | 1. The default representation of any exact literal (without a
         | modifier representing a particular inexact representation,
         | e.g., as an optimization or a necessity of interfacing with an
         | external library) is exact,
         | 
         | 2. Any operation on any operations between exact
         | representations that can be done exactly is unless specified
         | otherwise (as it might be for the same reasons discussed
         | above), and stored in a representation that can represent the
         | result exactly.
         | 
         | 3. Essentially inexact operations or operations on inexact
         | numbers are conducted in a way and produce output
         | representations that minimize _additional_ imprecision
         | introduced, except when explicitly specified otherwise.
         | 
         | Computer algebra systems where the "top level representation is
         | symbolic are potentially the ultimate expression of this, but
         | the Scheme numeric tower is pretty good (but at least Racket,
         | and I think schemes in general, represent exact decimal
         | fractions as floats still, so don't entirely avoid the problem,
         | but at least division of integers produces exact rationals.)
         | Lots of languages default to putting numbers expressed as
         | literals into either fixed-sized integers (not _bad_ ,
         | especially when those are often 64-bit now which rarely has
         | much practical distinction from arbitrary precision in most
         | applications) or fixed-sized binary floats (which are more
         | problematic, especially given the mismatch between clean binary
         | and clean decimal representations.) This is _very good_ for
         | efficiency, because computers can process fixed sized integers
         | and binary floats very quickly. But, especially for floats, it
         | can be bad for _correctness_ when doing arithmetic where the
         | input is all clean decimal literals.
        
           | aag wrote:
           | Yes, Scheme has the concept of exactness[0]. In most Schemes,
           | (exact? 2.3) ==> #f. But at least the exact? procedure
           | exists. It can tell you whether your result might have been
           | affected by these problems. If you want perfect answers, you
           | can always use the rationals that are built into Scheme
           | implementations with the full numeric tower.
           | 
           | [0] https://standards.scheme.org/corrected-r7rs/r7rs-
           | Z-H-8.html#...
        
             | anthk wrote:
             | (inexact->exact x) and (exact->inexact x).
        
         | lifthrasiir wrote:
         | So 1/3 + 1/3 + 1/3 = 1 in M, right?
        
           | dragonwriter wrote:
           | Well, it is in racket?                 Welcome to Racket v8.7
           | [cs].       > (/ 1 3)       1/3       > (+ (/ 1 3) (/ 1 3) (/
           | 1 3))       1       > (integer? (+ (/ 1 3) (/ 1 3) (/ 1 3)))
           | #t
        
         | dragonwriter wrote:
         | Tangentially (and separate from my other response because it's
         | a whole different issue):
         | 
         | > The computer Language M
         | 
         | Which one? MUMPS (also known as M), or the Power Query Formula
         | Language (also known as M)? OR something else?
        
           | whitten wrote:
           | M alternately named MUMPS
           | 
           | M guarantees over 15 digits of precision, which is why it is
           | heavily used in banking and financial applications
        
         | compiler-guy wrote:
         | The article isn't about the imprecision in general, but rather
         | about the details of this particular equations imprecision, and
         | how that leads to this particular answer.
        
         | PaulHoule wrote:
         | It is not "floating point math" but "floating point math where
         | the exponent is expressed as a power of 2" that is the problem.
         | 
         | That is, if the exponent is a power of 2 you can write 1/2,
         | 1/4, 1/8 exactly but you can't write 1/3, 1/5, 1/10, etc.
         | 
         | If the exponent is base 10 then you can write 1/5, 1/10, 1/1000
         | and such exactly.
         | 
         | Note the mantissa and the exponent are both integers and so far
         | as this problem is concerned it does not matter if these are
         | written in binary or BCD or some other representation. There is
         | some controversy about what is better, if you use a binary
         | mantissa the math is a little faster and more accurate, if you
         | use a decimal mantissa conversions to and from ASCII are
         | quicker and ASCII conversions are a major part of real life
         | math workloads.
         | 
         | I've long thought that this problem is one of a list of
         | problems that many people encounter on the path to bending
         | computers to their will and that some people decide that
         | computer programming isn't for them because of this kind of
         | problem. I think the kind of person who learns Python to put
         | their outside-of-computing skills on wheels is particularly
         | affected.
         | 
         | It is a "disruptive technology" problem because the person who
         | is using IEEE floats heavily has accommodated to this problem
         | and would not give up the slightest amount of performance.
         | Decimal FP can be implemented in software but is slow in
         | software. IBM has had hardware Decimal FP in their mainframes
         | for a very long time and there is even an IEEE standard. (A
         | company that has been using mainframes for a long time cut a
         | check for the wrong amount because the abused the number system
         | back in 1963 and thus learned their lesson a long time ago.)
         | 
         | The best hope I have is that the "social justice" people can be
         | led to believe that unintuitive numerics keep underrepresented
         | people out of the field and that they threaten Intel that
         | they'll tear down their headquarters unless they catch up to
         | where mainframes were 50 years ago. It could be a huge win for
         | the industry and for the DEI office because employers would
         | have to buy everyone a new computer and even white guys might
         | think the DEI office was doing good work if it meant they got
         | to replace their 5 year old corporate craptop. I mean, how is
         | it that a few people with two fingers get to oppress all the
         | rest of us with ten?
        
         | pwdisswordfishc wrote:
         | How do you represent 1/10 exactly in binary fixed-point
         | representation?
        
           | tsukikage wrote:
           | ...or, indeed, 1/3 as binary coded decimal.
        
           | amelius wrote:
           | > How do you represent 1/10 exactly in binary fixed-point
           | representation?
           | 
           | The base of your number system does not need to be the same
           | as the base associated with the fixed point position.
           | 
           | Easiest to explain: if you have a uint64 that represents a
           | monetary value, you can let it express the number of
           | dollarcents instead of dollars. Then you can express 1/10
           | dollar as 10 dollarcents.
        
             | jstanley wrote:
             | The claim was "you never get this kind of result with fixed
             | point math", not "you can contrive to avoid this kind of
             | result with fixed point math if you use a different base
             | for the fractional part".
        
           | whitten wrote:
           | 1/10 is exactly 0.1
        
       | evdubs wrote:
       | It doesn't have to                   $ racket         Welcome to
       | Racket         > (read-decimal-as-inexact #f)         > (+ 0.1
       | 0.2)         3/10
       | 
       | Lovely.
        
       | shagie wrote:
       | https://0.30000000000000004.com is a fun site.
        
       | dec0dedab0de wrote:
       | I've said this before, but I wish python and other high level
       | languages defaulted to decimal for the literals, and made float
       | something you had to do explicitly. My reasoning behind this is
       | that floating point math is almost always an implementation
       | detail, instead of what you're actually trying to do. Sure,
       | decimal would be slower, but forcing people to use float as an
       | optimization would remind them to mitigate the risks with
       | rounding or whatever.
        
         | fnordpiglet wrote:
         | Do processors accelerate decimal/fixed point? I know some have
         | offered this in the past but I'm not current on instruction
         | sets for accelerated maths. My guess is a lot more energy goes
         | into floating point and integer.
        
           | blibble wrote:
           | fixed point arithmetic is just integer arithmetic
           | 
           | (with a multiplication to enter and a division to exit)
        
           | dahfizz wrote:
           | Not at far as I know. That would require everyone to agree on
           | one binary representation, which hasn't happened. There are
           | tons of different fixed-point implementations out there, each
           | with different tradeoffs. Choosing one implementation and
           | getting all languages to implement it (so that CPU makers
           | would bother accelerating it) would be a herculean task, IMO.
        
             | labcomputer wrote:
             | IEEE 754 actually does specify a decimal floating point
             | format since 2008, but I don't think it's widely
             | implemented.
        
               | dahfizz wrote:
               | Do you have any more info on this? The text of IEEE 754
               | costs $100 and I can't find any reference to it on Google
        
               | jabl wrote:
               | IEEE 754-2008 combined the IEEE 754 with the IEEE 854
               | decimal float standard, so hence any post-2008 IEEE 754
               | version also contains decimal float (and IEEE 854 has
               | been withdrawn).
               | 
               | But like the parent poster noted, hardware tends to not
               | actually implement the decimal float parts (I mean, IEEE
               | 754 doesn't care about how calculations are made or how
               | fast they are, so a software emulation is perfectly
               | acceptable from the standard perspective). I think IBM
               | POWER has one of the rare HW implementations of decimal
               | floats.
        
               | dahfizz wrote:
               | Thank you! With the hint of IEEE 854 I was able to find
               | https://en.wikipedia.org/wiki/Decimal64_floating-
               | point_forma...
        
               | kazinator wrote:
               | I'm amazed you didn't write: "The text of IEEE 754 costs
               | $100.00000000000003 and I can't find any reference to it
               | on Google."
               | 
               | :)
        
           | gpderetta wrote:
           | Some IBM POWER and mainframe microarchitectures have hardware
           | support for decimal floats.
           | 
           | Acceleration for binary fixed precision was (still is I
           | guess) common in DSPs. Not decimal fixed though.
           | 
           | I lost track of which extensions Intel provides, but I
           | wouldn't be surprised if something was available.
        
           | dev_hugepages wrote:
           | I don't think they do, at least on x86-64 and arm
        
         | im3w1l wrote:
         | I just had a horrible idea. Decimal is commonly used with fixed
         | point (no speed penalty), whereas binary is commonly used with
         | floating point.
         | 
         | But what if... what if they had a bastard child? What if we
         | moved the point a fixed distance in decimal... and also a
         | floating distance in binary?
         | 
         | The value represented would then be sign * mantissa *
         | 2^exponent * 10^bias
         | 
         | With a bias of -6, you could represent every multiple of
         | 0.000001 up to 9 billion if I did the math correctly.
        
         | toolslive wrote:
         | COBOL has it.
         | 
         | https://en.wikipedia.org/wiki/COBOL#PICTURE_clause
        
           | MayaFey wrote:
           | Is that why banks are known for using it?
        
             | int_19h wrote:
             | More likely it's the banks that were computerizing early
             | on. If you look at PLs that were available in late 60s /
             | early 70s, COBOL is the one that's most optimized for CRUD
             | and reports, which is largely what the banks wanted. And
             | then once you already have it and it works, why change?
        
         | jwmerrill wrote:
         | Raku interprets decimal literals (like 0.1) as limited-
         | precision rational numbers (Rats) [0-1].
         | 
         | I think this is a pretty user-friendly compromise.
         | 
         | [0] https://docs.raku.org/syntax/Number%20literals
         | 
         | [1] https://docs.raku.org/type/Rat
        
         | fsloth wrote:
         | I don't understand how float would be an implementation detail
         | and not the the thing you are trying to operate on. If a
         | programmer uses a float they are most certainly wanting to use
         | a float?
        
           | hn_throwaway_99 wrote:
           | Parent commenter is saying that in many cases when you write
           | something like                   let foo = 0.1 + 0.2;
           | 
           | the vast majority of the time people want 0.1 and 0.2 to be
           | decimals, not floats, so they should default to that.
        
             | archgoon wrote:
             | [dead]
        
           | dec0dedab0de wrote:
           | Because many programmers don't understand floats, even ones
           | that have been doing it for years. Not to mention that
           | higherlevel languages are being used by non programmers to
           | script things out. I mean I recently helped oversee a class
           | and heard someone tell people new to programming that floats
           | are basically decimals. I sounded like a pedantic jerk
           | interrupting to explain the difference, and I'm sure none of
           | them remembered.
           | 
           | But more to the point of your question, we use floating point
           | math because that's what computers are good at, not because
           | we want that for it's own sake. We want to figure out sales
           | tax, or how long until our kid will need to buy new shoes, or
           | what effect changing the speed limit had on the total number
           | of accidents, or all kinds of other things that humans care
           | about. Using a floating point representation may be the most
           | efficient way to get some of those answers using the
           | technology available, but it is just a step along the way,
           | not what we actually want. That's what I mean by
           | implementation detail.
           | 
           | Basically, I think that any literals typed into the
           | interpreter should work the same way as a calculator. If you
           | want something special for your implementation because it
           | will work better or faster, then that should be explicit.
        
             | oivey wrote:
             | If you want to compute sales tax, you're going to have to
             | define a rounding behavior, too. A decimal type wouldn't
             | save you from this. The desired rounding behavior will vary
             | by situation.
             | 
             | How your calculator handles rounding is itself an
             | implementation detail. I have no idea what rules your
             | calculator uses to round and it's not in any standard. Does
             | my calculator do the same thing? Unknowable.
        
           | l33t233372 wrote:
           | The programmer _wants_ to operate on a real number; the
           | mental/abstract model of whatever application they're
           | building almost certainly involves real numbers instead of
           | floats.
           | 
           | It's the conversation from an abstract model to a concrete
           | instantiation where floats are used, generally out of
           | necessity or ignorance.
           | 
           | The fewer details needed to do this conversion, the easier it
           | is to develop programs. When I say easier, I mean it's faster
           | AND less buggy -- since the conversation often involves
           | introduces errors, subtleties, and logic not present in the
           | abstract model.
        
         | embedded_hiker wrote:
         | This put my daughter off of programming. When she was 7, I
         | showed her how to use python in immediate mode, and she got it
         | without difficulty. She even understood variables. Then one day
         | she wanted to add prices, and she got one of these errors, and
         | she never wanted anything to do with it again.
        
           | angry_moose wrote:
           | I still remember writing something in high school along the
           | lines of:                 i=0         while(i<1):
           | <something with i>             i=i+.1
           | 
           | And spending hours trying to figure out why it ran an extra
           | iteration, and this was early enough it wasn't easily
           | googleable. Whatever I was doing with i needed it to be .1,
           | .2, .3... and thought I was being clever not doing 1...10 and
           | dividing by 10 every iteration within the loop. I think there
           | was also a weird language quirk with whatever I was using
           | that a print(i) rounded to a handful of decimal places so it
           | looked fine while debugging it.
           | 
           | Very frustrating, but in retrospect very eye opening.
        
           | Lendal wrote:
           | Okay, I'll defend floating point numbers. The choice of
           | floating point over decimal represents the choice of science
           | over money. In science, it's more important to have a number
           | system that represents everything from the infinitely small
           | to the infinitely large, rather than one that has perfect
           | precision. Because in nature, perfect precision does not
           | exist. It doesn't matter what pi is to perfect accuracy
           | because there are no perfectly round circles in reality. Only
           | in money and mathematics do people really care about perfect
           | precision. In the real world, precision is negotiable.
           | 
           | I think that's a good lesson for kids.
        
             | rhn_mk1 wrote:
             | Yes, but actually no. Precision becomes important once you
             | start digging in. Calculate the GPS time dilation without
             | sufficient precision and you'll be in trouble. Go down to
             | quantum physics to discover that the exact ratio of mass
             | between the electron and proton might matter for your
             | nuclear reactor.
        
               | worksonmine wrote:
               | You don't even need to get that specific, even web
               | developers encounter this sooner or later, often as a UI
               | bug in what should be really simple math. Then one day
               | you wonder "what the fuck are all these zeroes?
               | Oooooh..."
               | 
               | That's how I learned about it years ago.
        
           | londons_explore wrote:
           | It's understandable - you trust a tool like a calculator to
           | give you the right answer. If it sometimes makes mistakes and
           | you have to check each answer by hand, it isn't really saving
           | you any time.
           | 
           | To many, a rounding error makes the answer "wrong", and
           | suddenly the tool has switched from a reliable one into an
           | untrustworthy one.
        
             | dahfizz wrote:
             | > you trust a tool like a calculator to give you the right
             | answer.
             | 
             | By middle school, kids should have learned that you can't
             | trust calculators. There are all sorts of numbers like pi,
             | e, sqrt(2) that are impossible to represent. Once you start
             | getting into trig, you have to accept rounding.
        
               | chrchang523 wrote:
               | This does depend a bit on the calculator.
               | embedded_hiker's anecdote has made me update in the
               | direction of exposing my daughter to Wolfram Alpha before
               | Python...
        
               | aaaronic wrote:
               | Sure, but .1 is definitely representable, so they can be
               | excused for finding it a little unreasonable that
               | .1+.1+.1+.1+.1+.1+.1+.1+.1+.1 doesn't equal 1 in many
               | languages.
               | 
               | Explaining _why_ .1 isn't representable requires
               | explaining IEEE-754 and explaining _that_ requires an
               | understanding of binary numeric representation.
               | 
               | I teach college students who find this confusing, so I
               | think it's fair that the average person finds floating
               | point behavior confusing (in fact, I've had to explain to
               | Physics Professors doing computation simulation work why
               | their 1-<tiny number> isn't working out the way they
               | expect -- though they initially tried using double
               | doubles to get around the problem).
        
         | shadowgovt wrote:
         | This smells like a good fit for Haskell, since computation is
         | deferred until a result is demanded. I haven't tried it but I
         | can imagine an implementation of, for example, division that
         | would do its best to keep the numerator and denominator intact
         | in their original formats until forced to kick out a value.
         | 
         | (My Haskell-fu isn't deep, but I suspect it would even be
         | possible to write it so that, for example, multiplication of
         | two division operation expressions multiplied the numerators
         | together instead of doing divide -> divide -> multiply...).
        
           | tromp wrote:
           | There's Data.Ratio which represents fractions by their
           | numerator and denominator in lowest terms:
           | $ ghci         GHCi, version 8.10.7:
           | https://www.haskell.org/ghc/  :? for help         Prelude> :m
           | +Data.Ratio         Prelude Data.Ratio> :t (%)         (%) ::
           | Integral a => a -> a -> Ratio a         Prelude Data.Ratio>
           | 18 % 21         6 % 7         Prelude Data.Ratio> 1%10 + 2%10
           | 3 % 10
           | 
           | There's even Data.CReal for working with the computable
           | reals:                   Prelude> :m +Data.CReal Data.Complex
           | Prelude Data.CReal Data.Complex> let i = 0 :+ 1
           | Prelude Data.CReal Data.Complex> exp (i * pi) + 1 :: Complex
           | (CReal 0)         0 :+ 0
        
         | aidenn0 wrote:
         | Common Lisp defaults to ratios of integers for all precise
         | calculations, which is nice other than ending up with results
         | like 103571/20347, which is not obviously "slightly more than
         | 5" the way that 5.090234432594485 is. It does have the
         | advantage over decimals that e.g 1/3 can be represented
         | precisely.
        
           | chowells wrote:
           | I like rational numbers in general, but they do have some
           | huge practical issues in numerical algorithms. In particular,
           | there's no upper bound on the memory use of a rational based
           | on its magnitude. Following from that, there's no lower bound
           | on the time an arithmetic operation may take based on the
           | magnitudes of the operands. When you're doing hundreds of
           | thousands of operations on an accumulator, this can go very
           | wrong.
           | 
           | So I caution against blind preference for rational
           | representations as well. You really have to choose your
           | numeric representation based on your use case. It's
           | unfortunate that this can be so hard to control precisely in
           | many programming languages.
        
             | aidenn0 wrote:
             | Yup; all representations of numbers have tradeoffs. Fixed-
             | sized Integers, log-scaled numbers, and floats all have
             | finite precision. Everything else requires variable space
             | and/or time.
        
         | nailer wrote:
         | > Sure, decimal would be slower.
         | 
         | Would it? I thought dealing with integers - a value, in binary
         | - would be faster than floats - a value in binary, a decimal
         | places value, whatever odd logic there is required to hide the
         | leaky abstraction.
         | 
         | Edit: nevermind. Since the conversation was 'decimal versus
         | float' I thought 'decimal' meant integers without floating
         | points.
         | 
         | If decimals means a decimal point, I think a better suggestion
         | would be to use integers.
        
           | dahfizz wrote:
           | > a value in binary, a decimal places value, whatever odd
           | logic there is required to hide the leaky abstraction.
           | 
           | Ironically, this is a much better description of `decimal`
           | than of `float`.
           | 
           | IEEE float math is done in hardware. It is "one value in
           | binary" that is added, subtracted, multiplied, etc etc with
           | electric circuits.
           | 
           | The decimal abstraction requires manually keeping track of
           | the number of significant digits, converting back and forth
           | so that two different decimals can be added / multiplied, etc
           | etc. There's a lot more that has to happen besides asking the
           | CPU to do a single operation.
        
             | ClumsyPilot wrote:
             | I do hope we will get a hardware implementation of decimal
             | now that chipmakers dont k ow what to do with the extra
             | transistors and keep coming up with new vector
             | instructions, that most developers dont k ow how to ise and
             | most languages don't even supoort
        
               | dahfizz wrote:
               | Which decimal? Fixed point, or floating point? Should the
               | numerator and denominator be given the same bit width?
               | How do you deal with overflow and underflow?
               | 
               | Its easy to think of "decimal" as one thing because every
               | language provides a library called `decimal`, but there
               | are a million subtle decisions and tradeoffs to make when
               | choosing one standard binary representation. Most
               | languages don't have a binary representation at all, and
               | implement `decimal` as a high level abstraction with
               | regular integers.
        
               | int_19h wrote:
               | We managed to standardize on a single binary floating
               | point representation in practice, and even if it's not
               | perfect, the benefit from such standardization makes it
               | worthwhile.
        
               | ClumsyPilot wrote:
               | Pick a solution and make a decision just like it was done
               | with every other format. IEEae has defined one, I believe
               | it was mentioned above
        
           | t8sr wrote:
           | It really depends! Floats are /really/ fast for many
           | operations that are really slow on ints.
           | 
           | On modern CPUs, it's faster to cast a number to double, do a
           | square root and cast back to int, than even the cleverest
           | bithacking int algorithm.
        
           | billythemaniam wrote:
           | In terms of speed: int > float > decimal. Depends on the
           | hardware type. On GPUs, float > int. However the performance
           | difference is negligible for many, many use cases so I
           | generally use decimal as the default and only use float if
           | absolutely necessary.
        
             | ninepoints wrote:
             | Err it really depends on what you're doing. Integer
             | division and modulo is still not fast.
        
               | billythemaniam wrote:
               | Define "fast" please.
        
             | ClumsyPilot wrote:
             | Once you are doing microservices and serving any customer
             | call requires 3 http requests and converting everything
             | into JSON and back every time, it doesnt matter if you use
             | float or int, CPU, GPU, a microcontroller or even abacus by
             | hand.
        
         | otabdeveloper4 wrote:
         | a) You want rationals, not "decimals". Limiting yourself to
         | denominators of powers of 10 is utterly stupid if you have the
         | chance to implement a proper number type stack.
         | 
         | b) Floats are efficient approximations of real numbers.
         | Trigonometry and logarithms are vastly more important than
         | having the numbers be printed pretty, so defaulting to
         | rationals instead of reals is quite insane.
        
         | gorgoiler wrote:
         | Ruby sort of does! The type of 1/3 is Rational!
        
         | sfpotter wrote:
         | This is an insanely bad idea. You think Python is slow now,
         | wait'll you see it after this "improvement".
        
           | toolslive wrote:
           | Why? the hardware supports it.
           | 
           | https://en.wikipedia.org/wiki/Intel_BCD_opcode
        
             | sfpotter wrote:
             | Read the other responses here, and the "Alternatives"
             | section of the article you posted. I am very happy the
             | default is not what you just suggested.
        
               | toolslive wrote:
               | Sorry, my comment was about it being slow, not about it
               | being a bad idea: It is a bad idea, but it will not
               | really make your python even slower than it is today. The
               | Intel supports most of ieee754-2008 which has most of
               | what you would need.
        
               | sfpotter wrote:
               | Judging from other comments here, it is not clear how
               | widely supported IEEE754-2008 is. If it isn't supported
               | everywhere, it would make a VERY bad default for a
               | numeric type.
               | 
               | It also appears that the logic for implemented something
               | like this standard is indeed slower than standard
               | IEEE754. Even if it's only a bit slower, seems bad to
               | make it the default.
               | 
               | All this just to fix something which is confusing to a
               | novice programmer... and this is leaving aside the
               | additional complications a fixed width decimal has which
               | a floating point type doesn't have.
        
           | int_19h wrote:
           | Python is slow because it does a lot of dynamic dispatch,
           | which dwarfs the cost of actual operations such as addition.
           | So it's the other way around - Python, of all things, could
           | probably switch to decimal by default _without_ a significant
           | slowdown.
           | 
           | What would be much slower is all the _native_ code that
           | Python apps use for bulk math, such as numpy.
        
         | Someone wrote:
         | And then you get _"why isn't 3 x  1/3  equal to 1?"_ and
         | similar questions. "Use rationals" would only postpone the
         | issue to _"Why isn't ([?]2)2 equal to 2?"_ and similar
         | questions.
         | 
         | I would think that, nowadays, every child would learn that
         | calculators do not always produce exact answers almost in
         | kindergarten.
         | 
         | Also (nitpick), it's not "float vs decimal". "Floating vs fixed
         | point" and "binary vs decimal" are orthogonal issues.
        
           | int_19h wrote:
           | Yes, children do learn that. But on the calculator, they're
           | inputting numbers in decimal, and it's decimal internally. In
           | programming, we input numbers in decimal, and even write them
           | that way in source code, but the actual math is all binary -
           | thus, there's a disconnect between the common sense
           | expectation of what (0.1 + 0.2) ought to do, and what it
           | actually does. Someone coming from a calculator would _not_
           | expect that to be unequal to 0.3, unlike the situation with
           | square roots.
        
           | dylan604 wrote:
           | > I would think that, nowadays, every child would learn that
           | calculators do not always produce exact answers almost in
           | kindergarten.
           | 
           | such a strange comment to make. the number of people that
           | would ever bump into this situation is so small. like the
           | difference of .1 + .2 = .3 and .30000000000000004
           | 
           | i just used my iPhone to do ([?]2)2 and it displayed 2 as the
           | result. same for 3 x 1/3 to receive an answer of 1. i can
           | only assume that the default android calculator app would
           | behave the same. between those 2 apps, we've probably covered
           | the default calculator for the majority of people.
           | 
           | gotta break out of the HN is the world shell, and realize the
           | majority of people do not suffer the same issues you might
           | deal with on a daily basis.
        
         | lifthrasiir wrote:
         | There is a very big catch---many if not most mathematical
         | functions won't be exact anyway, so you have to round at some
         | decimal places. Python does this with its `decimal` module: the
         | number of fractional digits is literally a part of the global
         | state [1]. While this allows for more concrete control over
         | rounding, assuming that there was no such control, it turns out
         | that the choice of radixes doesn't matter that much.
         | 
         | [1] https://docs.python.org/3/library/decimal.html#context-
         | objec...
        
           | scubbo wrote:
           | > many if not most mathematical functions won't be exact
           | anyway
           | 
           | That's actually a really interesting question - while this is
           | obviously true for most functions which (in a mathematical
           | sense) exist, I wonder if it's true for "all functions
           | weighted by their use in computing applications"? That is -
           | do boring old "addition, subtraction, and multiplication of
           | integers" outweigh division, trigonometrics, etc.?
           | 
           | In 3D modelling/video games, almost certainly not. In
           | accounting software...probably? Across the whole universe of
           | programs: who could say?
        
             | lifthrasiir wrote:
             | Normally I would say that it is hard to tell, because it
             | is. But I think in this particular case I have a reasonable
             | argument---back in 2014 when Python added a support for
             | matrix multiplication operator `@`, the proposal author did
             | survey and made a case for it [1]. And you can see that an
             | exponentiation operator `**` is actually used more than
             | division `/` even in non-scientific usages. And as you've
             | guessed, exponentiation won't be exact if its exponent is
             | negative.
             | 
             | [1] https://peps.python.org/pep-0465/#so-is-good-for-
             | matrix-form...
        
               | dec0dedab0de wrote:
               | I'm trying to find the quote your talking about, but I
               | just see a comparison between stdlib, scikit-learn, and
               | nipy. And for the import stats it is just what was on
               | github in 2014. I think that it is safe to say that most
               | code is not publicly available on github.
               | 
               | Though regardless of usage, I think that people doing
               | stuff that needs floats are more likely to understand why
               | they need them, and have the ability to use them
               | explicitly without much issue. By using python, and most
               | other high level languages, we're already making
               | sacrifices to make things easier to use and understand,
               | and in Python specifically we're told that explicit is
               | better than implicit, except for this.
        
               | scubbo wrote:
               | Interesting data, thanks!
               | 
               | Since addition, subtraction, multiplication, and modulus
               | are each used more than division and exponentiation
               | _combined_ (and since not every use of those last two
               | functions would result in an "inexact" result), I think
               | we can pretty clearly conclude that "most usages of
               | mathematical operators in these libraries will result in
               | an 'exact' result" (I'm hand-waving on the definition of
               | "exact", I don't think it's at issue here)
               | 
               | Which is not, of course, a good justification for ceasing
               | to worry about the problem, since a) those packages might
               | not be representative of all libraries, and b) a small
               | proportion of uses might result in a disproportionate
               | amount of bugs.
        
               | topaz0 wrote:
               | > a small proportion of uses might result in a
               | disproportionate amount of bugs.
               | 
               | This is what concerns me. Sure, using decimal floating
               | point solves 0.1 + 0.2 = 0.3 (which I can't imagine ever
               | writing in real code). But if you get used to that, then
               | you start to expect 0.1*x + 0.2*x to be 0.3*x, and
               | depending what x is this may or may not be true. Maybe it
               | works for all of your test cases (because your test cases
               | are things like 2 and 10^-4), but then you accept some
               | user input and start getting weird bugs (or infinite
               | loops). There is no good solution besides expecting and
               | preparing for rounding error.
        
             | snickerbockers wrote:
             | You're missing the other major problem, which is that range
             | is mutually-exclusive with precision. The scientific
             | community discovered a long time ago that exponential
             | notation is the superior way to represent both for very
             | large and very small values because the mantissa is shifted
             | to the place where precision is needed most.
             | 
             | >In 3D modelling/video games, almost certainly not.
             | 
             | A 32-bit integer divided into a 16-bit whole and a 16-bit
             | fraction would be limited to only representing values
             | between -32768 and 32767 while also having worse precision
             | than a 32-bit ieee std754 floating-point at values near 0.
             | 
             | >In accounting software...probably?
             | 
             | Representing money in terms of cents instead of dollars
             | removes the need for real-numbers entirely outside of
             | "Office Space" scenarios where tracking fractions of cents
             | over millions of transactions adds up to tangible amounts
             | of money.
             | 
             | >Across the whole universe of programs: who could say?
             | 
             | Most computer programs don't need real numbers of any sort,
             | and the ones that do need to be written by people who
             | understand basic mathematical concepts like precision.
        
           | rqtwteye wrote:
           | Rounding is fine but it would be nice if 0.1+0.2 was
           | predictably 0.3. I am having a lot of trouble explaining to
           | people that float numbers should be avoided unless you really
           | need them. I have seen code that stored versions as floats
           | and the dev was surprised that version 1.1 wasn't always
           | equal to "1.1".
        
             | lifthrasiir wrote:
             | > I have seen code that stored versions as floats and the
             | dev was surprised that version 1.1 wasn't always equal to
             | "1.1".
             | 
             | And will break when the version reaches 1.10. While I agree
             | we need a better way to teach this (e.g. inexact-exact
             | distinction as in Scheme or more recently Pyret), that's as
             | problematic as storing a telephone number as an integer (or
             | worse, a FP number).
        
               | rqtwteye wrote:
               | Totally agree that storing a version in a float is stupid
               | but that's where we are :-(
        
           | int_19h wrote:
           | And that's fine! People directly deal with math in decimal
           | context, so we already have some expectations about how
           | rounding etc works. So long as decimal type and its
           | operations follow those expectations, they'll cope with it.
           | The problem with binary is that these expectations don't
           | translate for some of the most basic stuff.
        
           | scaredginger wrote:
           | Wouldn't seriously suggest doing this, but rationals with big
           | integers would have exact results for all the common
           | operations
        
             | crdrost wrote:
             | I mean, cosine is pretty common...
             | 
             | The next level solution is to apply generators so that
             | either the decimal stream or the continued fraction is
             | allowed to be infinitely precise, but I think this can have
             | dangerous effects where checking whether a number is equal
             | to 0 or maybe 1 can involve infinite computation? So that's
             | where you really understand "oh, I do really need that
             | epsilon, for comparisons' sake."
             | 
             | For continued fractions I think you can also just have your
             | library bound the size of the integers involved? So "it's
             | an array of signed int32s, but if your continued fraction
             | generates a number that would overflow that, we just
             | truncate the stream at that point." Then the library is
             | able to say that these two things are equal because their
             | difference is [0; int_overflow] which becomes just [0].
             | Something like that.
        
               | jfoutz wrote:
               | just yesterday someone commented about
               | https://fredrikj.net/calcium/index.html
               | 
               | which is pretty darn amazing. pi and e are essentially
               | first class, but a lot of transcendentals aren't. seems
               | like a really neat approach.
        
               | lifthrasiir wrote:
               | Calcium is amazing and so is exact real arithmetic or
               | constructive real number, but they all can't avoid
               | practically undecidable inputs. (Algebraic numbers as in
               | Calcium can be made decidable, but they still can take an
               | unreasonable amount of time to compute. Calcium does
               | answer "unknown" for those cases.)
        
             | runeks wrote:
             | I started out using the Haskell "Rational" type [1] (which
             | is exactly what you mention) for
             | https://cryptomarketdepth.com/ but I had to abandon it
             | because it was horribly slow. I was multiplying numbers
             | with roughly 8 decimal places, and once I had done this
             | like 100 times my program spent almost all its time trying
             | to simplify fractions with a 1000 digit numerator and
             | denominator.
             | 
             | [1] https://www.stackage.org/haddock/lts-20.10/base-4.16.4.
             | 0/Pre...
        
               | lifthrasiir wrote:
               | This is indeed the reason that Python didn't (initially)
               | have rational numbers while its spiritual predecessor ABC
               | had. [1]
               | 
               | [1] https://python-history.blogspot.com/2009/03/problem-
               | with-int...
        
         | eru wrote:
         | > [...] defaulted to decimal for the literals, [...]
         | 
         | Why not rational numbers?
        
         | mysterydip wrote:
         | Would a "fixed-point binary-coded decimal" type be a solution
         | here? With 64 bit values that gives you 16 digits to play with,
         | which for "everyday numbers" seems like plenty.
        
           | gpderetta wrote:
           | IEEE double gives you 15 digits and a much larger dynamic
           | range, so the tradeoff is just not worth it except for
           | specialized applications.
        
         | pdonis wrote:
         | _> I wish python and other high level languages defaulted to
         | decimal for the literals, and made float something you had to
         | do explicitly._
         | 
         | When Python originally made the choice to have literals with
         | decimal points in them be floats, the language did not have a
         | decimal implementation, so floats were the only choice.
         | 
         | I don't know if anyone has proposed changing the default now
         | that Python does have a decimal implementation, but I suspect
         | that such a proposal would be rejected by the Python developers
         | as breaking too much existing code.
         | 
         | What would be almost as nice, and would be backwards
         | compatible, would be introducing a more compact way to declare
         | decimal literals, something like "0.1d".
        
         | snickerbockers wrote:
         | You can already use fixed-point (AKA "decimal") values in any
         | language which supports integer artihmetic, but you will
         | quickly discover the two major limitations it has: your
         | programs still need to account for precision, and the range of
         | values which can be expressed becomes smaller as precision
         | increases.
        
         | Spivak wrote:
         | I would love that.                   f = float(closest_to=0.1)
         | 
         | You can't really mess up programmer expectations like this.
        
           | bruce343434 wrote:
           | fixed point numbers!
        
         | hn_throwaway_99 wrote:
         | Honestly, I'd just be happy with first class language support
         | for decimals at all.
         | 
         | For example, I'm a huge fan of TypeScript, but it is hamstrung
         | by the fact that javascript only supports a single `number`
         | type (and, recently, `bigint`). Worse is the effect that since
         | JSON is derived from javascript, it also has no built-in
         | decimal type. So what happens inevitably when you want to
         | represent stuff like money:
         | 
         | 1. First people start using plain numbers, then they eventually
         | hit the issues like this post.
         | 
         | 2. Then they have to decide how they will represent decimals in
         | things like APIs. Decimal string? Integers that represent
         | pennies or some fraction of pennies?
         | 
         | 3. Also, pretty much all databases support decimals natively,
         | so then you get into this weird mash of how to not lose
         | precision when transferring data to and from the DB.
         | 
         | Overall it's just definitely one of those issues that
         | programmers hit and rediscover again and again and again. I'm
         | surprised there hasn't been more movement towards a better
         | language-level solution for the post popular language in use
         | worldwide.
        
           | User23 wrote:
           | It's surprising that none of the popular high level languages
           | that borrow so much else from Lisp haven't borrowed its
           | rational number type. Really the whole numeric tower makes a
           | ton of sense, and you can always declare floats if needed.
        
             | cbolton wrote:
             | What about Julia? It's somewhat popular and heavily
             | inspired by Lisp. It has a type tree that's reminiscent of
             | Lisp's numerical tower: https://global.discourse-
             | cdn.com/business5/uploads/julialang...
        
             | mdouglass wrote:
             | Thanks for the encouragement to look up lisp's numeric
             | tower (https://en.wikipedia.org/wiki/Numerical_tower), that
             | was interesting to compare to the languages I'm more
             | familiar with.
        
             | kazinator wrote:
             | Including Lisp. Not every Lisp dialect has rationals.
        
           | noveltyaccount wrote:
           | Came here to make this exact same comment, including the "I
           | love TS but wish it wasn't built on JS" sentiment. I explored
           | Rust recently and was disappointed to see there's no stdlib
           | Decimal, but instead there are _multiple_ community
           | implementations - so I 'd have to sort through and vet the
           | right one.
        
           | colonCapitalDee wrote:
           | Check out C#'s decimal type.
           | 
           | https://learn.microsoft.com/en-
           | us/dotnet/api/system.decimal?...
        
             | hn_throwaway_99 wrote:
             | Thanks, it's been forever since I've used C# so glad to
             | know this exists, and seems like the ideal implementation.
             | 
             | Really wish JS had added first class support for a
             | bigdecimal class before bigint. After all, the first is
             | basically a superset of the latter.
        
           | recursive wrote:
           | JSON specifies the grammar of numbers as tokens, but not the
           | behavior of how they should be parsed. Implementors could
           | choose to parse numbers as decimals without violating the
           | spec.
        
             | dec0dedab0de wrote:
             | In practice, it is usually easier to use the parser that
             | came with your language(s) and figure out a different way
             | to encode the values that are causing trouble, instead of
             | writing a new parser.
        
             | hn_throwaway_99 wrote:
             | I agree with you, but there's theory and then there is
             | reality. The JSON spec is famously "underspecified" in that
             | it pretty much ONLY specifies the token grammar but nothing
             | with respect to interpretation, and hence there are lots of
             | areas which have been problematic for years - the spec even
             | says this with respect to object keys:
             | 
             | > The JSON syntax does not impose any restrictions on the
             | strings used as names, does not require that name strings
             | be unique, and does not assign any significance to the
             | ordering of name/value pairs. These are all semantic
             | considerations that may be defined by JSON processors or in
             | specifications defining specific uses of JSON for data
             | interchange.
             | 
             | So, in reality, the JSON "spec" is really how the most
             | popular implementations interpret it. I'm not aware of a
             | single implementation (though I could most definitely be
             | wrong) that interprets number tokens as anything but
             | floats/doubles by default.
        
           | IshKebab wrote:
           | Unless you're dealing with billions and care about pennies
           | then using float for money is fine in 99% of cases.
        
             | kazinator wrote:
             | You can use floating-point for money even if you're dealing
             | with (American) billions (10 figures), and care about
             | pennies. With 10 figures in the integer part, you have 5
             | more digits of precision in the fractional part, so down to
             | the thousandths of a cent. A single addition or
             | multiplication will not accumulate an error which affects
             | the cent, and you can round the calculation to the best
             | approximation of the penny in order to clip off the error.
        
             | dec0dedab0de wrote:
             | That's only true if you know that you have to round
             | everything to the second digit. If you have a user
             | calculating the total cost of buying something that is
             | $7.10 and something that is $10.20, you don't want to show
             | them $17.299999999999997. You would be better off storing
             | everything as an integer of pennies and just displaying the
             | dot in the frontend. To be fair, I also think that high
             | level languages should come with types for all the major
             | currencies.
        
               | kazinator wrote:
               | I'm an expert programmer and I agree with downvoted
               | IshKebab. This is just HN having a Reddit moment.
               | 
               | IEEE 64 bit floats are accurate to just past 15 decimal
               | digits. For ordinary monetary amounts, the exact figure
               | in cents is approximated with ridiculous precision. If
               | you rub two pennies together, you are likely causing more
               | of a difference in the amount of copper than the IEEE 64
               | bit float causes in the value.
               | 
               | You have to do many, many additions and multiplications
               | before you get a result which has accumulated so much
               | error that it is now closer to the wrong penny. E.g. if
               | you don't deal with dollar amounts more than 7 figures,
               | you have about 8 places past the decimal point; you need
               | something like a 6 place error before the penny is
               | affected.
               | 
               | You can counteract this problem by correcting
               | intermediate results to that floating-point value which
               | is closest to exact penny result. In other words,
               | throughout your calculation, you truncate away the
               | difference between the result, and the best approximation
               | of the dollar and cent value.
               | 
               | Within this framework, you can implement all required
               | rounding rules, too. You can take a floating-point result
               | representing a fraction of a penny and round it according
               | to banker's rule to the penny.
               | 
               | Of course, you can't just ignore the issue and just
               | blindly use floating-point for money in a serious
               | accounting system; but that's a strawman version of using
               | floating-point for money.
               | 
               | Also, Microsoft Excel uses floating point. See here:
               | 
               | https://learn.microsoft.com/en-
               | us/office/troubleshoot/excel/...
               | 
               | Vast armies of people rely on Excel for financial
               | calculations.
        
               | bheadmaster wrote:
               | > You have to do many, many additions and multiplications
               | before you get a result which has accumulated so much
               | error that it is now closer to the wrong penny
               | 
               | > You can counteract this problem by correcting
               | intermediate results to that floating-point value which
               | is closest to exact penny result
               | 
               | The argument seems to be "floats are okay, as long as
               | you're careful", but forgetting to round the number in
               | between a large number of operations is a probable
               | mistake.
               | 
               | Using decimals would make such a mistake impossible.
        
             | hn_throwaway_99 wrote:
             | That's absolutely, 100% false. Trivial example (I've
             | actually hit an analogous bug in production): User has
             | money in their wallet, and you want to check before they
             | make withdrawals that their balance doesn't go negative.
             | You have some logic in your code that is basically like:
             | if (walletBalance - sumOfWithdrawals < 0) { throw new
             | Error('overdrawn); }
             | 
             | Try that code in Javascript where walletBalance = 0.3 and
             | the sumOfWithdrawals = 0.2 + 0.1.
             | 
             | Point being there are tons of operations in the financial
             | world where you check things against 0, or want to ensure
             | that a breakdown of smaller transactions equals a larger
             | amount. Those all can fail with floating points but succeed
             | with decimals.
        
       | georgeburdell wrote:
       | This is one of my favorite interview questions. I embed the issue
       | in a short block of code, run it, and ask the candidate to
       | explain the what, why, and how to fix. I work in a field with
       | physical measurements. Maybe 1/3 of candidates get it correct
        
         | davbryn wrote:
         | What do you gain from asking this question in an interview?
         | Let's be honest, you've spotted it online and used it to show
         | that you have a higher understanding. If I had an interviewer
         | ask me this it would be a massive red flag. You are
         | interviewing a candidate - it isn't the place to recycle
         | someone else's online answer to flex
        
           | georgeburdell wrote:
           | No, we've actually had this issue in production. I did not
           | "spot it online". The question I ask is framed how the bug
           | appeared for us at the time. It's a great way to screen out
           | people who are too academic and do not consider the
           | limitations of the systems they use.
        
           | acuozzo wrote:
           | > If I had an interviewer ask me this it would be a massive
           | red flag.
           | 
           | Do you work in a field involving physical measurements like
           | OP does?
        
       | pletnes wrote:
       | I don't agree with the title, but this article is great if you
       | want to get to the very bottom of the floating point rabbit hole.
       | https://people.cs.pitt.edu/~cho/cs1541/current/handouts/gold...
       | 
       | What Every Computer Scientist Should Know About Floating-Point
       | Arithmetic
       | 
       | One thing worth mentioning is that the IEEE 754 floating point
       | standard is implemented in hardware in most CPUs
       | (microcontrollers might deviate) so if you learn this stuff
       | you'll be set for life, as in, it doesn't depend on the
       | programming language you're using.
        
       | bricss wrote:
       | TL;DR: Bocs of IEEE 754 encoding, yo
        
       | pantulis wrote:
       | And that's the reason why Decimal types are your friend, kids.
        
       | stefncb wrote:
       | Looks like the title got messed up in the process; the + sign was
       | definitely not supposed to be 'and'.
        
         | compiler-guy wrote:
         | People often use "and" to mean addition. "One and one make
         | two". So I think the title works fine.
        
           | stefncb wrote:
           | I personally read it as 0.1 and 0.2 are both equal to
           | 0.3000-whatever. Maybe it's because English is my second
           | language.
        
         | js2 wrote:
         | "and" as a conjunction is sometimes used to mean "plus". As in
         | "two and two make four."
        
       | anthk wrote:
       | Test with Unicon:
       | 
       | procedure main() if (0.1 + 0.2 == 0.3) then { write("true") } end
       | 
       | It prints "true".
        
       | dennis_jeeves1 wrote:
       | Simple explanation: because computers do not 'understand' the
       | concept of decimals. 'Natively', they can only manipulate
       | integers.
        
       | bradwood wrote:
       | Because mantissa and exponents
       | 
       | CS101 -- can we please move on.
        
       | ninepoints wrote:
       | Wow the "we should be using decimal" takes in this thread are
       | hilariously misguided.
        
       | yalogin wrote:
       | Why isn't the floating point version of 0.1 just 0.10000000000000
       | 00000000000000000000000000000000000000000000000000000000000000000
       | 0 and something like 0.100000000000000005551115123125782702118158
       | 34045410156250000000000000000000000000 ?
        
         | croes wrote:
         | That's why
         | 
         | https://www.exploringbinary.com/why-0-point-1-does-not-exist...
        
       | bookofjoe wrote:
       | I have no idea what a floating point is but I still enjoyed
       | reading the comments. I bet there are many people like me who
       | love HN but choose not to make their presence known so as not to
       | be downvoted (full disclosure: I've never down- or up-voted. But
       | I do know this: on the rare occasions I remark on how funny or
       | clever I found something, that comment usually gets downvoted).
        
         | weberer wrote:
         | Computers store numbers in base 2 rather than base 10. That
         | means each digit is no longer the ones/tens/hundreds place, but
         | instead the ones/twos/fours/eights place. This is all fine when
         | talking about integers, since every integer in base 10 can be
         | represented in base 2.
         | 
         | The problem is when converting decimals. Now instead of each
         | digit being tenths/hundredths/thousandths, we have
         | halfs/fourths/eights/etc. Now try out the problem yourself.
         | Imagine you had a formula in the form of
         | 
         | (1/2)x + (1/4)y + (1/8)z + (1/16)a + (1/32)b ...
         | 
         | Try to find a solution that adds up to exactly 0.1 and you'll
         | see that it can't be done. Computers just get as close as they
         | can.
        
         | ThrowawayR2 wrote:
         | > " _...on the rare occasions I remark on how funny or clever I
         | found something, that comment usually gets downvoted_ "
         | 
         | Not unjustifiably; without specifically referring to any posts
         | you may have made, such comments in general add no insight or
         | value to the discussion. It's just noise that has to be
         | scrolled past.
         | 
         | (The same goes for discussing getting downvoted, which is
         | discouraged by the HN guidelines.)
        
         | nixpulvis wrote:
         | Think of floating point as a "floating" "point" like the dot
         | that separates the two parts of a number like 3.1415. It floats
         | around because this allows you to have more precision when the
         | number is small without giving up the ability to represent
         | large number. Fixed point number encodings also exist and are
         | much simpler, e.g. use 24 bits for the left hand side and use 8
         | bits for the decimal part.
         | 
         | Anyway, thought I'd give you some background. This stuff is
         | easy to look up too. You're probably getting downvoted because
         | HN doesn't really like unsubstantive comments.
        
       | [deleted]
        
       | jacobmartin wrote:
       | This is great!
       | 
       | There was a post on /r/softwaregore recently where someone showed
       | a progress bar on Steam that said "57/100 achievements! Game 56%
       | complete" or something like that. I snarkily commented something
       | about naively using floor() on floating point and then moved on.
       | 
       | But then I thought that that may not have been the problem, fired
       | up emacs and wrote a C program basically just saying
       | printf("%f\n", 57.0 / 100.0 * 100.0);
       | 
       | To my surprise this correctly gave 57.000000, but in python,
       | 57 / 100 * 100
       | 
       | Gave 56.999... anybody know what was up here? Different
       | algorithms for printing fp?
        
         | OscarCunningham wrote:
         | It's possible for games to have over 100 achievements, which
         | could lead to it rounding to 0% even when they have an
         | achievement, or to 100% when they still have one missing.
         | 
         | People won't like this, so perhaps Steam put in some logic to
         | adjust the numbers, and it is also affecting your case.
         | 
         | EDIT: But I would have gone for ceiling(99*achievements/total),
         | which does give 57% in this case.
        
         | creeble wrote:
         | Compiler optimization.
        
           | creeble wrote:
           | I guess I'm misunderstanding what `-frounding-math` for gcc
           | does / does not do.
        
           | lifthrasiir wrote:
           | Compilers are very cautious about floating point optimization
           | because many real number identities do not hold in FP, so
           | they won't do much unless instructed to do so. Even if they
           | do a constant propagation they will generally calculate in
           | binary, in decimal. (Exception: decimal literals in some
           | languages like Go are untyped and can be exactly calculated
           | before getting rounded at once.)
        
           | kazinator wrote:
           | Wrong guess; the algebraic optimization you might be thinking
           | of is simply not allowed.
           | 
           | The expression is likely subject to an optimization known as
           | constant folding: the compiler calculates the value of the
           | constant expression, and substitutes that value into the
           | code.
           | 
           | However, that constant-folding calculation has to produce the
           | same result as what would happen at run-time: the
           | 56.999999999999992895... approximation of 57.
           | 
           | Constant-folding having to produce the same results as run-
           | time creates a challenge in cross-compiling situations, when
           | the host machine's math is different from the target
           | machine's math. The compiler must emulate the target machine
           | math.
        
         | lifthrasiir wrote:
         | The calculated number is indeed
         | 56.99999999999999289457264239899814128875732421875 which is not
         | same to 57 (IEEE 754 binary32 or binary64 can represent this
         | integer exactly). You can verify this with the following:
         | printf("%f\n", 57.0 / 100.0 * 100.0 - 57.0);
         | 
         | It just happens that `%f` in C defaults to 6 fractional digits.
        
           | jacobmartin wrote:
           | Thank you!
        
         | TheRealPomax wrote:
         | And what optimization did you tell the C compiler to use?
        
         | kazinator wrote:
         | The f in %f doesn't mean "floating-point"; it means "fixed
         | digits": printing the value in the style -dddd.dddd. If you
         | don't specify how many digits after the decimal point, it
         | defaults to six.
         | 
         | If 56.999999999999 is rounded to 6 digits after the decimal
         | point, you get 57.000000.
        
           | jacobmartin wrote:
           | Yes, thank you for this. I just ran it with the floor()
           | function actually called and it did give 56.000000. I didn't
           | think to check the precision. Rookie mistake!
        
             | kazinator wrote:
             | Thus, if you have a minute, try "%.20f" instead of %f.
             | 
             | :)
             | 
             | Or, how about this: #include <stdio.h>                 int
             | main(void)       {         for (int prec = 0; prec < 25;
             | prec++)         {           printf("%.*f\n", prec, 57.0 /
             | 100.0 * 100.0);         }         return 0;       }
             | 
             | Output:                 57       57.0       57.00
             | 57.000       57.0000       57.00000       57.000000
             | 57.0000000       57.00000000       57.000000000
             | 57.0000000000       57.00000000000       57.000000000000
             | 57.0000000000000       56.99999999999999
             | 56.999999999999993       56.9999999999999929
             | 56.99999999999999289       56.999999999999992895
             | 56.9999999999999928946       56.99999999999999289457
             | 56.999999999999992894573       56.9999999999999928945726
             | 56.99999999999999289457264
             | 56.999999999999992894572642
             | 
             | The 64 bit double will store 15 decimal digits reliably.
             | That is to say, if you have a decimal figure with 15
             | significant digits, which is in range of the type (and not
             | mapping to a denormal value close to zero and whatnot), all
             | 15 digits are representable and can be recovered.
             | 
             | In the reverse direction, you need about 17 decimal digits
             | in order to capture an 64 bit double as decimal text such
             | that the exact value can be recovered from the decimal
             | text.
             | 
             | Thus in the above loop's output, once we are past 17 digits
             | (including the 56 before the decimal point), we are no
             | longer seeing any new data, just a continuation of the
             | fraction.
             | 
             | And, notice how the last value that is still 57.000.... is
             | exactly 15 digits wide. The next row is 16 digits, and
             | that's where we now have 56.9999.... but 16 digits isn't
             | quite enough to capture the value. I believe the next row
             | gets us that: the ...99929. If we use that as a constant,
             | any digits after that make no difference.
             | 
             | Programming languages which, by default, print floating-
             | point values to 15 digits will show the nice result .1 + .2
             | = .3.
             | 
             | This is what I did in TXR Lisp.                 1> *print-
             | flo-precision*       15       2> (+ .1 .2)       0.3
             | 3> (set *print-flo-precision* 16)       16       4> (+ .1
             | .2)       0.3       5> (set *print-flo-precision* 17)
             | 17       6> (+ .1 .2)       0.30000000000000004
             | 
             | We can see there is no value difference in digits beyond
             | 17:                 7> (eq 0.30000000000000004
             | 0.300000000000000049)       t       8> (eq
             | 0.30000000000000004 0.300000000000000040)       t
             | 
             | To get different value (different floating-point bit
             | pattern), we need a difference in the 17th digit. And not
             | just a single increment:                 9> (eq
             | 0.30000000000000004 0.30000000000000003)       t       10>
             | (eq 0.30000000000000004 0.30000000000000002)       t
             | 
             | The last digit being 3 and 2 is still mapping to the same
             | value. When we make it 1, we start getting a different
             | float:                 11> (eq 0.30000000000000004
             | 0.30000000000000001)       nil
        
       | twawaaay wrote:
       | The answer is: "If it matters to you, you should not be doing FP
       | math."
        
         | j16sdiz wrote:
         | Well.. IEEE754 have well defined behavior and detail reasoning.
         | If it matter to you, you may still want FP math
        
         | kazinator wrote:
         | If that .000...004 matters to you, your application requires
         | more precision than is available from the IEEE 64 bit double.
        
       | mharig wrote:
       | Hopefully, we will have posits with hardwaresupport sooner than
       | later.
       | 
       | https://spectrum.ieee.org/floating-point-numbers-posits-proc...
       | 
       | https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/posits...
       | 
       | Unfortunately, they are not a solution to the OPs problem, which
       | is fundamentally embedded in the architecture of computers. One
       | has to find an appropriate representation for the needed numbers
       | in bits and bytes.
       | 
       | In Python one can use fractions or decimals, if the float format
       | is not good enough. Other options are fixed point arithmetic or
       | arbitrary precision arithmetic. Choose one that combines the
       | needed characteristics with the least amount of work.
        
       | McGlockenshire wrote:
       | > I think the reason that 0.1 + 0.2 prints out 0.3 in PHP is that
       | PHP's algorithm for displaying floating point numbers is less
       | precise than Python's
       | 
       | It's a display thing, not an algorithm thing. It rounds by
       | default at a certain length, previously 17 digits.
       | php > echo PHP_VERSION;         8.2.1         php >
       | $zeropointthree = 0.1 + 0.2;         php > echo $zeropointthree;
       | 0.3         php > ini_set('precision', 100);         php > echo
       | $zeropointthree;
       | 0.3000000000000000444089209850062616169452667236328125
       | 
       | https://www.php.net/manual/en/ini.core.php#ini.precision
        
       | asicsp wrote:
       | See also: Floating Point visually explained
       | (https://fabiensanglard.net/floating_point_visually_explained...)
       | -- for those allergic to mathematic notations
       | 
       | There's also a stackoverflow thread:
       | https://stackoverflow.com/q/588004
        
         | brazzy wrote:
         | Also https://floating-point-gui.de/
        
       | [deleted]
        
       | abofh wrote:
       | Very large values of 0.1
        
       | mensetmanusman wrote:
       | Hard mode: what application would be effected by .3 being off
       | 10^-17
        
       | rkagerer wrote:
       | "The short answer is that 0.1 + 0.2 lies exactly between 2
       | floating point numbers, 0.3 and 0.30000000000000004, the answer
       | is 0.30000000000000004 because its significand is even."
        
         | hgsgm wrote:
         | That's a poor summary. It's round(0.1) + round(.2) that sits
         | between 0.3 and 0.3000...4 (rounding in base 2)
        
       | picture wrote:
       | For those who prefer a more visual and interactive demo:
       | https://evanw.github.io/float-toy/
       | 
       | It's not very complicated when you see it in bits
        
       | alexjplant wrote:
       | I once inherited the development and maintenance responsibilities
       | on a financial forecasting/business development app. There were
       | lots of expected value computations in the form of adding up
       | potential revenues multiplied by their probability of win with
       | costs subtracted (i.e. currency values). The previous developer
       | had used floats everywhere resulting in runoff values as seen
       | here.
       | 
       | I started using decimal in all of the new features in an effort
       | to mitigate this but it resulted in even more headache as I now
       | had a bunch of casts floating around on top of the truncation
       | band-aids that I had to implement for existing lengthier
       | calculations. My plan was to refactor the whole app and SQL
       | schema but if memory serves I got pulled onto something more
       | pressing before I had the chance.
       | 
       | This was especially disappointing to me because this was all
       | implemented in C# and T-SQL which are languages with first-class
       | support for decimal numbers. It wouldn't surprise me if the app
       | is still in use today with some hapless dev halfway across the
       | country whacking these bugs as they pop up.
        
         | parhamn wrote:
         | This makes me wonder why Decimals aren't part of std libs in
         | Javascript or Golang.
        
           | lifthrasiir wrote:
           | It is hard to design. There should be some sort of precision
           | and rounding mode control throughout calculation, meaning
           | there should be some sort of implicit state or those controls
           | should be sprayed into every operation. The latter is
           | explicit but tedious [1]. The former will still surprise
           | people from time to time. IEEE 754 binary number is sort of
           | working well in this aspect.
           | 
           | [1] See QuickJS's BigDecimal extension for example:
           | https://bellard.org/quickjs/jsbignum.html#Properties-of-
           | the-...
        
             | ClumsyPilot wrote:
             | that's eactly why it needs to be in standard library- so
             | people dont make mistakes reinventing the wheel
        
               | lifthrasiir wrote:
               | Only if you have a proven design. I don't think we have
               | any satisfactory design at all.
        
               | ClumsyPilot wrote:
               | What's wrong with
               | https://en.m.wikipedia.org/wiki/Decimal64_floating-
               | point_for...
               | 
               | Or with C# 128 bit implementation of decimal?
               | 
               | Is there no good implementation anywhere?
        
       | jwmoz wrote:
       | tldr; use Decimal
        
         | jacobsenscott wrote:
         | The proper tldr is - understand how floating point works (you
         | can represent a huge range of numbers with a lot of precision,
         | but not perfect accuracy, and high performance), and then
         | decide if it is the proper data type for your problem. For
         | example setting the heading and velocity of a missile (85.623
         | degrees, 500.138 m/s (idk how fast missiles go) floats are
         | great. It is impossible to steer on an exact course anyway due
         | to wind, temperature, etc (I assume...)
         | 
         | Storing the number of dollars in you savings account - us
         | decimal or better yet integer (just count the pennies).
        
           | kazinator wrote:
           | IEEE 64 bit double can represent a missile velocity of
           | 85.6230000000000.
        
       | TheRealPomax wrote:
       | Mandatory link to the original "What Every Computer Scientist
       | Should Know About Floating-Point Arithmetic" by David Goldberg
       | over on
       | https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.h...
        
         | jordigh wrote:
         | That url must have the highest linked-to-read ratio on the
         | internet. Although it seems to be fading away, linked to less
         | frequently.
        
       | johnklos wrote:
       | I love when someone figures out the detailed reasoning behind why
       | some things are the way they are. Sometimes I go down those
       | rabbit holes myself and never figure it out, but when I do, it
       | feels so good! Just as reading articles like this.
       | 
       | Thanks, Julia!
        
       | tiffanyh wrote:
       | TL;DR;
       | 
       | 0.1 in binary, is a repeating decimal - just like how 1/3 in base
       | 10 is a repeating decimal.
       | 
       | So you get errors due to rounding.
       | 
       | This is also why it's super important in loops to never use "="
       | as a condition statement, but instead use "<=" (or ">=").
       | Otherwise you might create an infinite loop.
        
         | hedora wrote:
         | It is also sometimes useful to write abs(x-y) < epsilon.
        
           | tragomaskhalos wrote:
           | A caveat here is that a good value for epsilon is dependent
           | on the magnitude of the value you're abs'ing.
        
         | insane_dreamer wrote:
         | This is the first clear and concise explanation I've read about
         | why a number as "simple" as 0.1 is imprecise as a float.
         | Thanks!
        
         | bobleeswagger wrote:
         | > super important in loops to never use "=" as a condition
         | statement, but instead use "<=" (or ">="). Otherwise you might
         | create an infinite loop.
         | 
         | Never seems a bit strong, it depends on the context. You can
         | use = on int types all day.
        
           | creeble wrote:
           | But never with floating point. I think that's the context of
           | the whole article, and should really be the main take-home
           | (which I'm not sure the article gets across clearly enough).
        
             | TheRealPomax wrote:
             | you can use it with floats just fine if your condition
             | boundary is a "whole number float", like running from 0.0
             | to 1.0 in steps of "some small increment". It's only when
             | your boundary has a non-zero fraction that you're going to
             | have to be more careful.
        
               | creeble wrote:
               | >running from 0.0 to 1.0 in steps of "some small
               | increment".
               | 
               | I'm not sure what you mean; those aren't integers (if
               | that's what you mean by "whole number float"):
               | 
               | python3
               | 
               | >>> 0.4 == 0.6 - 0.2
               | 
               | False
               | 
               | (edit: formatting for python)
        
               | TheRealPomax wrote:
               | fair, that was completely the wrong example.
        
         | MuffinFlavored wrote:
         | https://docs.rs/rust_decimal/latest/rust_decimal/
         | 
         | Or something like this?
        
           | jacobsenscott wrote:
           | The choice of decimal vs float depends on domain. For
           | financial calculations you want to be able to represent
           | amounts exactly. Decimal can represent $1.01 exactly, but
           | float cannot (it picks a value very close to that, and as you
           | do calculations eventually these small inaccuracies amount to
           | real money).
           | 
           | But if you are storing the weight of a product a float might
           | be fine - you don't really care if the system thinks you have
           | 50.000001 pounds of product when you have 50 pounds (because
           | your scale isn't that accurate anyway).
           | 
           | Floats will generally give you better performance than
           | decimal types as well.
        
             | MuffinFlavored wrote:
             | Would you say it's "odd" that popular languages (C#, Java,
             | JavaScript/node.js/TypeScript, Python) don't have built in
             | decimal libraries?
        
               | jacobsenscott wrote:
               | I don't think I would say it is odd because it isn't a
               | type of number that is directly supported by the
               | hardware. That said it is available in the standard
               | library of most popular languages, and probably just a
               | package install away for any other languages.
               | 
               | Also, a plain integer is usually sufficient for financial
               | calculations (just count pennies, not dollars) so in some
               | ways I think decimal types are overused when an integer
               | would do just fine.
        
               | MichaelNolan wrote:
               | Java has BigDecimal. Python has a decimal and a fraction
               | class. I'm assuming C# has something apart of their
               | standard library as well. And JS/TS has third party
               | libraries that can for arbitrary length precision math as
               | well.
        
               | MuffinFlavored wrote:
               | Thank you. I had a feeling I was wrong. So just
               | JavaScript has BigInt/BigDecimal
               | https://stackoverflow.com/questions/16742578/bigdecimal-
               | in-j..., interesting
        
       | ColinDabritz wrote:
       | I love that this is a common enough problem, that there's a full
       | domain website for it:
       | 
       | https://0.30000000000000004.com/
        
       | undershirt wrote:
       | From a deleted comment I liked here from @stabbles:
       | 
       | > some things can be represented in finite digits in base x but
       | require infinite digits in base y.
       | 
       | Very good summary. Binary to decimal is very straightforward
       | until fractions require infinite digits. I don't think dec64[1]
       | is even a tradeoff--it's just better. The significand stays a
       | normal binary number-- but it encodes the decimal point in _gasp_
       | decimal. No infinities required for the numeric language that we
       | all think in.
       | 
       | [1] https://en.wikipedia.org/wiki/Decimal64_floating-
       | point_forma...
        
         | lifthrasiir wrote:
         | > ... dec64 is ...
         | 
         | Not to be confused with Douglas Crockford's DEC64 [1], which I
         | believe is worse than binary floating points.
         | 
         | [1] https://www.crockford.com/dec64.html
        
           | undershirt wrote:
           | Oh, thank you! I actually think I'm going with Crockford on
           | this one, and that's what I meant to post.
        
             | lifthrasiir wrote:
             | In which case I disagree ;-). Most strikingly DEC64 doesn't
             | do normalization, so comparison will be a nightmare (as you
             | _have_ to normalize in order to compare!). He tried to
             | special-case integer-only arguments, which hides the fact
             | that non-integer cases are much, much slower thanks to
             | added branches and complexity. If DEC64 were going to be
             | "the only number type" in future languages, it had to be
             | much better than this.
        
               | undershirt wrote:
               | Good points! I think decimal64 doesn't normalize the
               | significand also. But I can't assess what I haven't used.
               | Mine is a snap judgment in favor of understanding more of
               | dec64 vs the wiki article. My general feeling is that
               | it's time for the scale of computing to tip away from
               | total correctness and efficiency, and more toward non-
               | awkward interfaces. But at bottom, I would try both and
               | then talk about it.
        
         | topaz0 wrote:
         | > it's just better
         | 
         | This assertion does not withstand scrutiny. You may like being
         | able to get True as the result of 0.1 + 0.2 == 0.3, but the
         | landscape will still be littered with rounding errors as soon
         | as you try to do anything nontrivial. (Or even plenty of
         | trivial things like expecting 1/6 + 1/6 to add to 1/3). So all
         | you gain is a false sense of security in exchange for less
         | precision and slower computation.
         | 
         | (Of course, there are plenty of tasks for which floats are just
         | wrong for the job, and you should transform the problem so that
         | you can use integers or rationals instead. For example, when
         | you are incrementing a number by (integer multiples of a) fixed
         | delta, just change units so you can count numbers of increments
         | as an integer, and change units back at the end.)
        
       | blackflame7000 wrote:
       | [dead]
        
       | [deleted]
        
       | JJMcJ wrote:
       | At first I was expecting an ill informed rant, until I saw it was
       | Julia Evans, who always digs down and then explains a subject
       | with great clarity.
        
       ___________________________________________________________________
       (page generated 2023-02-08 23:01 UTC)