-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Maybe deprecate BigFloat(f::FloatingPoint) constructor #4278
Comments
(2) is the best option, allowing conversion from Float64 only via The fact that floats are binary fractions is something you just have to get over. The fact is that |
I completely agree that it's insane to pretend that a number is a different number than it actually is. Hallucinating extra digits that aren't there is crazy town. I also really don't think that 2 is a reasonable option. Are we seriously going to make people write I think that having There's also a fifth option that wasn't listed: (5) Document that this is an issue and move on. |
What about the Ruby BigDecimal way? When initializing a BigDecimal with a Float, Ruby expects a precision argument or it throws an error: |
I would say that makes more sense for BigDecimal than for BigFloat. |
In addition to big"1.2" (or instead of) why not have a notation like 1.2b0 be a BigFloat (similar to how 1.2f0 is Float32). |
I have updated the Issue with your suggestions. I hope that is okay with you. @StefanKarpinski What do you argue is insane in the two first sentences? The problem with all of
@karatedog I agree with Jeff that the Ruby BigDecimal approach makes more sense for Decimal types than Float. The number of decimal digits is not really relevant when you are going to store it as a binary fraction anyway. (see the 2 at the end of BigFloat("0.1")) @BobPortmann That is a good suggestion, if they want to add more syntax. It will also be usable for BigInt if the decimal point is missing. Should we use a captial I realized that the |
The syntax |
Rather than introducing a new notation for BigFloat which basically reproduces the problem on smaller scale (2.1 still not representable) one could think of introducing a floating point notation for (big) rationals. In the end, one wants the BigFloat representation of 21//10 = 2.1 (= "2.1d0"?), or? |
I'd really like to stop eating away at the space of valid numeric juxtaposition syntaxes. The more we do that, the less people who don't know all of the language by heart are going to feel comfortable with using juxtaposition and are just going to avoid it because it is "brittle" and sometimes just doesn't mean what you wanted it to. We already have |
@ivarne – @JeffBezanson's statement that |
I strongly support @StefanKarpinski's fifth alternative, document it and move on. I really see no point in trying to hide floating point arithmetic, because this is exactly the kind of behavior that creates these issues, not to mention that it makes reasoning about what your code is doing much more difficult, and makes debugging rounding issues much worse. Case in point: julia> BigFloat(1.1 + 0.1)
1.20000000000000017763568394002504646778106689453125e+00 with 256 bits of precision
julia> BigFloat(string(1.1 + 0.1))
1.2000000000000002e+00 with 256 bits of precision
julia> BigFloat(string(with_rounding(RoundDown) do
1.1 + 0.1
end))
1.200000000000000000000000000000000000000000000000000000000000000000000000000007e+00 with 256 bits of precision Also, please notice that doing |
@mschauer - rationals have problems too, especially when combined with floating point representations of fractions that can't be represented accurately. There was a discussion somewhere if @StefanKarpinski - So in short you were arguing against suggestion 1. (Sorry for putting it first, it is not my first choice). Making a special case for the @andrioni - you are just using the |
Chiming in as someone likely to make this error I vote 2 followed by 5. |
Documenting the problem is not antithetical to doing something more drastic, and the current paralysis does not serve any useful purpose, so I went ahead and pushed a documentation change in 9a691d0. I'm not convinced that option @bigfloat y = exp(x) - 1 to perform numerically-delicate calculations in But I presume this would be a huge amount of work for something that seems like a pretty small problem. |
I support deprecating the However, it can be very convenient to provide a decimal notation which can be checked and translated by the compiler into a rational or arbitrary precision decimal number. Using strings means parsing literals at runtime with errors only detected when the code containing a decimal number encoded as a string is finally executed -- hopefully no later than unit testing... In fact, creating initialized arrays and matrices for unit tests themselves can benefit from the notational convenience of rational or arbitrary precision literal notation. For one not entirely artificial example, consider an array of market share values which must arithmetically sum to 1.0 as input to, say, a markov chain analysis. I would prefer:
...to either an array of string processed into an array of rational number or an array of expressions which construct rational numbers form string parameters. Though, to be honest, I would prefer even more to just write:
...using unadorned decimal notation with some other means used to tell the compiler that numeric literals in "this block," however identified, are to be parsed into rational numbers or arbitrary precision decimal numbers instead of IEEE floating point representation. |
For whatever it's worth, we do have a mktShare = [ 8//10, 1//10, 1//10 ] |
@pao I know. That is one of the many things I like about Julia and your code example is what I would use absent some means to express rational numbers using a decimal notation. However, a decimal notation may be a clearer expression of some values even when floating-point operations are not appropriate. Granted, it is a minor nit in a world where I have to use far more verbose syntax and cumbersome run-time solutions in other languages. Still, all numeric literals in computing can be represented internally as rational numbers and absent a literal syntax for a single rational number expressed as a ratio such as that provided by Julia, all numeric literals are finite rational numbers. Therefore, it does not seem unreasonable to want to use a common decimal notation for all numeric literals, if possible -- obviously, the decimal point and fractional digits are not appropriate for integers. But I do get that "reasonable to want" is not the same as "reasonable to implement" in either syntax or a lexical scanner... |
The |
Thanks @nalimilan. I added (a hopefully improved version of) that suggestion as |
This is possible too without parser changes by using a macro which delimits a block and transforms numeric literals into the desired forms. |
Scratch that, the AST will have the interpreted literal in it. Never mind. |
From Overloading Haskell numbers, part 3, Fixed Precision, it appears that Haskell provides for treating decimal numbers with a fractional part as rational numbers rather than floating-point literals in some cases. At least, that is what I gather from a quick reading of Haskell documentation and that blog post in which the author states:
I realize that is Haskell -- the sometimes impenetrable -- and here we are discussing Julia -- the generally comprehensible -- but it seems to offer an example in the wild of what I am looking for. Note, my interest is to be able to provide a literal number in familiar decimal notation, including a fractional part, and have it converted to a rational number without having already been converted to an IEEE floating-point representation in which precision may have been lost. Admittedly, not a high priority kind of request, but it does seem like something the compiler could manage, perhaps by delaying the conversion of the literal token into a machine or arbitrary precision rational number until the expression types have been determined by the parser. |
I agree that the ideal behavior would be to keep numbers exactly as written, until they are forced to some other particular type. Unfortunately this is hard to do in julia, since unlike Haskell our expression contexts do not have types. A literal needs to have a specific type; if a function only accepts |
@JeffBezanson I understand, but just a thought: What if numeric literals were not |
That's the trouble. This cannot be done transparently in Julia. As Jeff said, if a floating-point literal is not a f(x::Float64, y::Float64) = singnificand(x)*2.0^exponent(y) Under the scheme you're talking about, you cannot call this function as f(x::FloatLiteral, y::FloatLiteral) = f(float(x),float(y)) Now you can argue that you could just allow the original definition to apply to |
@StefanKarpinski I see. Then no, it does not seem remotely practical to treat float literals as rational numbers. Thank you for explaining. |
+1 for the objective of leaving literals "exactly as written". I found this thread when researching a Decimal type. Is there one proposed for Base? |
@StefanKarpinski Julia would have to keep it internally as a rational until it is used somewhere by the code, in which case it would be converted silently depending on whether a |
Not to mention that decimal fixed-point implementations tend to be even more problematic than floating-point, and rational arithmetic can get pretty expensive pretty fast. |
I vote for deprecation too. I think that using strings (
|
@rominf Your macro only works for floats with less than ~16 significant decimal digits. The parser will truncate the remaining digits. |
@ivarne Hmm, I didn't know that. |
@nalimilan I believe it could be made practical. Go has arbitrary precision compile time constants that get converted to Int or Float if they are used in a non-compile-constant context. I want to propose doing something similar.
If a function is called with a FloatLiteral argument at position where it explicitly declares a FloatLiteral argument, it will get FloatLiteral, if it does not declare any type or declares a Float64 type, it will get Float64. In line with this, I suggest making FloatLiteral a second-class citizen. It cannot be assigned into a variable (that would convert it into Float64), it is converted to Float64 before being used in a non-literal expression (0.5 * 0.6 is still FloatLiteral, 0.5 * a for some variable a first converts 0.5 to Float64). It can pretty much be only passed as an argument to a function (BigFloat constructor). One trick pony. |
Sounds interesting. The problem is that in Julia the distinction compile time vs. run time isn't as clear as in Go, so you'll necessarily expose that |
I don't think that introducing second-class, compile-time-only types is a satisfactory solution – it just complicated matters further. In Go, for example, you get completely different behavior if you operate on a literal than if you assign that literal to a variable and do the same operation on it, which, of course, confuses people. Instead of just explaining to people that computers binary instead of decimal – which is something they're going to need to know about to do numerical computing effectively – you also have to explain that there's this subtle difference between literal numbers runtime numbers. So now people have two confusing things to learn about instead of just one. |
I am using Julia only as "faster Octave", so I surely don't see all the consequences for the language. I just realized my example with 0.5 * 0.6 staying a FloatLiteral is very problematic because the programmer might want to redefine operators (something that Go does not allow, and for good reasons, while Julia wants that, and for good reasons too). If a FloatLiteral would always collapse into a Float64 every time it is touched except when it is passed as a function parameter or treated as a string, the whole thing provides only syntactic convenience over passing in strings. Then it is probably not worth having. On the other hand, it does not create the semantic difficulties as in Go (because then it has no semantics). What about attaching a string property to every Float64 that would contain the token in the source code that led to creation of that number? Possibly keeping the property around only "for a short time" (the same lifetime as my version of FloatLiteral was supposed to have)? Or storing it only if the string cannot be "losslessly" recovered from the binary representation of the number, otherwise computing it when needed? (suggestion # 3) I like suggestion # 6 the most, it would IMO work well and it does not require wild changes like # 3. |
I originally thought a float literal type could be a good idea, but I have come to change my mind: it would be really confusing to have
do different things. I think this problem could be alleviated somewhat by having a separate syntax for nonstandard numeric literals: I think postfix underscores could be nice:
|
On a related note, one thing I would like to have is a
At the very least, it would be great for teaching floating point.... |
I'm starting to feel like we should ditch numeric literal juxtaposition and use |
I, for one, would be willing to sacrifice implicit multiplication for this. |
+1 to that. I never use implicit multiplication in practice. |
On implicit multiplication, isn't |
@Mike43110 "4.x" is parsed as "4.0 * x" and does not always preserve the semantics of integer operations. A simple counterexample: julia> x=1;
julia> 10_000_000_000_000_001x == 10_000_000_000_000_000x #integer arithmetic
false
julia> 10_000_000_000_000_001.x == 10_000_000_000_000_000.x #floating point arithmetic
true |
Bump with new idea: What would happen if we just deprecated creating BigFloats from floats? If we forced them to be made from int's, rationals, or other more exact types, we would get rid of all cases with unexpected behavior. This does seem fairly radical, but I'm not sure it's a bad idea. |
I frequently create bigfloats from floats, for checking the precision loss of computations. In fact that's the main thing I use them for. |
Could there at least be a warning? It seems like a lot of hard to catch errors could result form expressions like |
It seems like this issue could be closed? I think the clear consensus of core devs here is that the current behavior of As I commented on discourse, my In principle, we could use the grisu trick in the julia> mybig(x::AbstractFloat) = parse(BigFloat, string(x))
mybig (generic function with 1 method)
julia> mybig(2.1) == big"2.1"
true though this is somewhat expensive and I think |
Even if people find it confusing, I don't think having |
There has been a long of discussion in the julia-users group that started with a user that used the
BigFloat(2.1)
constructor and expected to get the same result asBigFloat("2.1")
. The problem arrises because most floating point types in computers are stored as base2 fractions, but a programmer works in base10. A lot of base10 fractions can not be represented as a finite binary fraction, just likeone third = 0.1(base3) = 0.33333333333333...(base10)
. When programmers forget this property of computers they get unexpected results.As the current solution is confusing (for newcomers) and leads to very hard to find bugs, I want to propose some solutions and have it open for discussion on github (where it might be easier to find in the future than the High traffic julia-users list).
BigFloat(f::Union(Float64,Float32,Float16)) = BigFloat(string(f))
. This approach might be unexpected by experienced MPFR users and also lets programmers writeBigFloat(2.1^256)
which obviously will not give the desired result. See: RFC: Conversion from Float64 to BigFloat should go via string #1015BigFloatFromFloat64()
, orconvert(BigFloat, 2.1)
)big"2.1"
and or2.1b0
BigFloat
.signif <= 0
gives the current behavior. Deprecation can be done by givingsignif
a magical negative value that triggers a deprecation warning. This approach could also be used forbig
.The text was updated successfully, but these errors were encountered: