[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)