Skip to content

Commit 78ef0bb

Browse files
committed
Rewrite readBitsInt{Be,Le}() - fix known issues, speed up a bit
See kaitai-io/kaitai_struct#949 Fix #20, fix #22
1 parent 8f84e8e commit 78ef0bb

File tree

1 file changed

+70
-30
lines changed

1 file changed

+70
-30
lines changed

KaitaiStream.js

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -412,40 +412,53 @@ KaitaiStream.prototype.readF8le = function() {
412412
// ------------------------------------------------------------------------
413413

414414
KaitaiStream.prototype.alignToByte = function() {
415-
this.bits = 0;
416415
this.bitsLeft = 0;
416+
this.bits = 0;
417417
};
418418

419+
/*
420+
bitsLeft = 3
421+
\ \ bitsNeeded = 10 -> bytesNeeded = 2
422+
\ \ / \
423+
|01101xxx|xxxxxxxx|xx......|
424+
\ /\ \
425+
\__ n = 13 _/ \ \
426+
new bitsLeft = 6
427+
*/
419428
KaitaiStream.prototype.readBitsIntBe = function(n) {
420429
// JS only supports bit operations on 32 bits
421430
if (n > 32) {
422431
throw new Error("readBitsIntBe: the maximum supported bit length is 32 (tried to read " + n + " bits)");
423432
}
433+
var res = 0;
434+
424435
var bitsNeeded = n - this.bitsLeft;
436+
this.bitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8`
437+
425438
if (bitsNeeded > 0) {
426439
// 1 bit => 1 byte
427440
// 8 bits => 1 byte
428441
// 9 bits => 2 bytes
429-
var bytesNeeded = Math.ceil(bitsNeeded / 8);
442+
var bytesNeeded = ((bitsNeeded - 1) >> 3) + 1; // `ceil(bitsNeeded / 8)` (NB: `x >> 3` is `floor(x / 8)`)
430443
var buf = this.readBytes(bytesNeeded);
431444
for (var i = 0; i < bytesNeeded; i++) {
432-
this.bits <<= 8;
433-
this.bits |= buf[i];
434-
this.bitsLeft += 8;
445+
res = res << 8 | buf[i];
435446
}
447+
448+
var newBits = res;
449+
res = res >>> this.bitsLeft | this.bits << bitsNeeded; // `x << 32` is defined as `x << 0` in JS, but only `0 << 32`
450+
// can occur here (`n = 32` and `bitsLeft = 0`, this implies
451+
// `bits = 0` unless changed externally)
452+
this.bits = newBits; // will be masked at the end of the function
453+
} else {
454+
res = this.bits >>> -bitsNeeded; // shift unneeded bits out
436455
}
437456

438-
// raw mask with required number of 1s, starting from lowest bit
439-
var mask = n === 32 ? 0xffffffff : (1 << n) - 1;
440-
// shift this.bits to align the highest bits with the mask & derive reading result
441-
var shiftBits = this.bitsLeft - n;
442-
var res = (this.bits >>> shiftBits) & mask;
443-
// clear top bits that we've just read => AND with 1s
444-
this.bitsLeft -= n;
445-
mask = (1 << this.bitsLeft) - 1;
457+
var mask = (1 << this.bitsLeft) - 1; // `bitsLeft` is in range 0..7, so `(1 << 32)` does not have to be considered
446458
this.bits &= mask;
447459

448-
return res;
460+
// always return an unsigned 32-bit integer
461+
return res >>> 0;
449462
};
450463

451464
/**
@@ -455,32 +468,59 @@ KaitaiStream.prototype.readBitsIntBe = function(n) {
455468
*/
456469
KaitaiStream.prototype.readBitsInt = KaitaiStream.prototype.readBitsIntBe;
457470

471+
/*
472+
n = 13 bitsNeeded = 10
473+
/ \
474+
bitsLeft = 3 ______ __
475+
\ \ / \ \ \
476+
|xxx01101|xxxxxxxx|......xx|
477+
\ /
478+
new bitsLeft = 6
479+
480+
bitsLeft = 7
481+
\ \
482+
|01101100|..xxxxx1|........|
483+
\___/
484+
n = 5
485+
*/
458486
KaitaiStream.prototype.readBitsIntLe = function(n) {
459487
// JS only supports bit operations on 32 bits
460488
if (n > 32) {
461489
throw new Error("readBitsIntLe: the maximum supported bit length is 32 (tried to read " + n + " bits)");
462490
}
491+
var res = 0;
463492
var bitsNeeded = n - this.bitsLeft;
493+
464494
if (bitsNeeded > 0) {
465-
// 1 bit => 1 byte
466-
// 8 bits => 1 byte
467-
// 9 bits => 2 bytes
468-
var bytesNeeded = Math.ceil(bitsNeeded / 8);
469-
var buf = this.readBytes(bytesNeeded);
470-
for (var i = 0; i < bytesNeeded; i++) {
471-
this.bits |= (buf[i] << this.bitsLeft);
472-
this.bitsLeft += 8;
473-
}
495+
// 1 bit => 1 byte
496+
// 8 bits => 1 byte
497+
// 9 bits => 2 bytes
498+
var bytesNeeded = ((bitsNeeded - 1) >> 3) + 1; // `ceil(bitsNeeded / 8)` (NB: `x >> 3` is `floor(x / 8)`)
499+
var buf = this.readBytes(bytesNeeded);
500+
for (var i = 0; i < bytesNeeded; i++) {
501+
res |= buf[i] << (i * 8);
502+
}
503+
504+
// NB: in JavaScript, bit shift operators always shift by modulo 32 of the right-hand operand (see
505+
// https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-unsignedRightShift),
506+
// so `res >>> 32` is equivalent to `res >>> 0` (but we don't want that)
507+
var newBits = bitsNeeded < 32 ? res >>> bitsNeeded : 0;
508+
res = res << this.bitsLeft | this.bits;
509+
this.bits = newBits;
510+
} else {
511+
res = this.bits;
512+
this.bits >>>= n;
474513
}
475514

476-
// raw mask with required number of 1s, starting from lowest bit
477-
var mask = n === 32 ? 0xffffffff : (1 << n) - 1;
478-
// derive reading result
479-
var res = this.bits & mask;
480-
// remove bottom bits that we've just read by shifting
481-
this.bits >>>= n;
482-
this.bitsLeft -= n;
515+
this.bitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8`
483516

517+
// always return an unsigned 32-bit integer
518+
if (n < 32) {
519+
var mask = (1 << n) - 1;
520+
res &= mask; // this produces a signed 32-bit int, but the sign bit is cleared
521+
} else {
522+
res >>>= 0;
523+
}
484524
return res;
485525
};
486526

0 commit comments

Comments
 (0)