Rfcs: Consider infering floats from integer literals

Created on 24 Sep 2014  路  20Comments  路  Source: rust-lang/rfcs

A-coercions A-inference A-primitive T-lang postponed

Most helpful comment

If someone is not familiar with the accuracy problems of floating point numbers, I don't think sticking a ".0" at the end will make them realize.

All 20 comments

This would be nice for 1.0 or soon after. It would fix something first time users may easily run into on the rust-lang.org landing page. See https://github.com/rust-lang/rust/issues/23994 for more details.

Just pointing out that this seems to harmonize with the 2017 goal of "lower learning curve".

In languages like C/C++/Java, it works using an integer constant when assigning values to floats.

In languages like python,javascript,etc, everything is a "float", and it just works using "whole numbers" without any decimal point notation.

This is also a nice ergonomic boost.

I don't like Rust to infer floats from integer literals.

Think about this code that should work with your change:

fn main() {
    let x: f32 = 16_777_217;
    println!("{:?}", x);
    let y: f64 = 9_007_199_254_740_993;
    println!("{:?}", y);
}

It's going to print:

16777216
9007199254740992

Integer literals can't be represented exactly in f32/f64, so if you don't put a ".0" to them you are giving the programmer a false expectation of exactness. Copying the other languages is not an improvement here.

If someone is not familiar with the accuracy problems of floating point numbers, I don't think sticking a ".0" at the end will make them realize.

In languages like C/C++/Java, it works using an integer constant when assigning values to floats.

That's why 0.0 for floats have to be enforced through style guides.
I prefer this to be done by the language itself.

@petrochenkov @crumblingstatue In those languages (okay I'm not sure about Java, but in C and C++), it is an integer type (usually int, unless it exceeds the value of INT_MAX, in which case it becomes a long, then a long long), and then is converted to a float or double. This means that C and C++ don't strictly have integer literals being floats, but just a language defined coercion.

@leonardo-m In theory the compiler could produce an error in that case (if there is no point).

Arguably it should produce a warning even if there is one.

It's going to print:

9007199254740992

Integer literals can't be represented exactly in f32/f64, so if you don't put a ".0" to them you are giving the programmer a false expectation of exactness. Copying the other languages is not an improvement here.

It would be trivial to add a warning when a loss of information may occur during the implicit cast of a const value. That chain of thought could also lead to small constant positive integers to be implicitly castable as just about any numerical value. In fact, this would be MUCH safer than developers getting into the habit of "as" casting everywhere.

In fact, this would be MUCH safer than developers getting into the habit of "as" casting everywhere.

The wide usage of "as" should be deprecaterd, discouraged, warned-off, and clipped-off. Using .into() and T::from() is the way to go.

The wide usage of "as" should be deprecaterd, discouraged, warned-off, and clipped-off. Using .into() and T::from() is the way to go.

Unfortunately, there are various issues with from and especially into.

  • They don't work in a const context. You pretty much __have__ to use as in a const context. It is unsure if they ever will work, because making them const fn in the trait would be too limiting. I am interested in solving this issue in some way, but I'm not aware of anyone else interested. A generic numeric consts feature (similar to Go) would help alleviate this issue, but again, as far as I'm aware, there currently seems to be no interest in such a feature.
  • into is really stupid when it comes to type inference. It fails at even simple binary expressions.
    Rust fn main() { let a: u8 = 1; let b: u16 = 2; // let c: u16 = a.into() + b; // fails // ^^^^^^^^ cannot infer type for `T` let d: u16 = u16::from(a) + b; // works }
  • Using from a lot in a complex expression can get rather unwieldy, and can make the expression much less readable. I wonder if there could be a shorthand syntax sugar for from/into, similar to how ? is just syntax sugar for early returns with a few extras.

This is a real pain point. @leonardo-m's point isn't really valid because:

a) The compiler can easily warn about these situations - in fact it already does for integers:

error: literal out of range for i32
 --> src/main.rs:3:19
  |
3 |     let _a: i32 = 10000000000000000;
  |                   ^^^^^^^^^^^^^^^^^

b) You can already write a floating point number that can't be exactly represented:

fn main() {
    let x: f32 = 1.00000000000001;
    println!("{:?}", x);
}

Currently this gives no warning and prints 1.0.

Assuming there was a smart warning like Warning: floating point literal 1.0000000001 is equal to 1 is there any real downside to allowing this?

(I say "smart" because you probably don't want a warning about 1.5, etc.)

I'm not sure how one would check that needless precision was added, but simply checking that every non-zero digit actually affects the stored data is a good starting point.

I'd be concerned about divisions - if I write 3/2 will it suddenly become 1.5 because I assign it to a float rather than an int? Or would this inference only be for direct assignment?

That's a great point. I think an error here would still make a lot of sense. Allowing the implicit cast of literal constants to float doesn't have to mean allowing implicit casting in the operation or of its result.

I wouldn't call such a feature "implicit cast" because it would make literals behave as if they had .0, not like as (which has some unfortunate implications).

Anyway, I don't think there is a way to disallow 4/3 that doesn't disallow pretty much any other inference, with the exception of things like 1: f32 (at which point, why bother?).

I think we need some sort of intermediary literal type, that is guaranteed to be castable to a set of types.

The literal 1 would be castable as any numeric type, but the result of any operation involving could be a standard type.

let f:f32 = 1 / 3; would result in the usual error.

1 and 3 are assumed to be ints for the purpose of the operation, and the result of the operation is therefore an int.

Additionally, if that intermediary type is accessible to the user, we could have very easy to use constants that render most as-casting obsolete.

Something like:

const IMPORTANT_CONSTANT:lit = 10;
let a:f32 = IMPORTANT_CONSTANT;
let u:usize = IMPORTANT_CONSTANT;
let c:u8 = IMPORTANT_CONSTANT;

Should those types not be compatible, the usual warning would appear, which is not the case with the current:

const IMPORTANT_CONSTANT:lit = 1000;
let a:f32 = IMPORTANT_CONSTANT as f32;
let u:usize = IMPORTANT_CONSTANT as f32;
let c:u8 = IMPORTANT_CONSTANT as f32;

lgarczyn your const suggestion is very similar to how Go constants work (one of Go's better ideas) - they are untyped and infinite precision.

Constants do look quite powerful in go, but it does raise the question of what would be the result of

let x = 1 << 63;

If 1 is untyped instead of i32, the operation is legal, but only makes sense for an u64. Should rust guess the type of x?

Go uses infinite precision in numeric consts, which is well and good, but not exactly rust-like. Unless we add an entirely new, arbitrary-precision, compile-time numeric type.

On the subject of adding "Go constants" to Rust, see the interrelated and overlapping discussions in:
https://github.com/rust-lang/rfcs/pull/1945 RFC: Polymorphic Numeric Constants
https://github.com/rust-lang/rfcs/issues/1349 type inference for consts/statics
https://github.com/rust-lang/rfcs/pull/2507 RFC: Numeric literal types
https://github.com/rust-lang/rfcs/pull/2010 Const/static type annotation elision

I believe the main points that are relevant here are that:

  • much like #2507, tying this proposal to Go-like arbitrary-precision constants will probably just muddle the issue
  • according to data gathered in #2010, the obvious const type inference for integers would almost always get the wrong answer in practice. I'm not sure integers "that are supposed to be floats" would fare any better, so I'd want to see a similar data-gathering exercise to disprove that.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

camden-smallwood-zz picture camden-smallwood-zz  路  3Comments

rust-highfive picture rust-highfive  路  4Comments

clarfonthey picture clarfonthey  路  3Comments

p-avital picture p-avital  路  3Comments

rudolfschmidt picture rudolfschmidt  路  3Comments