Stream: ideas

Topic: automatic casting for numeric comparisons


view this post on Zulip Richard Feldman (Nov 20 2022 at 04:12):

something just occurred to me when writing some rust code that used < to compare two numbers of different sizes (meaning I had to cast one to the type of the other): what if we had operations like < be Num *, Num * -> Bool and just automatically cast the smaller of the two numbers to the bigger of the two?

view this post on Zulip Richard Feldman (Nov 20 2022 at 04:13):

that's what I always find myself doing manually, after all, except it can be annoying to try to do that when one of the two numbers is Nat and the other isn't (which was the case in the Rust code I was writing)

view this post on Zulip Richard Feldman (Nov 20 2022 at 04:15):

the only real downside I can think of in terms of user experience is that it would mean the builtin would do some implicit extra computations, e.g. if you give Dec for one of the numbers and I64 for the other

view this post on Zulip Richard Feldman (Nov 20 2022 at 04:16):

but when I thought about that, I then considered cases like "what if one of the numbers is F64 and the other is I128?" - it is definitely possible to reliably give an answer to the question "is one of these bigger than the other" (without crashing), but there are definitely some edge cases there which (if I'm being honest) I would not normally bother to account for myself

view this post on Zulip Richard Feldman (Nov 20 2022 at 04:18):

for example, since the biggest I128 is bigger than the biggest F64, I might be tempted to convert the F64 to I128 and then compare those (since converting the other way around could fail), but then I'm losing the fractional component - so if the integer component of the F64 happens to be exactly the same as the I128 integer, my "convert to I128 and then compare" will incorrectly return that they are equal when in fact they aren't

view this post on Zulip Richard Feldman (Nov 20 2022 at 04:18):

so thinking through that example made me realize that this isn't just a convenience, it's also potentially a way to make things less error-prone!

view this post on Zulip Richard Feldman (Nov 20 2022 at 04:18):

what do others think?

view this post on Zulip Luke Boswell (Nov 20 2022 at 04:21):

It sounds to me like a really helpful and convenient feature to have builtin. :+1: I can't really think of any major downsides, thought I'm no an SME in this area.

view this post on Zulip Kevin Gillette (Nov 20 2022 at 16:39):

For comparisons where this yields a bool or Ord/Cmp or equivalent, I am confident this is safe within the same family of types, so an I8 can always safely be casted into any other signed integer type.

I suspect this is also the case for F32 -> F64 (i.e. if every F32 value is value-representable as an F64).

I don't think this would be wise for crossing type families, since certainly not every value of an I8 is representable as a U128, nor for F64 -> Dec.

Even in cases where it is guaranteed to permit a clean conversation, such as U16 -> I32, I believe the convenience of cross-family casting wouldn't be worth the potential confusion arising from the the rules that control when that could occur. For many of us (through experience or intuition), those rules may be obvious, but for many others, I suspect it would be non-obvious and surprising (i.e. we might get bug reports about an intentional design decision)

view this post on Zulip Kevin Gillette (Nov 20 2022 at 16:48):

If we wanted cross-family conversions for comparisons, then perhaps the result could be "maybe" instead of merely just true or false. iow, is this Dec greater than this F64? The answer would be maybe if, after accounting for precision, either is found to be within the other operand's value and neighboring value (I forgot the term for this).

https://en.m.wikipedia.org/wiki/Unum_(number_format) broaches this with the concept of an error range (i.e. every value is actually a range of possible values due to compounding error, where bespoke constants have identical upper and lower bounds).

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 16:52):

That's a really good point. Any F64 above 2^53 is no longer directly comparable to an integer. Actually, if you include decimal places and rounding, it would be a smaller number than 2^53.

view this post on Zulip Kevin Gillette (Nov 20 2022 at 17:03):

Indeed, at higher values within an I64 range (say around 2^62), each "next" F64 value skips over multiple integers, and so which way an F64 and an I64 compare at values within that range could flip merely due to error in the F64 causing its value to jump by 16, for example (i.e. it could be 8 greater than the I64, thus clearly greater, but with a trivial, not-mathematically-relevant change in instruction ordering in a prior computation, would end up being 7 less than the I64).

Integers and floats are not especially compatible in their use or behavior, and making them too convenient in terms of interoperability could lead to casual mistakes that otherwise could have been avoided (i.e. keeping some friction in the right parts of the language to guide conscientious choice of types)

view this post on Zulip Richard Feldman (Nov 20 2022 at 17:17):

that's a general F64 problem though - any comparison involving any F64 has this concern; I don't think this situation is unique :big_smile:

view this post on Zulip Richard Feldman (Nov 20 2022 at 17:18):

I don't understand the "comparing I8 to U16" concern - I can always tell you which of those two is greater than the other (or if they're equal), so it doesn't seem like it would be error prone

view this post on Zulip Richard Feldman (Nov 20 2022 at 17:19):

I agree that it would be surprising to me if it worked and gave me the correct answer ever time, given how in most languages it wouldn't, but that seems like a good kind of surprise to me!

view this post on Zulip Kevin Gillette (Nov 20 2022 at 17:20):

You're right about the unsigned vs signed thing. You found my wetware bug

view this post on Zulip Richard Feldman (Nov 20 2022 at 17:20):

haha cool!

view this post on Zulip Kevin Gillette (Nov 20 2022 at 17:21):

Okay, so any integer type seems safe to compare to any other

view this post on Zulip Kevin Gillette (Nov 20 2022 at 17:26):

I think there's some nuance still to integer vs float or integer vs Dec, depending (i can't recall if Dec is fixed point). Integers have perfect precision, and when explicitly converting a float to an int, you're essentially vouching for the precision of the float (you're shedding the error). That seems different to me than an implicit conversion ("Oh, the compiler did this for me without my asking, so it must be an especially safe and reasonable thing in the general case. Definitely no bugs here then!")

view this post on Zulip Kevin Gillette (Nov 20 2022 at 17:33):

If Dec is fixed point, then I think that means that the error, relative to the integer sequence, is also bounded and fixed, and thus may be okay to implicitly convert for comparisons.

iow, based on my loose, possibly false understanding, fixed point error compound by shedding fractional digits rather than by potentially inflating the magnitude of values: while floats will get exponentially worse answers relative to the integer sequence further from zero, fixed point would get consistent behavior throughout its range, at the expense of a smaller value range.

view this post on Zulip Richard Feldman (Nov 20 2022 at 17:56):

I understand that concern in the abstract, I'm just struggling to convince myself that having < automatically compare floats to non-floats will somehow lead to float imprecision bugs that wouldn't have happened otherwise

view this post on Zulip Richard Feldman (Nov 20 2022 at 17:58):

for that to happen, someone would have to:

view this post on Zulip Richard Feldman (Nov 20 2022 at 17:59):

like what specifically goes in the blank there?

view this post on Zulip Richard Feldman (Nov 20 2022 at 18:02):

I can think of plenty of ways they could do something more error prone than what the builtin would have done! :big_smile:

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 18:38):

I think it is more about choosing a default than it is about float imprecision bugs. The comparison will be deterministic. It just may be the case that the behaviour should really be decided by a tag union of options rather than always the same.

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 18:39):

Kinda like forcing someone to pick if the want floor, ceil, round, trunc, or even a minimum bounded error comparison.

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 18:39):

Instead of just defaulting to whatever float to int conversion happens to pick

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 18:40):

In reality floats compared to integers can have a large enough range that < may need to return a tag (less, roughlyEqual, or greater)

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 18:41):

The concern is about if a language should pick a default. Will that lead to more confusion or bugs for end users.

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 18:42):

It may be the case the default is good enough, but i think it is possible that users should always be explicit here. So we don't want a default. We want to make users think about the choice. Ultimately, if they really don't care about the choice, they can just use Num.toI64 on the float.

view this post on Zulip Richard Feldman (Nov 20 2022 at 18:51):

those are all reasonable considerations, but at the same time I think that same argument applies to comparing two floats to each other too

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 19:01):

They are the same format, so it applies less so. It should only apply for equality.

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 19:01):

For less than, floats should have an exact ordering

view this post on Zulip Brendan Hansknecht (Nov 20 2022 at 19:01):

And our floats don't have equality, so should be fine

view this post on Zulip Kevin Gillette (Nov 20 2022 at 19:54):

those are all reasonable considerations, but at the same time I think that same argument applies to comparing two floats to each other too

Yes :) And we did have a few threads about how to deal with float comparisons. iirc, we arrived at a decision in that case, but peripheral topics will always be a bit fuzzy, because floats are intrinsically fuzzy.


Last updated: Jun 16 2026 at 16:19 UTC