@@ -67,42 +67,92 @@ extends scala.collection.AbstractSeq[Int]
67
67
{
68
68
override def par = new ParRange (this )
69
69
70
- private def gap = end.toLong - start.toLong
71
- private def isExact = gap % step == 0
72
- private def hasStub = isInclusive || ! isExact
73
- private def longLength = gap / step + ( if (hasStub) 1 else 0 )
74
-
75
- // Check cannot be evaluated eagerly because we have a pattern where
76
- // ranges are constructed like: "x to y by z" The "x to y" piece
77
- // should not trigger an exception. So the calculation is delayed,
78
- // which means it will not fail fast for those cases where failing was
79
- // correct.
80
70
override final val isEmpty = (
81
71
if (isInclusive)
82
72
(if (step >= 0 ) start > end else start < end)
83
73
else
84
74
(if (step >= 0 ) start >= end else start <= end)
85
75
)
86
76
77
+ if (step == 0 ) throw new IllegalArgumentException (" step cannot be 0." )
78
+
79
+ /** Number of elements in this range, if it is non-empty.
80
+ *
81
+ * If the range is empty, `numRangeElements` does not have a meaningful value.
82
+ *
83
+ * Otherwise, `numRangeElements` is interpreted in the range [1, 2^32],
84
+ * respecting modular arithmetics wrt. the unsigned interpretation.
85
+ * In other words, it is 0 if the mathematical value should be 2^32, and the
86
+ * standard unsigned int encoding of the mathematical value otherwise.
87
+ *
88
+ * This interpretation allows to represent all values with the correct
89
+ * modular arithmetics, which streamlines the usage sites.
90
+ */
87
91
private val numRangeElements : Int = {
88
- if (step == 0 ) throw new IllegalArgumentException (" step cannot be 0." )
89
- else if (isEmpty) 0
90
- else {
91
- val len = longLength
92
- if (len > scala.Int .MaxValue ) - 1
93
- else len.toInt
94
- }
92
+ val stepSign = step >> 31 // if (step >= 0) 0 else -1
93
+ val gap = ((end - start) ^ stepSign) - stepSign // if (step >= 0) (end - start) else -(end - start)
94
+ val absStep = (step ^ stepSign) - stepSign // if (step >= 0) step else -step
95
+
96
+ /* If `absStep` is a constant 1, `div` collapses to being an alias of
97
+ * `gap`. Then `absStep * div` also collapses to `gap` and therefore
98
+ * `absStep * div != gap` constant-folds to `false`.
99
+ *
100
+ * Since most ranges are exclusive, that makes `numRangeElements` an alias
101
+ * of `gap`. Moreover, for exclusive ranges with step 1 and start 0 (which
102
+ * are the common case), it makes it an alias of `end` and the entire
103
+ * computation goes away.
104
+ */
105
+ val div = Integer .divideUnsigned(gap, absStep)
106
+ if (isInclusive || (absStep * div != gap)) div + 1 else div
95
107
}
96
108
97
- // This field has a sensible value only for non-empty ranges
98
- private val lastElement = step match {
99
- case 1 => if (isInclusive) end else end- 1
100
- case - 1 => if (isInclusive) end else end+ 1
101
- case _ =>
102
- val remainder = (gap % step).toInt
103
- if (remainder != 0 ) end - remainder
104
- else if (isInclusive) end
105
- else end - step
109
+ /** Computes the element of this range after `n` steps from `start`.
110
+ *
111
+ * `n` is interpreted as an unsigned integer.
112
+ *
113
+ * If the mathematical result is not within this Range, the result won't
114
+ * make sense, but won't error out.
115
+ */
116
+ @ inline
117
+ private [this ] def locationAfterN (n : Int ): Int = {
118
+ /* If `step >= 0`, we interpret `step * n` as an unsigned multiplication,
119
+ * and the addition as a mixed `(signed, unsigned) -> signed` operation.
120
+ * With those interpretation, they do not overflow, assuming the
121
+ * mathematical result is within this Range.
122
+ *
123
+ * If `step < 0`, we should compute `start - (-step * n)`, with the
124
+ * multiplication also interpreted as unsigned, and the subtraction as
125
+ * mixed. Again, using those interpreatations, they do not overflow.
126
+ * But then modular arithmetics allow us to cancel out the two `-` signs,
127
+ * so we end up with the same formula.
128
+ */
129
+ start + (step * n)
130
+ }
131
+
132
+ /** Last element of this non-empty range.
133
+ *
134
+ * For empty ranges, this value is nonsensical.
135
+ */
136
+ private [this ] val lastElement : Int = {
137
+ /* Since we can assume the range is non-empty, `(numRangeElements - 1)`
138
+ * is a valid unsigned value in the full int range. The general formula is
139
+ * therefore `locationAfterN(numRangeElements - 1)`.
140
+ *
141
+ * We special-case 1 and -1 so that, in the happy path where `step` is a
142
+ * constant 1 or -1, and we only use `foreach`, we can entirely
143
+ * dead-code-eliminate `numRangeElements` and its computation.
144
+ *
145
+ * When `step` is not constant, it is probably 1 or -1 anyway, so the
146
+ * single branch should be predictably true.
147
+ *
148
+ * `step == 1 || step == -1`
149
+ * equiv `(step + 1 == 2) || (step + 1 == 0)`
150
+ * equiv `((step + 1) & ~2) == 0`
151
+ */
152
+ if (((step + 1 ) & ~ 2 ) == 0 )
153
+ (if (isInclusive) end else end - step)
154
+ else
155
+ locationAfterN(numRangeElements - 1 )
106
156
}
107
157
108
158
/** The last element of this range. This method will return the correct value
@@ -135,18 +185,31 @@ extends scala.collection.AbstractSeq[Int]
135
185
def isInclusive = false
136
186
137
187
override def size = length
138
- override def length = if (numRangeElements < 0 ) fail() else numRangeElements
139
188
189
+ override def length : Int =
190
+ if (isEmpty) 0
191
+ else if (numRangeElements > 0 ) numRangeElements
192
+ else fail()
193
+
194
+ // Check cannot be evaluated eagerly because we have a pattern where
195
+ // ranges are constructed like: "x to y by z" The "x to y" piece
196
+ // should not trigger an exception. So the calculation is delayed,
197
+ // which means it will not fail fast for those cases where failing was
198
+ // correct.
140
199
private def fail () = Range .fail(start, end, step, isInclusive)
141
200
private def validateMaxLength () {
142
- if (numRangeElements < 0 )
201
+ if (numRangeElements <= 0 && ! isEmpty )
143
202
fail()
144
203
}
145
204
146
205
final def apply (idx : Int ): Int = {
147
- validateMaxLength()
148
- if (idx < 0 || idx >= numRangeElements) throw new IndexOutOfBoundsException (idx.toString)
149
- else start + (step * idx)
206
+ /* If length is not valid, numRangeElements <= 0, so the condition is always true.
207
+ * We push validateMaxLength() inside the then branch, out of the happy path.
208
+ */
209
+ if (idx < 0 || idx >= numRangeElements || isEmpty) {
210
+ validateMaxLength()
211
+ throw new IndexOutOfBoundsException (idx.toString)
212
+ } else locationAfterN(idx)
150
213
}
151
214
152
215
@ inline final override def foreach [@ specialized(Unit ) U ](f : Int => U ) {
@@ -162,22 +225,26 @@ extends scala.collection.AbstractSeq[Int]
162
225
}
163
226
}
164
227
228
+ /** Is the non-negative value `n` greater or equal to the number of elements
229
+ * in this non-empty range?
230
+ *
231
+ * This method returns nonsensical results if `n < 0` or if `this.isEmpty`.
232
+ */
233
+ @ inline private [this ] def greaterEqualNumRangeElements (n : Int ): Boolean =
234
+ (n ^ Int .MinValue ) > ((numRangeElements - 1 ) ^ Int .MinValue ) // unsigned comparison
235
+
165
236
/** Creates a new range containing the first `n` elements of this range.
166
237
*
167
238
* $doesNotUseBuilders
168
239
*
169
240
* @param n the number of elements to take.
170
241
* @return a new range consisting of `n` first elements.
171
242
*/
172
- final override def take (n : Int ): Range = (
243
+ final override def take (n : Int ): Range = {
173
244
if (n <= 0 || isEmpty) newEmptyRange(start)
174
- else if (n >= numRangeElements && numRangeElements >= 0 ) this
175
- else {
176
- // May have more than Int.MaxValue elements in range (numRangeElements < 0)
177
- // but the logic is the same either way: take the first n
178
- new Range .Inclusive (start, locationAfterN(n - 1 ), step)
179
- }
180
- )
245
+ else if (greaterEqualNumRangeElements(n)) this
246
+ else new Range .Inclusive (start, locationAfterN(n - 1 ), step)
247
+ }
181
248
182
249
/** Creates a new range containing all the elements of this range except the first `n` elements.
183
250
*
@@ -186,15 +253,11 @@ extends scala.collection.AbstractSeq[Int]
186
253
* @param n the number of elements to drop.
187
254
* @return a new range consisting of all the elements of this range except `n` first elements.
188
255
*/
189
- final override def drop (n : Int ): Range = (
256
+ final override def drop (n : Int ): Range = {
190
257
if (n <= 0 || isEmpty) this
191
- else if (n >= numRangeElements && numRangeElements >= 0 ) newEmptyRange(end)
192
- else {
193
- // May have more than Int.MaxValue elements (numRangeElements < 0)
194
- // but the logic is the same either way: go forwards n steps, keep the rest
195
- copy(locationAfterN(n), end, step)
196
- }
197
- )
258
+ else if (greaterEqualNumRangeElements(n)) newEmptyRange(end)
259
+ else copy(locationAfterN(n), end, step)
260
+ }
198
261
199
262
/** Creates a new range containing the elements starting at `from` up to but not including `until`.
200
263
*
@@ -204,14 +267,16 @@ extends scala.collection.AbstractSeq[Int]
204
267
* @param until the element at which to end (not included in the range)
205
268
* @return a new range consisting of a contiguous interval of values in the old range
206
269
*/
207
- override def slice (from : Int , until : Int ): Range =
208
- if (from <= 0 ) take(until)
209
- else if (until >= numRangeElements && numRangeElements >= 0 ) drop(from)
270
+ override def slice (from : Int , until : Int ): Range = {
271
+ if (isEmpty) this
272
+ else if (from <= 0 ) take(until)
273
+ else if (greaterEqualNumRangeElements(until) && until >= 0 ) drop(from)
210
274
else {
211
275
val fromValue = locationAfterN(from)
212
276
if (from >= until) newEmptyRange(fromValue)
213
277
else new Range .Inclusive (fromValue, locationAfterN(until- 1 ), step)
214
278
}
279
+ }
215
280
216
281
/** Creates a new range containing all the elements of this range except the last one.
217
282
*
@@ -250,9 +315,6 @@ extends scala.collection.AbstractSeq[Int]
250
315
else current.toLong + step
251
316
}
252
317
}
253
- // Methods like apply throw exceptions on invalid n, but methods like take/drop
254
- // are forgiving: therefore the checks are with the methods.
255
- private def locationAfterN (n : Int ) = start + (step * n)
256
318
257
319
// When one drops everything. Can't ever have unchecked operations
258
320
// like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue }
@@ -300,30 +362,19 @@ extends scala.collection.AbstractSeq[Int]
300
362
* $doesNotUseBuilders
301
363
*/
302
364
final override def takeRight (n : Int ): Range = {
303
- if (n <= 0 ) newEmptyRange(start)
304
- else if (numRangeElements >= 0 ) drop(numRangeElements - n)
305
- else {
306
- // Need to handle over-full range separately
307
- val y = last
308
- val x = y - step.toLong* (n- 1 )
309
- if ((step > 0 && x < start) || (step < 0 && x > start)) this
310
- else new Range .Inclusive (x.toInt, y, step)
311
- }
365
+ if (n <= 0 || isEmpty) newEmptyRange(start)
366
+ else if (greaterEqualNumRangeElements(n)) this
367
+ else copy(locationAfterN(numRangeElements - n), end, step)
312
368
}
313
369
314
370
/** Creates a new range consisting of the initial `length - n` elements of the range.
315
371
*
316
372
* $doesNotUseBuilders
317
373
*/
318
374
final override def dropRight (n : Int ): Range = {
319
- if (n <= 0 ) this
320
- else if (numRangeElements >= 0 ) take(numRangeElements - n)
321
- else {
322
- // Need to handle over-full range separately
323
- val y = last - step.toInt* n
324
- if ((step > 0 && y < start) || (step < 0 && y > start)) newEmptyRange(start)
325
- else new Range .Inclusive (start, y.toInt, step)
326
- }
375
+ if (n <= 0 || isEmpty) this
376
+ else if (greaterEqualNumRangeElements(n)) newEmptyRange(end)
377
+ else new Range .Inclusive (start, locationAfterN(numRangeElements - 1 - n), step)
327
378
}
328
379
329
380
/** Returns the reverse of this range.
@@ -341,14 +392,14 @@ extends scala.collection.AbstractSeq[Int]
341
392
else new Range .Inclusive (start, end, step)
342
393
343
394
final def contains (x : Int ) = {
344
- if (x == end && ! isInclusive ) false
395
+ if (isEmpty ) false
345
396
else if (step > 0 ) {
346
- if (x < start || x > end ) false
347
- else (step == 1 ) || ((( x - start) % step) == 0 )
397
+ if (x < start || x > lastElement ) false
398
+ else (step == 1 ) || (Integer .remainderUnsigned( x - start, step) == 0 )
348
399
}
349
400
else {
350
- if (x < end || x > start ) false
351
- else (step == - 1 ) || (((x - start) % step) == 0 )
401
+ if (x > start || x < lastElement ) false
402
+ else (step == - 1 ) || (Integer .remainderUnsigned(start - x, - step) == 0 )
352
403
}
353
404
}
354
405
@@ -399,8 +450,13 @@ extends scala.collection.AbstractSeq[Int]
399
450
400
451
override def toString = {
401
452
val preposition = if (isInclusive) " to" else " until"
453
+
402
454
val stepped = if (step == 1 ) " " else s " by $step"
403
- val prefix = if (isEmpty) " empty " else if (! isExact) " inexact " else " "
455
+ def isInexact =
456
+ if (isInclusive) lastElement != end
457
+ else (lastElement + step) != end
458
+
459
+ val prefix = if (isEmpty) " empty " else if (isInexact) " inexact " else " "
404
460
s " ${prefix}Range $start $preposition $end$stepped"
405
461
}
406
462
}
@@ -433,16 +489,19 @@ object Range {
433
489
)
434
490
if (isEmpty) 0
435
491
else {
436
- // Counts with Longs so we can recognize too-large ranges.
437
- val gap : Long = end.toLong - start.toLong
438
- val jumps : Long = gap / step
439
- // Whether the size of this range is one larger than the
440
- // number of full-sized jumps.
441
- val hasStub = isInclusive || (gap % step != 0 )
442
- val result : Long = jumps + ( if (hasStub) 1 else 0 )
443
-
444
- if (result > scala.Int .MaxValue ) - 1
445
- else result.toInt
492
+ val stepSign = step >> 31 // if (step >= 0) 0 else -1
493
+ val gap = ((end - start) ^ stepSign) - stepSign // if (step >= 0) (end - start) else -(end - start)
494
+ val absStep = (step ^ stepSign) - stepSign // if (step >= 0) step else -step
495
+
496
+ val div = Integer .divideUnsigned(gap, absStep)
497
+ if (isInclusive) {
498
+ if (div == - 1 ) // max unsigned int
499
+ - 1 // corner case: there are 2^32 elements, which would overflow to 0
500
+ else
501
+ div + 1
502
+ } else {
503
+ if (absStep * div != gap) div + 1 else div
504
+ }
446
505
}
447
506
3DD8
}
448
507
def count (start : Int , end : Int , step : Int ): Int =
0 commit comments