Skip to content

Commit 2d94051

Browse files
committed
Reimplement shift_right_round on native 128-bit integers
Replace the bigint-based shift_right_round with a free function that operates directly on uint128_t, dropping the bigint bit/any_below/ increment helpers it relied on. The uint128 fallback gains the few operators needed (<<, >>, <, ++) and loses the now-unused [[ZMIJ_MAYBE_UNUSED]] markers. Move and expand the tests into their own case covering non-ties, 64-bit-boundary shifts, full-width inputs, and the n >= 128 branch.
1 parent 37e71fa commit 2d94051

2 files changed

Lines changed: 63 additions & 72 deletions

File tree

test/zmij-impl-test.cc

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -723,17 +723,31 @@ TEST(zmij_impl_test, bigint) {
723723
bigint b = make_bigint(1);
724724
b.shift_left(80);
725725
EXPECT_EQ(to_string(b), "1208925819614629174706176");
726+
}
726727

727-
// shift_right_round divides by 2**bits, rounding ties to even.
728-
auto rshift = [](uint64_t value, int bits) {
729-
bigint n = make_bigint(value);
730-
n.shift_right_round(bits);
731-
return to_string(n);
728+
TEST(zmij_impl_test, shift_right_round) {
729+
// Divides a 128-bit value by 2**bits, rounding ties to even.
730+
auto rshift = [](uint128_t value, int bits) {
731+
return uint64_t(shift_right_round(value, bits));
732732
};
733-
EXPECT_EQ(rshift(6, 1), "3"); // 3.0 -> 3 (exact)
734-
EXPECT_EQ(rshift(5, 1), "2"); // 2.5 -> 2 (tie to even)
735-
EXPECT_EQ(rshift(7, 1), "4"); // 3.5 -> 4 (tie to even)
736-
EXPECT_EQ(rshift(3, 1), "2"); // 1.5 -> 2 (tie to even)
733+
// Ties round to even.
734+
EXPECT_EQ(rshift(6, 1), 3u); // 3.0 -> 3 (exact)
735+
EXPECT_EQ(rshift(5, 1), 2u); // 2.5 -> 2 (tie down to even)
736+
EXPECT_EQ(rshift(7, 1), 4u); // 3.5 -> 4 (tie up to even)
737+
EXPECT_EQ(rshift(3, 1), 2u); // 1.5 -> 2 (tie up to even)
738+
// Non-ties round to nearest.
739+
EXPECT_EQ(rshift(1, 2), 0u); // 0.25 -> 0
740+
EXPECT_EQ(rshift(3, 2), 1u); // 0.75 -> 1
741+
// Shifts crossing the 64-bit boundary.
742+
EXPECT_EQ(rshift(uint128_t(1) << 63, 64), 0u); // 0.5 -> 0 (tie to even)
743+
EXPECT_EQ(rshift(uint128_t(3) << 62, 64), 1u); // 0.75 -> 1
744+
// A full-width value: (2**64 - 1)**2 >> 64 = 2**64 - 2, remainder rounds down.
745+
EXPECT_EQ(rshift(umul128(~uint64_t(0), ~uint64_t(0)), 64), ~uint64_t(0) - 1);
746+
// n >= 128 shifts everything out; 2**127 is the only in-range tie.
747+
EXPECT_EQ(rshift(uint128_t(1) << 126, 128), 0u); // 0.25 -> 0
748+
EXPECT_EQ(rshift(uint128_t(1) << 127, 128), 0u); // 0.5 -> 0 (tie to even)
749+
EXPECT_EQ(rshift(uint128_t(3) << 126, 128), 1u); // 0.75 -> 1
750+
EXPECT_EQ(rshift(uint128_t(1) << 127, 200), 0u); // far past the width
737751
}
738752

739753
TEST(zmij_impl_test, bigint_divmod_1e9) {

zmij.cc

Lines changed: 40 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,30 @@ struct uint128 {
240240
uint64_t hi;
241241
uint64_t lo;
242242

243-
[[ZMIJ_MAYBE_UNUSED]] explicit constexpr operator uint64_t() const noexcept {
244-
return lo;
245-
}
243+
uint128() = default;
244+
constexpr uint128(uint64_t hi, uint64_t lo) noexcept : hi(hi), lo(lo) {}
245+
constexpr uint128(uint64_t lo) noexcept : hi(0), lo(lo) {}
246+
247+
explicit constexpr operator uint64_t() const noexcept { return lo; }
246248

247-
[[ZMIJ_MAYBE_UNUSED]] constexpr auto operator>>(int shift) const noexcept
248-
-> uint128 {
249-
if (shift == 32) return {hi >> 32, (hi << 32) | (lo >> 32)};
250-
assert(shift >= 64 && shift < 128);
251-
return {0, hi >> (shift - 64)};
249+
constexpr auto operator<<(int s) const noexcept -> uint128 {
250+
if (s == 0) return *this;
251+
if (s < 64) return {hi << s | lo >> (64 - s), lo << s};
252+
if (s < 128) return {lo << (s - 64), 0};
253+
return {0, 0};
254+
}
255+
constexpr auto operator>>(int s) const noexcept -> uint128 {
256+
if (s == 0) return *this;
257+
if (s < 64) return {hi >> s, lo >> s | hi << (64 - s)};
258+
if (s < 128) return {0, hi >> (s - 64)};
259+
return {0, 0};
260+
}
261+
constexpr auto operator<(uint128 o) const noexcept -> bool {
262+
return hi != o.hi ? hi < o.hi : lo < o.lo;
263+
}
264+
auto operator++() noexcept -> uint128& {
265+
if (++lo == 0) ++hi;
266+
return *this;
252267
}
253268
};
254269

@@ -1370,29 +1385,6 @@ struct bigint {
13701385
while (num_limbs > 0 && limbs[num_limbs - 1] == 0) --num_limbs;
13711386
}
13721387

1373-
auto bit(int pos) const noexcept -> uint32_t {
1374-
int limb_index = pos >> 5;
1375-
return limb_index < num_limbs ? (limbs[limb_index] >> (pos & 31)) & 1 : 0;
1376-
}
1377-
1378-
// Returns true if any bit strictly below `pos` is set.
1379-
auto any_below(int pos) const noexcept -> bool {
1380-
int limb_index = pos >> 5, b = pos & 31;
1381-
for (int i = 0; i < limb_index && i < num_limbs; ++i) {
1382-
if (limbs[i] != 0) return true;
1383-
}
1384-
return limb_index < num_limbs &&
1385-
(limbs[limb_index] & ((uint32_t(1) << b) - 1)) != 0;
1386-
}
1387-
1388-
void increment() noexcept {
1389-
for (int i = 0; i < num_limbs; ++i) {
1390-
if (++limbs[i] != 0) return; // No carry out of this limb.
1391-
}
1392-
assert(num_limbs < max_limbs);
1393-
limbs[num_limbs++] = 1; // Carry into a new top limb.
1394-
}
1395-
13961388
// Shifts left by `n`; requires enough spare limbs.
13971389
void shift_left(int n) noexcept {
13981390
assert(n >= 0);
@@ -1415,33 +1407,6 @@ struct bigint {
14151407
trim();
14161408
}
14171409

1418-
// Shifts right by `n`, rounding to nearest with ties to even.
1419-
void shift_right_round(int n) noexcept {
1420-
assert(n >= 0);
1421-
if (n == 0) return;
1422-
bool round_bit = bit(n - 1) != 0;
1423-
bool sticky = any_below(n - 1);
1424-
int limb_shift = n >> 5, bit_shift = n & 31;
1425-
if (limb_shift >= num_limbs) {
1426-
num_limbs = 0;
1427-
} else if (bit_shift == 0) {
1428-
for (int i = 0; i < num_limbs - limb_shift; ++i)
1429-
limbs[i] = limbs[i + limb_shift];
1430-
num_limbs -= limb_shift;
1431-
} else {
1432-
int new_size = num_limbs - limb_shift;
1433-
for (int i = 0; i < new_size - 1; ++i) {
1434-
uint32_t hi = limbs[i + limb_shift + 1] << (32 - bit_shift);
1435-
limbs[i] = limbs[i + limb_shift] >> bit_shift | hi;
1436-
}
1437-
limbs[new_size - 1] = limbs[num_limbs - 1] >> bit_shift;
1438-
num_limbs = new_size;
1439-
trim();
1440-
}
1441-
bool lsb = num_limbs > 0 && (limbs[0] & 1) != 0;
1442-
if (round_bit && (sticky || lsb)) increment();
1443-
}
1444-
14451410
// Divides by 10**9 in place and returns the remainder.
14461411
auto divmod_1e9() noexcept -> uint32_t {
14471412
uint64_t rem = 0;
@@ -1482,6 +1447,20 @@ struct bigint {
14821447
}
14831448
};
14841449

1450+
// Returns the 128-bit `value` shifted right by `n` bits (n >= 1), rounded to
1451+
// nearest with ties to even.
1452+
auto shift_right_round(uint128_t value, int n) noexcept -> uint128_t {
1453+
if (n >= 128) { // everything shifts out; 2**127 is the only in-range tie
1454+
uint128_t half = uint128_t(1) << 127;
1455+
return n == 128 && half < value ? uint128_t(1) : uint128_t(0);
1456+
}
1457+
uint128_t q = value >> n;
1458+
uint128_t rem = (value << (128 - n)) >> (128 - n); // discarded low n bits
1459+
uint128_t half = uint128_t(1) << (n - 1);
1460+
if (half < rem || ((uint64_t(q) & 1) && !(rem < half))) ++q;
1461+
return q;
1462+
}
1463+
14851464
// Emits n's decimal digits (most significant first) ending at `end`, consuming
14861465
// n, and returns a pointer to the first (most significant) digit.
14871466
auto write_digits(bigint& n, char* end) noexcept -> char* {
@@ -1501,11 +1480,9 @@ auto write_digits(bigint& n, char* end) noexcept -> char* {
15011480
// digits, correctly rounded (ties to even) via exact big-integer arithmetic.
15021481
auto write_fixed_big(char* buffer, uint64_t bin_sig, int bin_exp,
15031482
int precision) noexcept -> char* {
1504-
bigint n(umul128(bin_sig, pow10s[precision]));
1505-
if (bin_exp >= 0)
1506-
n.shift_left(bin_exp);
1507-
else
1508-
n.shift_right_round(-bin_exp);
1483+
uint128_t product = umul128(bin_sig, pow10s[precision]);
1484+
bigint n(bin_exp < 0 ? shift_right_round(product, -bin_exp) : product);
1485+
if (bin_exp >= 0) n.shift_left(bin_exp);
15091486

15101487
// n <= round(DBL_MAX * 10**18): DBL_MAX has 309 integer digits and precision
15111488
// adds at most 18 more.

0 commit comments

Comments
 (0)