Skip to content

Commit 1866c61

Browse files
committed
Rewrite read_bits_int_{be,le}()
See kaitai-io/kaitai_struct#949
1 parent 29d0718 commit 1866c61

File tree

1 file changed

+34
-29
lines changed

1 file changed

+34
-29
lines changed

lib/IO/KaitaiStruct.pm

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -246,38 +246,37 @@ sub read_f8le {
246246
sub align_to_byte {
247247
my ($self) = @_;
248248

249-
$self->{bits} = 0;
250249
$self->{bits_left} = 0;
250+
$self->{bits} = 0;
251251
}
252252

253253
sub read_bits_int_be {
254254
my ($self, $n) = @_;
255+
my $res = 0;
255256

256257
my $bits_needed = $n - $self->{bits_left};
258+
$self->{bits_left} = -$bits_needed & 7; # `-$bits_needed mod 8`
259+
257260
if ($bits_needed > 0) {
258261
# 1 bit => 1 byte
259262
# 8 bits => 1 byte
260263
# 9 bits => 2 bytes
261-
my $bytes_needed = int(($bits_needed - 1) / 8) + 1;
264+
my $bytes_needed = (($bits_needed - 1) >> 3) + 1; # `ceil($bits_needed / 8)` (NB: `x >> 3` is `floor(x / 8)`,
265+
# but ONLY for `x >= 0`, because `>>` is unsigned in Perl
266+
# unless it is inside a `use integer` block)
262267
my $buf = $self->read_bytes($bytes_needed);
263-
for my $byte (split("", $buf)) {
264-
$byte = unpack("C", $byte);
265-
$self->{bits} <<= 8;
266-
$self->{bits} |= $byte;
267-
$self->{bits_left} += 8;
268+
for my $byte (unpack('C*', $buf)) {
269+
$res = $res << 8 | $byte;
268270
}
269-
}
270-
271-
# Raw mask with required number of 1s, starting from lowest bit
272-
my $mask = (1 << $n) - 1;
273271

274-
# Shift $self->{bits} to align the highest bits with the mask & derive reading result
275-
my $shift_bits = $self->{bits_left} - $n;
276-
my $res = ($self->{bits} >> $shift_bits) & $mask;
272+
my $new_bits = $res;
273+
$res = $res >> $self->{bits_left} | $self->{bits} << $bits_needed;
274+
$self->{bits} = $new_bits; # will be masked at the end of the function
275+
} else {
276+
$res = $self->{bits} >> -$bits_needed; # shift unneeded bits out
277+
}
277278

278-
# Clear top bits that we've just read => AND with 1s
279-
$self->{bits_left} -= $n;
280-
$mask = (1 << $self->{bits_left}) - 1;
279+
my $mask = (1 << $self->{bits_left}) - 1; # `bits_left` is in range 0..7
281280
$self->{bits} &= $mask;
282281

283282
return $res;
@@ -294,28 +293,34 @@ sub read_bits_int {
294293
sub read_bits_int_le {
295294
my ($self, $n) = @_;
296295

296+
my $res = 0;
297297
my $bits_needed = $n - $self->{bits_left};
298+
298299
if ($bits_needed > 0) {
299300
# 1 bit => 1 byte
300301
# 8 bits => 1 byte
301302
# 9 bits => 2 bytes
302-
my $bytes_needed = int(($bits_needed - 1) / 8) + 1;
303+
my $bytes_needed = (($bits_needed - 1) >> 3) + 1; # `ceil($bits_needed / 8)` (NB: `x >> 3` is `floor(x / 8)`,
304+
# but ONLY for `x >= 0` - see `read_bits_int_be` method)
303305
my $buf = $self->read_bytes($bytes_needed);
304-
for my $byte (split("", $buf)) {
305-
$byte = unpack("C", $byte);
306-
$self->{bits} |= ($byte << $self->{bits_left});
307-
$self->{bits_left} += 8;
306+
my $i = 0;
307+
for my $byte (unpack('C*', $buf)) {
308+
$res |= $byte << ($i * 8);
309+
$i++;
308310
}
311+
312+
my $new_bits = $res >> $bits_needed;
313+
$res = $res << $self->{bits_left} | $self->{bits};
314+
$self->{bits} = $new_bits;
315+
} else {
316+
$res = $self->{bits};
317+
$self->{bits} >>= $n;
309318
}
310319

311-
# Raw mask with required number of 1s, starting from lowest bit
312-
my $mask = (1 << $n) - 1;
313-
# Derive reading result
314-
my $res = $self->{bits} & $mask;
315-
# Remove bottom bits that we've just read by shifting
316-
$self->{bits} >>= $n;
317-
$self->{bits_left} -= $n;
320+
$self->{bits_left} = -$bits_needed & 7; # `-$bits_needed mod 8`
318321

322+
my $mask = (1 << $n) - 1; # unlike some other languages, no problem with this in Perl
323+
$res &= $mask;
319324
return $res;
320325
}
321326

0 commit comments

Comments
 (0)