Description
The midpoint between two endpoints in Range#bsearch
is computed as from + ((to - from) >> 1)
. This overflows if both from
and to
are the same signed integer type:
(-100_i8..100_i8).bsearch { true } # Arithmetic overflow (OverflowError)
(Int32::MIN..Int32::MAX).bsearch { true } # Arithmetic overflow (OverflowError)
as 200
and 0xFFFFFFFF
do not fit into an Int8
or an Int32
respectively. These calls of course should never raise, because the midpoint of two integers of the same type is guaranteed to fit into that type's range (after truncation).
The situation is more complicated if the two endpoints' types are different:
(Int8::MIN..UInt8::MAX).bsearch(&.>= -100) # -100?
(Int8::MIN..UInt8::MAX).bsearch(&.>= 200) # 200?
If these values were Int32
, the first call would yield 63
then -33
, and the second call would yield 63
then 159
. If we fix the yielded value's type to either Int8
or UInt8
, then an overflow would always happen. We should decide a sensible behavior here:
- Since any two integer types will overlap in range, any two values' midpoint will also fit into at least one type's range. Always try to find that type. The block must therefore accept a
B | E
. (This would also apply to Fix float number overflow in range rand method #13825, actually.) - Specify that only values fitting into
B
's range are allowed; if any midpoint exceedsB
's range, raiseOverflowError
even if a valid first element withinB
's range exists. The block then accepts aB
, similar toIndexable#bsearch
.
Extra union types in B
and E
should not matter; (1_i8.as(Int8 | Int32)..10_u8.as(UInt8 | UInt32)).bsearch
will only ever yield Int8 | UInt8
s, never an Int32
nor UInt32
.