Let's pretend we trying to solve the equivalent problem for decimal for a second.
Say you want 567 (inclusive) to 1203 (exclusive).
- Enlarging phase
- You increment by 1 until you have the a multiple of 10 or you would exceed the range.
- ⇒598 (Creates 597-597)
- ⇒599 (Creates 598-598)
- ⇒600 (Creates 599-599)
- You increment by 10 until you have a multiple of 100 or you would exceed the range.
- You increment by 100 until you have a multiple of 1000 or you would exceed the range.
- ⇒700 (Creates 600-699)
- ⇒800 (Creates 700-799)
- ⇒900 (Creates 800-899)
- ⇒1000 (Creates 900-999)
- You increment by 1000 until you have a multiple of 10000 or you would exceed the range.
- [Would exceed limit]
- Shrinking phase
- You increment by 100 until you would exceed the range.
- ⇒1100 (Creates 1000-1099)
- ⇒1200 (Creates 1100-1199)
- You increment by 10 until you would exceed the range.
- You increment by 1 until you would exceed the range.
- ⇒1201 (Creates 1200-1200)
- ⇒1202 (Creates 1201-1201)
- ⇒1203 (Creates 1202-1202)
Same in binary, but with powers of 2 instead of powers of 10.
my $start = 1000;
my $end = 1999 + 1;
my @ranges;
my $this = $start;
my $this_power = 1;
OUTER: while (1) {
my $next_power = $this_power * 2;
while ($this % $next_power) {
my $next = $this + $this_power;
last OUTER if $next > $end;
my $mask = ~($this_power - 1) & 0xFFFF;
push @ranges, sprintf("0x%04x/0x%x", $this, $mask);
$this = $next;
}
$this_power = $next_power;
}
while ($this_power > 1) {
$this_power /= 2;
while (1) {
my $next = $this + $this_power;
last if $next > $end;
my $mask = ~($this_power - 1) & 0xFFFF;
push @ranges, sprintf("0x%04x/0x%x", $this, $mask);
$this = $next;
}
}
say for @ranges;
We can optimize that by taking advantage of the fact that we're dealing with binary.
my $start = 1000;
my $end = 1999 + 1;
my @ranges;
my $this = $start;
my $power = 1;
my $mask = 0xFFFF;
while ($start & $mask) {
if ($this & $power) {
push @ranges, sprintf("0x%04x/0x%x", $this, $mask);
$this += $power;
}
$mask &= ~$power;
$power <<= 1;
}
while ($end & ~$mask) {
$power >>= 1;
$mask |= $power;
if ($end & $power) {
push @ranges, sprintf("0x%04x/0x%x", $this, $mask);
$this |= $power;
}
}
say for @ranges;
Output:
0x03e8/0xfff8
0x03f0/0xfff0
0x0400/0xfe00
0x0600/0xff00
0x0700/0xff80
0x0780/0xffc0
0x07c0/0xfff0