Skip to content
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

Change printing of Float32/Float16 #7298

Closed
quinnj opened this issue Jun 18, 2014 · 20 comments
Closed

Change printing of Float32/Float16 #7298

quinnj opened this issue Jun 18, 2014 · 20 comments

Comments

@quinnj
Copy link
Member

quinnj commented Jun 18, 2014

Having mucked around in the float printing code quite a bit lately, I found it unfortunate that Float16 is more lame than Float32. It's also unfortunate that there's no literal syntax for Float16.

I'd like to propose the following:

1.0  #  => Float64
1.0f32  #  => Float32
1.0f16  #  => Float16

Currently, Float32 is printed as 1.0f0, which, while visually indicative of its type, doesn't mean anything more (the f0 at the end doesn't indicate anything). Using 1.0f32 instead gives a hint to the bit-size of the float you're dealing with, along with allowing a natural extension to other float types 1.0f16, 1.0f80, 1.0f128, etc. The other advantage is it could allow literal syntax as well for different floats, and we all know how great it is to have literal syntax for types. I'm not sure if changing 1.0f0 to 1.0f32 would be breaking or not (does anyone rely on how Float32's are printed?), but we could always parse 1.0f0 as Float32 for another release if need be.

Is this really that important? Maybe not. Would it provide a little more polish and consistency? I think so.

@andrioni
Copy link
Member

Just a note: the f0 in the end means a power of ten, the Float32 equivalent to e0, e.g.

julia> 10f0^10
1.0f10

@JeffBezanson
Copy link
Sponsor Member

The f0 does mean something. The 0 is a power of 10 and f replaces e.

@quinnj
Copy link
Member Author

quinnj commented Jun 18, 2014

Ah, thanks for the clarification guys. Not sure how I got that mixed up. I guess my proposal wouldn't really work then.....hmmm.

@quinnj
Copy link
Member Author

quinnj commented Jun 18, 2014

Ok, I figured out where I went wrong. C/C++ has 1e-45f as float literal syntax. I think I got confused because I interpreted Julia's syntax as simply 1e-45f0. Which comes to my new suggestion:

1e-45 || 1e-45f64  # => Float64
1e-45f32  # => Float32
1e-45f16  # => Float16

Similar idea, but exponentiation would always use e, whereas the f is solely used for syntax literal specification.

@JeffBezanson
Copy link
Sponsor Member

That conflicts with implicit multiplication by numbers. It's also hard to generalize e.g. to fixed point.

@quinnj
Copy link
Member Author

quinnj commented Jun 18, 2014

Oh drat. Seems there's not an easy solution here.

@quinnj
Copy link
Member Author

quinnj commented Jun 18, 2014

Ok, one more. It's actually been suggested before through a combination of #25 and #964.

1.0  # => default Float64
1.0::Float32  # => Obvious
1.0::Float16  # => Again
#exponentials
1e-45
1f-45 or 1e-45::Float32 # I'd be in favor of getting rid of the 'f' exponential syntax since it doesn't generalize well
1e-45::Float16

And as #25 suggests, I think we go further and do the same with Int8, Int16, and Int32. Any time you're working with those you're constantly doing typeof(ans) to make sure you got the right type.

@JeffBezanson
Copy link
Sponsor Member

That can't work either:

julia> 1.0::Float32
ERROR: type: typeassert: expected Float32, got Float64

It might work if there were more concise syntax for convert, but that's still limited by what the parser is able to parse. For example, you can't write BigFloats without using a string or extending the parser.

@quinnj
Copy link
Member Author

quinnj commented Jun 19, 2014

I think there's a case for having 1.0::Float16 actually be syntax in any scope for convert(Float16,1.0). It's already what it does on top of typeasserts in functions, so I don't think it's too crazy to expect a call to convert when used. I feel like that's been brought up in #1470, as well as #1090, for return type declarations.
Could the parser handle something like that?

1.0::BigFloat
1.0::Float64
1.0::Float32
1.0::Float16

Or maybe I'm not understanding the parsing problem. Is it that we don't want to litter code with all these calls to convert?

@JeffBezanson
Copy link
Sponsor Member

:: never calls convert. It either calls typeassert, or adds a declaration that will later call convert on assignment. I think having :: sometimes call convert will confuse people to no end, especially since this syntax is already a bit subtle.

The parsing problem is that if I write <long digit string>::BigFloat, the long digit string may have already been parsed as a Float64, rounding off the extra digits.

@quinnj
Copy link
Member Author

quinnj commented Jun 19, 2014

Part of the subtlety and confusion is that using :: in a declaration directly leads to calls to convert.
So yes, definitely the syntax is subtle because sometimes it's just asserting and other times it's actually leading to calls to convert.
All the more reason to make it consistent. The behavior, in a future world, could be:

f(a,b)::Int64    # => return type declaration; I know I'll get an Int64 back; :: has forceful behavior
1.0::Float32   # => float literal syntax that makes sure I get a Float32; :: has forceful behavior
function foo()
  x::Int16
end     # => declaration syntax works as it currently does, leading to calls to convert; :: has forceful behavior

With BigFloat parsing, is it just a "hard to get right" problem? Or not really possible problem?

@andrioni
Copy link
Member

As far as I remember (don't quote me!), the parser itself treats most numbers as numbers directly (using the femtolisp types) instead of just postponing this step to Julia, which makes a potential BigFloat literal something problematic.

@jakebolewski
Copy link
Member

The big float literal is problematic because not every floating point number is machine representable. You need a default float type in the parser because otherwise you would have inconsistent behavior with rounding if you still want to have floating point numbers as literal values in the ast.

@JeffBezanson
Copy link
Sponsor Member

"Forceful behavior" is not a meaningful concept. Right now :: does two things. That change would add a third: turning 1.0::Float32 into convert(Float32, 1.0). If we want to add special syntax for convert (and possibly use it for declarations too) and have :: always mean typeassert, that would be fine with me. But typeassert is here to stay.

Parsing can be handled; we are able to parse BigInt literals for example. But floats are harder because it is customary to write Float64 literals with extra digits to be sure you get the closest representable value to the one you want. We would need some kind of syntactic cue, but I would be against adding yet another meaning to :: for this purpose.

@StefanKarpinski
Copy link
Sponsor Member

I think a lot of the confusion about x::T stems from the fact that it is sometimes a type assertion and sometimes a variable declaration – and it depends on if you're in global or local scope. This is exacerbated by expectations from static languages.

@JeffBezanson
Copy link
Sponsor Member

We could add @float16_str and @bigfloat_str, but other than that I'm not sure what to do. I don't think it's so important to have Float16 literals. BigFloat literals are probably more useful.

@samhatfield
Copy link

Is there any possibility of a Float16 literal notation being introduced? It seems inconsistent to have literals for the other types but not Float16. Also, I would argue that Float16 has become more important in the past 5 years, with increasing interest from machine learners and (in my case) atmospheric modellers.

What about 1.0g0 (suggested by @milankl, continuing the e, f... pattern)?

@StefanKarpinski
Copy link
Sponsor Member

It would be a breaking change, so not until Julia 2.0 at the earliest.

@MasonProtter
Copy link
Contributor

MasonProtter commented May 17, 2019

One stopgap in the meantime could be

struct Float16Scalar
    exp::Int
end 
Base.:(*)(x, u:: Float16Scalar) = convert(Float16, x*10.0^(u.exp))

macro g_str(sn)
    Float16Scalar(Meta.parse(sn)::Int)
end

julia> 1g"0"
Float16(1.0)

julia> 2g"10"
Inf16

Inspired by Unitful.jl

@JeffBezanson
Copy link
Sponsor Member

Or of course a f16"1.0" string macro.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants