-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
improve documentation of numeric literals #11081
Comments
This is all by design. |
And it's documented: http://docs.julialang.org/en/latest/manual/integers-and-floating-point-numbers/#integers. Also, see #197. Int128 and BigInt literals could use a mention, though. Have you read through the manual yet, @ScottPJones? The floating point issues seem like they should be a separate issue. Please try to keep issues focused on exactly one topic! |
I find that the flaws of binary floating point are severely exaggerated. While it is true that some applications do require accurate decimal representations, binary floats are here to stay and they aren't going away. Without support for computing with decimal floating point numbers that runs at speeds comparable to binary floating point numbers, the former are not very useful for practical computations. |
I don't think selecting an integer type based on length in base-10 digits is reasonable... |
Agreed, which is why we don't do that. Some people seem not to care for the selection of unsigned integer types based on the number of hex digits, but I generally find it to be really practical and intuitive. The only thing that seems fishy to me here is that over-long hex numbers probably shouldn't construct BigInts – i.e. |
@mbauman What precisely is off topic? My topic was "Inconsistencies in numeric literal handling". Is 0.0 not a numeric literal? |
@JeffBezanson My issue is that it is inconsistent. Why do hex numeric literals behave one way (i.e. scaled based on length), decimal integer literals behave another (based on value), and floating point literals a third way (always the same, no matter the number)?
It says nothing about the way hex literals are treated differently, nor that integers that happen to have a .0 or e+xx at the end don't get automatically promoted to BigFloat, in a way consistent with the way @JeffBezanson and @StefanKarpinski I didn't say that selecting an integer type based on length in base-10 digits is reasonable... just that it is NOT consistent with what is done with hex or floating point literals, one on length, one on value, the other fixed. @jiahao I don't think I ever exaggerated the flaws of binary floating point numbers... but unless you use a real binary (or octal or hex) for representing floating point literals, you are really dealing with decimal floating point literals, which you are converting to an inexact binary representation, and when you print it out, you are also not representing exactly the binary value... (unless you use around 53 or 54 digits for a Float64). |
@StefanKarpinski Your |
I don't think it's entirely an argument by authority. It means that these were deliberate decisions, with reasons behind them, not just some mess that happened because nobody was paying attention. It's not always obvious which features have good justifications, and people's initial impressions tend to differ. |
Yes, signed integer literals, unsigned integer literals, and floating-point literals behave differently, but there are good reasons for these differences, so the inconsistency isn't a bug. Duly noted that you don't care for this particular design decision. |
As for inconsistency, isn't it sometimes the case that different things are different? Integers overflow, floats saturate at Inf. Is that inconsistent? It seems to me that when somebody writes |
@ScottPJones kindly do not put words in my mouth. I did not say that decimal floats are useless, nor did I ever advocate ignoring the needs of any applications that require decimal floats. My point is that all floats can be read into The outputting of floats is a different matter entirely. Algorithms like |
Of course it's not off-topic; you defined the topic! I just didn't think the topic was very well focused - which is reflected in the responses here. I always find the best response to the issues I create when they are very specific and sharply pointed. I also try to search the manual and existing issues — and if I don't find anything I often say so. Do you bring up Floating Point numbers because you'd like to see automatic parsing as BigFloats if the precision is over-specified for Float64? Then create an issue "Automatic BigFloat parsing for large and precise numbers," citing integers as precedence. Or do you want Base 10 float support from the parser? That's a little different and has been discussed some before, too (#4278 (comment)). Each of these topics takes time from the developers. I don't mean to be insulting at all - I just want to make sure that we use all our resources effectively. |
@StefanKarpinski kindly do not put words in my mouth Did I say whether I cared or not about the design decision? No, I did say that it was confusing to people coming from elsewhere, and at least should be noted prominently in the appropriate section of the documentation, which I had read, and referenced here. An action item of this issue would be to add some appropriate warnings to the documentation... I don't consider myself a particularly good writer, but unless someone else steps up, I'll do so and issue a PR. Once I realized the rather different way that Julia treated hex constants from everybody else, and even from Julia's own integer constants and float constants, I was fine, I fixed the masking operations in my Julia code (just had to be a bit different from my C code). I didn't ever say that this feature should be removed. Everybody should stop being so defensive and taking things personally, IMO. |
@jiahao I don't think I was putting words in your mouth... here are your very own words:
Saying that they are "not very useful for practical computations" does seem rather strong! Please take a look at the following: |
Documentation improvements would be great. |
@JeffBezanson If @StefanKarpinski had pointed to the discussion, and not just stuck a "won't fix" immediately on it, then it wouldn't have been "argument by authority". What he did, certainly was. |
@StefanKarpinski There are still the issues I raised about the inconsistencies between the way floats are not promoted, but integers (both decimal and hex) are, so I think it is premature to change the title on me to "improve documentation" |
To everybody... I don't mean to be a PITA, I am just trying to have a reasoned discussion of some issues that I have found... whether they need to be fixed by better documentation (which is fine for me in the hex "length vs value" case, I wouldn't have run into the problem if there had been something in the documentation section for people coming from other languages), fixed by a syntax error (if that is the best approach for large hex literals... I'm not sure, and that may require separate discussion...), or by changing floating point literals to promote from Float64 to BigFloat in a fashion consistent with integer and hex literal promotion. |
Another thing that I think causes confusion, is that Julia conflates two separate things... whether a literal is represented as hex or decimal, and whether a number is signed or unsigned... |
@ScottPJones I stand by what I said. The presentation you showed does not change my opinion. I happen to be working right now with teams who are specialists in medical data and in financial data, and all the code they have shown me runs on hardware binary floats. Show me a single application that runs on modern hardware that Julia can run on where decimal floats are supported and are used because binary floats are genuinely harmful, and I will reconsider my position. |
Part of the reason you're not getting good answers here that these answers are already out there.
I don't think the documentation needs to defend every design decision. It simply must explain the behaviors adequately. You can fall back on secondary sources (GitHub, Google, StackOverflow) for rationales. |
On the topic of floats: I believe we give a syntax error for float literals that don't fit in Float64 (i.e. would overflow). Writing extra decimal digits is another matter entirely: it's a common practice of numerical library developers to put a few extra digits in constants to make sure they get the right value. Switching to BigFloat in those cases would be surprising, especially since it's hard to predict where the cutoff will be. FWIW I'm not a huge fan of giving BigInts from literals. It makes it that much harder to remove the dependency on a BigInt library. |
@JeffBezanson If it did give a syntax error, instead of silently truncating, I wouldn't have been bothered... My example was of an integer value, where the literal ended in .0. |
julia> 1e100000
ERROR: syntax: overflow in numeric constant "1e100000" |
@mbauman I didn't say the documentation needed to explain every design decision... just that the documentation section that I'd read particularly thoroughly (about significant differences from other languages) said nothing on these issues. I just think some better warnings about the differences in literals is needed there, to help people from wasting any time trying to figure out why something didn't work as expected... I was just getting errors... I didn't even know at first that the errors were caused by Julia treating hex literals differently from C/C++, so I could not have known to do the search you suggested. |
@StefanKarpinski Please check the more reasonable case that I was talking about: |
What do you want that to produce? This is how floating-point literals work in basically every language:
Same in C, C++, Java, etc. |
@jiahao That surprises me a bit, about the medical and financial teams you are working with... and I thought Mike Cowlishaw's telephone billing company example was rather compelling. |
@ScottPJones whenever you have a specific example, it's always good to show it up front --- avoids a lot of confusion! See my comment about extra precision in float literals. This is what numerical programmers expect. I would call it not "silent truncation" but "expected rounding". Given any float literal in decimal, it must be rounded to a particular binary floating point precision. Writing floating point numbers without excess digits requires a fancy algorithm that human beings can't run in their heads. You would have to fuss with your digit strings until you stopped getting syntax errors. |
@StefanKarpinski It seemed rather inconsistent to me, the promotion of Int to BigInt, but fixed Float64. |
@JeffBezanson Now, that's a better answer. I'd still say that the case with hex literals -> BigInt deserves at least a warning message from the compiler... since it is inconsistent with decimal literals, and is changing from an unsigned to a signed type... isn't that a violation of some type safety rules? |
@ScottPJones the example given in the slides is actually a straw man argument. To quote slides 10-11:
First, "rounded to the nearest cent" is ambiguous in IEEE-754/854, even for decimal floats. The rounding operation is defined only in the context of a global rounding mode, which is specified in these standards. Julia even lets you specify the rounding mode. It turns out that in this example, only one of these rounding modes produces the "erroneous" result of 73.0 instead of 74.0: julia> round(1.05*0.70*100) #default RoundNearest rounding mode recommended by IEEE
74.0
julia> round(1.05*0.70*100, RoundUp)
74.0
julia> round(1.05*0.70*100, RoundDown)
73.0
julia> round(1.05*0.70*100, RoundNearestTiesAway)
74.0
julia> round(1.05*0.70*100, RoundNearestTiesUp)
74.0 Second, anyone who does any serious computations knows to avoid premature rounding. Computations should only be rounded off at the very end, and never in the middle. This is why IEEE-754 takes pains to define all operations as if they were done in infinite precision, and only then apply a rounding operation to make the result float-representable. In the financial and medical projects I work on, the sensitive numbers like dosage and prices are all stored in proprietary binary formats, presumably to preserve representability. However the analytics all use double precision floats internally. To my knowledge, no one does anything more complicated than elementary arithmetic with decimal floats. The nice thing about Julia's type system is that we actually now have the ability to do more sophisticated things (like linear algebra) on decimal floats, now that there is a package that implements the latter. |
@jiahao Not sure why you think that is a straw man argument, and why do you think that that calculation is being rounded "in the middle"? There is one multiplication, followed by a round to 2 decimal places...
It is not true at all that no one does anything more complicated than elementary arithmetic with decimal floats... athough it is true that that is the vast majority of the computations are just elementary arithmetic... for those cases, the decimal arithmetic is often as fast as binary floating point... and back in the day when there was no hardware binary floating support, was actually much faster. I am very glad that Steve Johnson is doing the DecFP package, it is pretty much a necessity for what I'm working on... I would have had to do it myself (I was already starting to do so, using Cowlishaw's decNumber package). |
The title of Slide 10 is "Where it costs real money…" The implication is very clear that the example is meant to be used in the context of additional computations. (Otherwise, why would 1 cent be so onerous?) Legal and financial uses aside, summation on the rounded numbers should be avoided for accurate computations. The argument is a straw man because "wrong rounding" is not a good reason to reject binary floats. Did you notice that only the non-standard "RoundDown" mode produces the "wrong" answer in the example given?
Wrong. Just look at the Wikipedia article on the history of rounding:
...
While it is true that IEEE 754 was the first time a standardized interface was provided for different rounding modes on machines, the claim that "rounding up is all that people ever used" is simply false. Human computers consistently used "round to even" for statistical purposes, and there are machines that used "round to even" as the default rounding mode, even pre-IEEE 754-1985. An example is the machine used by the National Physical Laboratory of the UK in the 1960s, whose rounding behavior was documented by J. H. Wilkinson, "Rounding errors in algebraic processes", 1963, p. 4 as: |
…f documentation
@jiahao Why would you think there would be correct to have additional calculations on the intermediate, unrounded numbers? That is totally incorrect! If the telephone company bills me $0.74, then they'd better not be adding up $0.0349999 when trying to calculate the amount of tax charged that they have to pay the government! How can you say "legal and financial issues aside"? That's kind of the whole point... you have to sum up the rounded numbers, because the rounded numbers are the real value charged the customer... not $0.7349999 or even $0.735... What do you mean "non-standard"? I was taught in school, that from 0-4, you round down, 5-9, round up. That is also what is done when those tax payments are rounded... and do you really want to use something from Wiki as definitive? That statement about "Until the 1980's" is totally wrong in my experience... first off, for this sort of thing, people used fixed point, or decimal floating point, and for those, I've never seen anything but 5 rounding up as standard (and I don't mean "de facto" standard, I mean, standard as defined in ANSI standard computer languages... look up Cobol and Mumps, which were ANSI standards along with FORTRAN back in the 70's), secondly, even for binary floating point, most platforms did not even have floating point hardware, and even multiplication and division of integers were very costly operations back then). People who wanted consistent results used decimal arithmetic, because the software floating point libraries (and hardware on big machines) had different formats and each generated different results... It was a big mess back then to try to use binary floating point... The IEEE standardization in 1985 (which didn't get used everywhere until a good deal later... people were still using machines with IBM or DEC or xxx's floating point for years afterwards) was a great step forward... probably hardly anybody except some scientists would be using binary floating point if that hadn't happened... I'm still interested in what systems those financial and healthcare teams you are working with are using... especially for the healthcare teams, it is highly likely that the decimal arithmetic they are using is something I architected and implemented most of (a good friend, now at Partner's Healthcare, also worked on the implementation for IBM VM/CMS with me, I did the design and Intel implementation) back in 1986. |
…n C/C++ and Julia
I'm not disputing the fact that people use round up rounding, and that for business applications that might even be preferable. My point is simply that you have overstated the case that everyone used round up rounding pre-IEEE 754. There are people who have had experiences different from yours, and I have already provided evidence that proves it. You question my choice of citing Wikipedia. Fine. But you happily sidestep the fact that I have quoted you evidence from the primary literature on floating point computations that state very clearly a description of machines that do not use round up rounding. And it is not just any source: Wilkinson quite literally wrote one of the earliest books on floating point computations. Your claim that no one used anything other than "round up" is patently, demonstrably false, even though it may be indeed true that in your personal experience you have not encountered pre-standardization machines that did anything else. There are generations of statisticians who use floating point who know that rounding up introduces systematic errors into their computations that they should avoid.
In the context I used it, "non-standard" refers to the round down rounding mode. You should reread the example I posted. I thought I had made myself very clear: julia> round(1.05*0.70*100) #default RoundNearest rounding mode recommended by IEEE
74.0
julia> round(1.05*0.70*100, RoundUp)
74.0
julia> round(1.05*0.70*100, RoundDown)
73.0
julia> round(1.05*0.70*100, RoundNearestTiesAway)
74.0
julia> round(1.05*0.70*100, RoundNearestTiesUp)
74.0 The point is that
Therefore, I don't see that the example given on the slides presents the "dangers" of rounding correctly. While I can see that there can be problems, the slide deck happened to pick an example that doesn't work to illustrate the point. |
@jiahao Sorry, but your examples aren't the example Mike Cowlishaw was talking about, and the default in julia has the problem... julia> round(.7*.05*100)
3.0 Decimal floating point would give the expected answer (the one the government will insist you pay, i.e. Also, I didn't say that nobody used different rounding modes for binary floating point... as I said, until the mid 1980's, binary floating point was a mess, and for some years afterwards, because there were still a lot of platforms that used their own proprietary software libraries or hardware...
Looking at the rounding names in your example, I see that what I had always heard called "rounding up", is called RoundNearest, which is also the default in Julia (on the Internet, I saw that what I'm used to is also called "rounding half up"...). It is also called "banker's rounding"... I've even heard that that method was found used on tables dating back to the time of the Summerians, with a base 60 system... I never saw any other rounding mode than 5-9 rounding up in the ANSI standard languages of the time, or the calculators of the time either (which were all decimal arithmetic either). Here is another example: from Julia: julia> x = BigFloat(1.05)
1.0500000000000000444089209850062616169452667236328125e+00 with 256 bits of precision
julia> x * .7 *100
7.349999999999999844568776552478064619168935941990020956785867930344258169839122e+01 with 256 bits of precision
julia> round(x * .7 * 100)
7.3e+01 with 256 bits of precision I see the exact same error here, that was shown on Cowlishaw's presentation. BTW, I am still curious what system those healthcare teams you are working with use? Is that a secret, or do you not know? |
#11081 Add C/C++ section to Noteworthy differences documentation
In #8964, I was pointing out some serious inconsistencies that occur with the default or built-in numeric types in Julia, and how numeric constants are not handled consistently.
A decimal integer literal will start off as Int64 (or I imagine Int32 on 32-bit machines?), then go to Int128, and finally to Base.GMP.BigInt, depending on the value, not it's length (0, 00, 00000000...)
are all Int64, no matter how many leading 0s there are.
A hexadecimal literal, on the other hand, starts off as an unsigned integer, but it is the length, not the actual value, that determines it's type... i.e. 0x0 and 0x00 are both UInt8s, but 0x000 and 0x0000 are UInt16, and 0x00000 - 0x00000000 are UInt32... and so forth, up through UInt64, UInt128, and then, magically, it changes from an unsigned type, to a Base.GMP.BigInt...
This is very apparent if you use the ~ operator on the literal... a frequent thing to do with a mask value.
Floats are even worse - they are always Float64, even if Float64 is not large enough to represent the value (forgetting for the moment that binary floating point is not suitable for exactly representing base-10 literals...].
The text was updated successfully, but these errors were encountered: