Stream: general

Topic: Float to int conversions


Federico Mena Quintero (Apr 22 2020 at 17:03, on Zulip):

I'm tickled that https://github.com/rust-lang/rust/pull/71269 is happening. Are there plans for checked conversions, too? Something like fn i32::checked_from(f64) -> Result<i32, DoesntFit> ?

simulacrum (Apr 22 2020 at 17:07, on Zulip):

@Simon Sapin had a pre-RFC for a bunch of such methods (I'm not sure if that particular one is in there).

Federico Mena Quintero (Apr 22 2020 at 17:10, on Zulip):

ah, https://internals.rust-lang.org/t/pre-rfc-add-explicitly-named-numeric-conversion-apis/11395 ?

simulacrum (Apr 22 2020 at 17:10, on Zulip):

I believe so yeah

Federico Mena Quintero (Apr 22 2020 at 17:11, on Zulip):

excellent, thanks. I'll subscribe to that.

simulacrum (Apr 22 2020 at 17:11, on Zulip):

checked_from might be too simplistic (though could be a reasonable default). it's not very clear to me how rust will eventually support rounding modes etc in floating point well

simulacrum (Apr 22 2020 at 17:11, on Zulip):

(or whether we even want to)

Federico Mena Quintero (Apr 22 2020 at 17:12, on Zulip):

oh, hmm, that topic is closed now

simulacrum (Apr 22 2020 at 17:13, on Zulip):

yeah it's an old pre-rfc, I think Simon ran out of time for it

Simon Sapin (Apr 22 2020 at 17:14, on Zulip):

Yeah I still want to make a proper RFC, help gathering prior art would be appreciated

Federico Mena Quintero (Apr 22 2020 at 17:14, on Zulip):

given the length of #10184, that's perfectly understandable :)

Simon Sapin (Apr 22 2020 at 17:14, on Zulip):

@Federico Mena Quintero Do you mean checking for overflow and NaN? Or should non-integer values also return an error instead of rounding?

Federico Mena Quintero (Apr 22 2020 at 17:15, on Zulip):

@Simon Sapin so I just ran into a bug in librsvg where a big positive"f64 as i32" gives back a negative number, and in that case I don't want saturation; I want to know that it won't fit so I can do something else

Simon Sapin (Apr 22 2020 at 17:16, on Zulip):

But fractional values should still be rounded/truncated?

Federico Mena Quintero (Apr 22 2020 at 17:17, on Zulip):

I guess I want to check for overflow, as in not fitting within the int's range. I'm not sure about NaNs; since this is a checked operation, they should probably also yield an error

Federico Mena Quintero (Apr 22 2020 at 17:17, on Zulip):

@Simon Sapin yeah, round to zero is sensible for my case

Simon Sapin (Apr 22 2020 at 17:18, on Zulip):

Ok, I think this is "Fallible approximate" that I mention in future possibilites

Federico Mena Quintero (Apr 22 2020 at 17:18, on Zulip):

... I was looking at the docs, found the unstable approx_unchecked_to, got really happy that something there is happening already, and then went to look back at the issues/rfcs :)

Federico Mena Quintero (Apr 22 2020 at 17:21, on Zulip):

ah, got it, indeed the "fallible approximate" case would cover this

Simon Sapin (Apr 22 2020 at 17:24, on Zulip):

Unchecked is the one that has Undefined Behavior, so probably not what you want :)

Simon Sapin (Apr 22 2020 at 17:26, on Zulip):

It’s what as does in current Rust versions. as will likely be changed to saturate which has extra runtime cost. That cost is typically low, but for the few cases where it isn’t (and the values are known not to overflow) we’re adding unsafe fn to_int_unchecked with the old behavior (which also matches C and C++ casts)

Federico Mena Quintero (Apr 22 2020 at 17:41, on Zulip):

yeah, librsvg has a lot of f64 as i32 stuff :( Will start auditing it soon. I think saturating casts could help that code in some cases; for some others I really want to know about overflow. I can see that carefully-written pixel-crunching stuff would like the unchecked casts...

Federico Mena Quintero (Apr 22 2020 at 17:43, on Zulip):

btw, let me thank you in passing for html5ever / kuchiki / all those. They are super useful as crates and as examples of how to implement SVG lookalikes :)

RalfJ (Apr 22 2020 at 20:43, on Zulip):

Federico Mena Quintero said:

Simon Sapin so I just ran into a bug in librsvg where a big positive"f64 as i32" gives back a negative number, and in that case I don't want saturation; I want to know that it won't fit so I can do something else

that sounds like you ran into UB, and cannot happen once as saturates (as in, once https://github.com/rust-lang/rust/pull/71269 lands)

RalfJ (Apr 22 2020 at 20:44, on Zulip):

a positive float is then guaranteed to give a positive int

RalfJ (Apr 22 2020 at 20:44, on Zulip):

(including pos. infinity, which returns i32::MAX)

scottmcm (Apr 22 2020 at 22:49, on Zulip):

@Federico Mena Quintero you might also be interested in https://github.com/rust-lang/rfcs/pull/2484, which hit the possibility explosion pretty hard

Federico Mena Quintero (Apr 22 2020 at 22:50, on Zulip):

@RalfJ oh yeah, that was definitely UB and a bug. A carry-over from the C code, unfortunately.

Federico Mena Quintero (Apr 22 2020 at 22:53, on Zulip):

@scottmcm thanks, I'll read that

someone just suggested using the cast crate which sounds like exactly the low-impact solution we need right now

RalfJ (Apr 23 2020 at 07:01, on Zulip):

Federico Mena Quintero said:

RalfJ oh yeah, that was definitely UB and a bug. A carry-over from the C code, unfortunately.

to be clear, this was/is a bug in rustc. we shouldn't have UB from safe as casts.

RalfJ (Apr 23 2020 at 07:02, on Zulip):

So I think your problem is solved by https://github.com/rust-lang/rust/pull/71269. is your question how to get a fix without waiting for that PR?

Federico Mena Quintero (Apr 23 2020 at 16:23, on Zulip):

RalfJ said:

So I think your problem is solved by https://github.com/rust-lang/rust/pull/71269. is your question how to get a fix without waiting for that PR?

Oh, no, the code in librsvg is wrong even with saturating casts; it really needs to check if there's an overflow. I'm happy that "as" is being finally un-UBed with saturation. As for my problem I'll audit our "as" casts (lots of them, unfortunately, from the initial C to Rust conversion when I didn't know what I was doing) and replace them with something correct, I hope.

RalfJ (Apr 24 2020 at 12:55, on Zulip):

I dont know what an "overflow" means when casting float-to-int

RalfJ (Apr 24 2020 at 12:55, on Zulip):

but anyway it seems you have a plan forward so that's good :D

RalfJ (Apr 24 2020 at 12:56, on Zulip):

maybe you mean 200.0 as i8? but the result is 127, I wouldnt say it "overflows" -- in the linked issue we say that it "saturates". sure the cast loses information, but then so does 1.5 as i8.

Laurențiu Nicola (Apr 24 2020 at 13:41, on Zulip):

I think they're asking for something like fn checked_to_i32(self) -> Option<i32>, that never saturates but returns an error or None.

RalfJ (Apr 24 2020 at 14:04, on Zulip):

and then someone asked above what it should do on 1.5, and indeed I have the same question

RalfJ (Apr 24 2020 at 14:04, on Zulip):

usually a "checked" cast should only succeed if the result is exactly representable

RalfJ (Apr 24 2020 at 14:04, on Zulip):

for int-int, overflow is the only concern; for float-int we need to consider saturation and rounding

Laurențiu Nicola (Apr 24 2020 at 14:08, on Zulip):

"Whatever 1.5 as i32 does".

Laurențiu Nicola (Apr 24 2020 at 14:08, on Zulip):

It's implied that an f32 won't be exactly representable as an i32 most of the time

RalfJ (Apr 24 2020 at 14:21, on Zulip):

so... 127.99 as i8, is that "rounding" or "saturation"?

RalfJ (Apr 24 2020 at 14:21, on Zulip):

also I am somewhat curious in which cases rounding would be okay but saturation undesired, that seems awfully specific to me

RalfJ (Apr 24 2020 at 14:22, on Zulip):

RalfJ said:

so... 127.99 as i8, is that "rounding" or "saturation"?

answering myself: probably rounding. I guess the rule is "anything for which the unchecked cast has defined behavior returns Some; everything else returns None".

Laurențiu Nicola (Apr 24 2020 at 14:27, on Zulip):

Which is totally fine if you interpret checked as "like unchecked but tell me instead of triggering UB"

RalfJ (Apr 24 2020 at 14:27, on Zulip):

right, what remains is my question for a usecase

RalfJ (Apr 24 2020 at 14:28, on Zulip):

I can understand caring about information loss, but separating saturation from rounding seems really odd

Laurențiu Nicola (Apr 24 2020 at 14:28, on Zulip):

Maybe some audio processing code where you want to detect and warn in case of saturation (because it might be audible)

RalfJ (Apr 24 2020 at 14:28, on Zulip):

the thing thats "unchecked" about the unchecked cast isn't motivated by some usecase, it's just how LLVM and C happen to work

Laurențiu Nicola (Apr 24 2020 at 14:31, on Zulip):

If I'm processing audio as float, then converting it back to integer, rounding is perfectly normal, as I'm quantizing to my target domain. Saturation is "okay" (better than UB, for sure), but it's still useful to know when it happens.

Laurențiu Nicola (Apr 24 2020 at 14:32, on Zulip):

I guess the workaround is to do the range check in float instead.

RalfJ (Apr 24 2020 at 14:38, on Zulip):

Laurențiu Nicola said:

If I'm processing audio as float, then converting it back to integer, rounding is perfectly normal, as I'm quantizing to my target domain. Saturation is "okay" (better than UB, for sure), but it's still useful to know when it happens.

sounds reasonable, but pretty niche

Asa Zeren (Apr 24 2020 at 15:48, on Zulip):

Perhaps a solution would be to have checked that will fail on saturation or rounding, and then something like reporting (bad name) that would return something like:

struct FloatToIntReport{
    result: i32,
    rounded: bool,
    saturated: bool,
}
cuviper (Apr 27 2020 at 16:47, on Zulip):

FWIW, the conversions in num-traits are properly checked for float to int

Last update: May 29 2020 at 18:05UTC