blob: 6d0322d673210572d70f9f0e8179614b3a3a8f9a [file]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Testing int.trailingZeroBitCount and int.oneBitCount.
import "package:expect/expect.dart";
import "package:expect/variations.dart" show jsNumbers;
// Platform width for bit operations: 64 with native integer semantics
// (VM, dart2wasm), 32 with JS-number semantics (dart2js, DDC).
const int width = jsNumbers ? 32 : 64;
// `js == null` means the case is only meaningful on a native (64-bit)
// implementation and the JS assertion is skipped.
void checkTrailing(int i, int native, int? js) {
final expected = jsNumbers ? js : native;
if (expected == null) return;
Expect.equals(expected, i.trailingZeroBitCount, '$i.trailingZeroBitCount');
}
void checkOne(int i, int native, int? js) {
final expected = jsNumbers ? js : native;
if (expected == null) return;
Expect.equals(expected, i.oneBitCount, '$i.oneBitCount');
}
void testTrailingZeroBitCount() {
// Zero: trailing count equals full platform width.
checkTrailing(0, 64, 32);
// Positive values.
checkTrailing(1, 0, 0);
checkTrailing(2, 1, 1);
checkTrailing(3, 0, 0);
checkTrailing(4, 2, 2);
checkTrailing(8, 3, 3);
checkTrailing(0x10, 4, 4);
checkTrailing(4096, 12, 12);
checkTrailing(0x8000_0000, 31, 31);
checkTrailing(0x7fff_ffff, 0, 0);
// Negative values: two's complement preserves low bits.
checkTrailing(-1, 0, 0);
checkTrailing(-2, 1, 1);
checkTrailing(-4, 2, 2);
checkTrailing(-1024, 10, 10);
checkTrailing(-0x4000_0000, 30, 30);
checkTrailing(-0x8000_0000, 31, 31);
// Values above the 32-bit range. On JS the operation is defined as
// counting trailing zeros of the low 32 bits, so any value whose low
// 32 bits are zero yields the JS platform width (32) regardless of
// what's above.
checkTrailing(0x1_0000_0000, 32, 32);
checkTrailing(0x2_0000_0000, 33, 32);
checkTrailing(0x4_0000_0000, 34, 32);
checkTrailing(0x100_0000_0000, 40, 32);
checkTrailing(0x8000_0000_0000_0000, 63, 32);
// 64-bit-range values whose low 32 bits are non-zero: the JS result
// is determined entirely by the low half.
checkTrailing(0x1_0000_0001, 0, 0);
checkTrailing(0x2_0000_0080, 7, 7);
// Near 2^63, JS doubles only have enough precision for values that
// differ by 2048 (= 2^11), so consecutive integers can't all be
// represented.
// `2^63 + 4096` is exactly representable.
// `2^63 + 4095` rounds up to the same value.
checkTrailing(0x8000_0000_0000_0000 + 4096, 12, 12);
checkTrailing(0x8000_0000_0000_0000 + 4095, 0, 12);
}
void testOneBitCount() {
checkOne(0, 0, 0);
checkOne(1, 1, 1);
checkOne(2, 1, 1);
checkOne(3, 2, 2);
checkOne(7, 3, 3);
checkOne(0x55, 4, 4);
checkOne(0xff, 8, 8);
checkOne(0xffff_ffff, 32, 32);
// Negative values: sign-extend to platform width.
checkOne(-1, 64, 32);
checkOne(-2, 63, 31);
checkOne(-3, 63, 31);
checkOne(~0x55, 60, 28);
checkOne(-0x5555_5555, 49, 17);
checkOne(-0x7fff_ffff, 34, 2);
checkOne(-0x8000_0000, 33, 1);
// Values above the 32-bit range. JS counts only the low 32 bits, so
// a single bit above bit 31 is invisible to JS oneBitCount.
checkOne(0x1_0000_0000, 1, 0);
checkOne(0x8000_0000_0000_0000, 1, 0);
checkOne(0x2_0000_0001, 2, 1);
checkOne(0x1_0000_FFFF, 17, 16);
// `0x5555_5555_0000_0000 + 0x5555_5555` constructs
// 0x5555_5555_5555_5555 on native, but the runtime addition overflows
// JS double precision so the result on JS is unpredictable. Only the
// native expectation is asserted.
if (!jsNumbers) {
final pattern = 0x5555_5555_0000_0000 + 0x5555_5555;
checkOne(pattern, 32, null);
checkOne(~0x8000_0000_0000_0000, 63, null);
// Setting any odd-position bit on the alternating pattern should
// raise the count from 32 to 33, exercising the 64-bit popcount path
// across all positions.
for (int i = 1; i < 64; i += 2) {
Expect.equals(
33,
(pattern | (1 << i)).oneBitCount,
'(pattern | (1<<$i)).oneBitCount',
);
}
}
}
// Exhaustive single-bit coverage across the full platform width.
void testSingleBitCoverage() {
for (int b = 0; b < width; b++) {
final n = 1 << b;
Expect.equals(b, n.trailingZeroBitCount, '(1<<$b).trailingZeroBitCount');
Expect.equals(1, n.oneBitCount, '(1<<$b).oneBitCount');
}
}
// Dart-on-JS guarantees that bit operations performed on unsigned 32-bit
// values produce the same answer as on a native 64-bit Dart implementation.
// Verify the new getters honor that for a representative set of inputs that
// span the full 32-bit unsigned range.
void testUnsigned32BitConsistency() {
const cases = <(int, int, int)>[
(0x0000_0001, 0, 1),
(0x0000_0002, 1, 1),
(0x0000_0080, 7, 1),
(0x0000_FFFF, 0, 16),
(0x5555_5555, 0, 16),
(0xAAAA_AAAA, 1, 16),
(0xCCCC_CCCC, 2, 16),
(0xF0F0_F0F0, 4, 16),
(0x4000_0000, 30, 1),
(0x4000_0001, 0, 2),
(0x8000_0000, 31, 1),
(0x8000_0001, 0, 2),
(0xC000_0000, 30, 2),
(0xFFFF_FFFE, 1, 31),
(0xFFFF_FFFF, 0, 32),
];
for (final (n, tzc, obc) in cases) {
Expect.equals(tzc, n.trailingZeroBitCount, '$n.trailingZeroBitCount');
Expect.equals(obc, n.oneBitCount, '$n.oneBitCount');
}
}
void testIdentities() {
// n.oneBitCount + (~n).oneBitCount == platform width.
for (final n in const [
0,
1,
2,
7,
42,
0x7fff_ffff,
0x8000_0000,
0xffff_ffff,
-1,
-2,
-42,
]) {
Expect.equals(
width,
n.oneBitCount + (~n).oneBitCount,
'$n.oneBitCount + ~$n.oneBitCount',
);
}
// Cross-check: for any nonzero n, `(n & -n) - 1` is a mask of exactly
// `trailingZeroBitCount(n)` ones, so counting them recovers that count.
// Exercises both getters against each other.
void checkIdentity(int n) {
Expect.equals(
((n & -n) - 1).oneBitCount,
n.trailingZeroBitCount,
'(($n & -$n) - 1).oneBitCount == $n.trailingZeroBitCount',
);
}
// Small values + unsigned 32-bit boundaries + sign-extended negatives.
// Identity holds on every backend.
for (final n in const [
1,
2,
3,
7,
42,
0x4000_0000,
0x8000_0000,
0xffff_ffff,
-1,
-2,
-42,
]) {
checkIdentity(n);
}
// Values above the 32-bit range but within JS double mantissa precision
// (≤ 2^52). Under Dart's "operate on the low 32 bits" web semantics
// both sides of the identity collapse to 32, so the identity still
// holds on dart2js / DDC.
for (final n in const [
0x1_0000_0000, // 2^32
0x100_0000_0000, // 2^40
0x10_0000_0000_0000, // 2^52
]) {
checkIdentity(n);
}
// Native-only: values whose source bit pattern is not preserved by JS
// doubles. Exercised only on backends with native 64-bit ints. The
// `+ 1` form is used to keep `0x20_0000_0000_0000` as the literal,
// since the dart2js analyzer rejects literals that can't be
// represented exactly as a JS Number.
if (!jsNumbers) {
checkIdentity(0x20_0000_0000_0000 + 1); // 2^53 + 1
checkIdentity(0x8000_0000_0000_0000); // 2^63
}
}
void main() {
testTrailingZeroBitCount();
testOneBitCount();
testSingleBitCoverage();
testUnsigned32BitConsistency();
testIdentities();
}