8 ms·
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
by dec0dedab0de 4y ago
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.
- lifthrasiir 4y agoThere 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-objects https://docs.python.org/3/library/decimal.html#context-objec...
- scaredginger 4y agoWouldn't seriously suggest doing this, but rationals with big integers would have exact results for all the common operations
- runeks 4y agoI started out using the Haskell “Rational” type [1] (which is exactly what you mention) for https://cryptomarketdepth.com/ 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/Prelude.html#t:Rational https://www.stackage.org/haddock/lts-20.10/base-4.16.4.0/Pre...
- lifthrasiir 4y agoThis 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-integer-division.html https://python-history.blogspot.com/2009/03/problem-with-int...
- runeks 4y agoVery interesting. Thank you for sharing this.
- scaredginger 4y agoYup, it's a terrible idea, even if theoretically possible
- crdrost 4y agoI 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 4y agojust yesterday someone commented about https://fredrikj.net/calcium/index.html 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 4y agoCalcium 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.)
- scubbo 4y ago> 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 4y agoNormally 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-formulas-but-how-common-are-those-really https://peps.python.org/pep-0465/#so-is-good-for-matrix-form...
- scubbo 4y agoInteresting 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 4y ago> 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.
- rqtwteye 4y agoRounding 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 4y ago> 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 4y agoTotally agree that storing a version in a float is stupid but that's where we are :-(
- int_19h 4y agoAnd 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.
- Spivak 4y agoI would love that. f = float(closest_to=0.1) You can't really mess up programmer expectations like this.
- bruce343434 4y agofixed point numbers!
- fsloth 4y agoI 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 4y agoParent 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 4y ago[dead]
- dec0dedab0de 4y agoBecause 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 4y agoIf 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.
- nailer 4y ago> 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.
- billythemaniam 4y agoIn 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 4y agoErr it really depends on what you're doing. Integer division and modulo is still not fast.
- billythemaniam 4y agoDefine "fast" please.
- ClumsyPilot 4y agoOnce 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.
- dahfizz 4y ago> 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.
- Someone 4y agoAnd then you get “why isn’t 3 × ⅓ equal to 1?” and similar questions. “Use rationals” would only postpone the issue to “Why isn’t (√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.
- dylan604 4y ago> 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)² 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.
- int_19h 4y agoYes, 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.
- eru 4y ago> [...] defaulted to decimal for the literals, [...] Why not rational numbers?
- lmm 4y agoBecause they have more confusing edge cases. Decimals getting rounded might be annoying, but it's at least understandable.
- trashtester 4y agoLet's say you and two friends are buying a group ticket to some show. The ticket costs $100. Now try to find a way to pay exactly 1/3 each. Money transfers tend to involve payments with fixed precisions. For cash, it's often rounded to the smallest available coin, for credit cards, you can add a few more digits, but it will still be fixed point. When you settle a transaction, you typically need to pay exactly the amount defined by the transaction because some system is doing some check that the amount is exactly right. So in my example above, if your friends are paying $33.33 you may have to pay $33.34 to make the transaction go through. When you're writing systems involved in actual money transfers (or accounting) you tend to be better off using fixed point decimal, with predefined precision for each variable involved. Within a calculation, it may be fine to use float, but the number going into ledgers or transactions need to have the predefined precision.
- eru 4y agoYes, if you want to work with an existing system, you want to follow the conventions of that system. Fixed point (ie calculate everything in cents) is a good convention for lots of things to do with money. But it's not a good convention to have as the default in a language like Python. > Within a calculation, it may be fine to use float, but the number going into ledgers or transactions need to have the predefined precision. I was not suggesting the use of float. I was suggesting using rational numbers by default. See eg https://docs.python.org/3/library/fractions.html https://docs.python.org/3/library/fractions.html
- mysterydip 4y agoWould 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 4y agoIEEE double gives you 15 digits and a much larger dynamic range, so the tradeoff is just not worth it except for specialized applications.
- embedded_hiker 4y agoThis 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.
- londons_explore 4y agoIt'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 4y ago> 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 4y agoThis 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 4y agoSure, 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 4y agoThis 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 4y agoThere'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
- initplus 4y agoYou could overload operators like + and / so they evaluate to an AST representing the calculation rather than the actual calculated value. Then you need to do some analysis on your AST to try and restructure it in a way that preserves as much precision as possible. You don’t really need Haskell for this though, in theory it will work in any language (but more ergonomically if you have operator overloading).
- hn_throwaway_99 4y agoHonestly, 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 4y agoIt'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.
- mdouglass 4y agoThanks for the encouragement to look up lisp's numeric tower (https://en.wikipedia.org/wiki/Numerical_tower https://en.wikipedia.org/wiki/Numerical_tower), that was interesting to compare to the languages I'm more familiar with.
- kazinator 4y agoIncluding Lisp. Not every Lisp dialect has rationals.
- cbolton 4y agoWhat 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/original/2X/4/4612397a30b75f0c3d7c48072f142d01d2309fc9.png https://global.discourse-cdn.com/business5/uploads/julialang...
- jwmerrill 4y agoRaku 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 https://docs.raku.org/syntax/Number%20literals [1] https://docs.raku.org/type/Rat https://docs.raku.org/type/Rat
- lizmat 4y agoActually, the limited precision is the default (caused by the numerator as well as the denominator having to fit in a 64-bit integer). By default, when it not longer fits, it will convert to floating point (called Num in Raku). But this behaviour can be set: another alternative is for the numerator and denominator both being big integers. This gives you infinite precision rational numbers, at the expense of potentially needing infinite CPU and/or RAM.
- fnordpiglet 4y agoDo 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.
- dahfizz 4y agoNot 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 4y agoIEEE 754 actually does specify a decimal floating point format since 2008, but I don’t think it’s widely implemented.
- dahfizz 4y agoDo 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 4y agoIEEE 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 4y ago
- toolslive 4y agoCOBOL has it. https://en.wikipedia.org/wiki/COBOL#PICTURE_clause https://en.wikipedia.org/wiki/COBOL#PICTURE_clause
- MayaFey 4y agoIs that why banks are known for using it?
- int_19h 4y agoMore 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?
- aidenn0 4y agoCommon 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 4y agoI 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 4y agoYup; 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.
- sfpotter 4y agoThis is an insanely bad idea. You think Python is slow now, wait’ll you see it after this “improvement”.
- toolslive 4y agoWhy? the hardware supports it. https://en.wikipedia.org/wiki/Intel_BCD_opcode https://en.wikipedia.org/wiki/Intel_BCD_opcode
- sfpotter 4y agoRead 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 4y agoSorry, 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 4y agoJudging 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.
- toolslive 4y agoIt's fully supported. However, some parts only via a library. Anyway, to drill down my argument. It doesn't matter how slow or fast it is: python's floats ints,... are boxed. Your performance is down the drain before you even start looking at the value at hand. If you want any kind of performance at all while crunching numbers you will not be doing it in Python (which is exactly what happens when you use things like numpy)
- otabdeveloper4 4y agoa) 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.
- lmm 4y ago> Trigonometry and logarithms are vastly more important than having the numbers be printed pretty Citation needed. I suspect more people are doing accountancy than physics simulations.
- snickerbockers 4y agoYou 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.
- im3w1l 4y agoI 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.
- pdonis 4y ago> 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".
- gorgoiler 4y agoRuby sort of does! The type of 1/3 is Rational!
- esoterica 4y agoYou are conflating fixed point vs floating point and decimal vs binary, which are entirely different things. You can have decimal floating point numbers and binary fixed point. You realize that decimal numbers also have the exact same types of rounding issues as binary numbers right? The only difference is that the former allows you to divide by both 2 and 5 cleanly, whereas the latter only lets you divide by powers of 2. If you want to divide by 3, 7, 11, or compute a square root or an exponent, using decimals is not going to save you from having to reason about rounding.