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?
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)
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
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
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
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!
what do others think?
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.
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)
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).
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.
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)
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:
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
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!
You're right about the unsigned vs signed thing. You found my wetware bug
haha cool!
Okay, so any integer type seems safe to compare to any other
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!")
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.
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
for that to happen, someone would have to:
like what specifically goes in the blank there?
I can think of plenty of ways they could do something more error prone than what the builtin would have done! :big_smile:
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.
Kinda like forcing someone to pick if the want floor, ceil, round, trunc, or even a minimum bounded error comparison.
Instead of just defaulting to whatever float to int conversion happens to pick
In reality floats compared to integers can have a large enough range that < may need to return a tag (less, roughlyEqual, or greater)
The concern is about if a language should pick a default. Will that lead to more confusion or bugs for end users.
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.
those are all reasonable considerations, but at the same time I think that same argument applies to comparing two floats to each other too
They are the same format, so it applies less so. It should only apply for equality.
For less than, floats should have an exact ordering
And our floats don't have equality, so should be fine
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