Skip to content

Commit cc74ec9

Browse files
committed
feat(numerics): add big integer bitwise operators
1 parent 3e7222a commit cc74ec9

8 files changed

Lines changed: 204 additions & 20 deletions

File tree

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
## Non-negotiable rules
1010

1111
1. **Zero errors, zero warnings** before any commit. `cmake --build build --parallel 4` must be clean.
12-
2. **No test-count regression.** `scripts/run_component_tests.sh build` must show no failures. The verified baseline is 12,650 tests across 36 component executables and one integration executable; this floor should be raised as new tests are added and lowered only with an explicit, documented reason.
12+
2. **No test-count regression.** `scripts/run_component_tests.sh build` must show no failures. The verified baseline is 12,654 tests across 36 component executables and one integration executable; this floor should be raised as new tests are added and lowered only with an explicit, documented reason.
1313
3. **Push only to `feature/work`.** Never push to `develop` or `master`, and never create tags, without explicit per-action user approval.
1414
4. **SPDX header on every project source/header** — `// SPDX-License-Identifier: MIT` + copyright + .NET attribution. Vendored sources retain their upstream headers; Markdown uses an HTML SPDX comment where one is present.
1515
5. **Property naming:** always `getXxxProperty()` / `setXxxProperty()`. Exception: indexers

‎NEXT.md‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
# NEXT.md
55

66
*Last verified: 2026-07-25. Branch: `feature/work`. The P0 component-boundary
7-
repair, three P1 parity repairs, P1 portability revalidation, and fourteen bounded
7+
repair, three P1 parity repairs, P1 portability revalidation, and fifteen bounded
88
P2 API slices are complete: 41 physical modules, 90 production dependency
9-
edges, and 12,650 tests across 37 executables.*
9+
edges, and 12,654 tests across 37 executables.*
1010

1111
This is the cold-start handoff for the next working session. Keep it focused
1212
on verified facts, remaining bounded work, and commands needed to resume.
@@ -28,7 +28,7 @@ Historical session detail belongs in git history and `plan.sqlite3`.
2828
- The ten-job selective consumer matrix, including a direct
2929
`Collections.Blocking` consumer, is green. Text.Json retains its target
3030
absence and negative include-leakage assertions.
31-
- The full native baseline is a warning-free build with 12,650 passing tests
31+
- The full native baseline is a warning-free build with 12,654 passing tests
3232
across 36 component executables and one integration executable.
3333
- `TaskT<TResult>::ContinueWith` now supports both action and result-producing
3434
callbacks. It runs inline on completion; `NotOn*` and `OnlyOn*` filter the
@@ -65,6 +65,9 @@ Historical session detail belongs in git history and `plan.sqlite3`.
6565
- `ImmutableList<T>::Sort(index, count)` sorts only a valid requested range
6666
with the default comparison. Zero-length boundary ranges are valid and the
6767
source plus outside elements remain unchanged.
68+
- `BigInteger` supports `&, |, ^, ~` and their compound assignments using
69+
infinite two's-complement semantics, including negative and beyond-native
70+
integer values. Shift and byte-array conversion APIs remain deferred.
6871
- MinGW-w64 GCC 14-win32/CMake 3.31.6 and Emscripten 5.0.7/CMake 3.31.6 both
6972
compile the post-modular `All` graph and selective `Text.Json` libraries.
7073
This is compile-only evidence: cross tests were deliberately disabled.
@@ -73,7 +76,7 @@ Historical session detail belongs in git history and `plan.sqlite3`.
7376
ASan/LSan ownership scenarios, including 100 continuation teardowns, pass.
7477

7578
The local `plan.sqlite3` snapshot contains 16,201 classified `task` rows and
76-
1,756 completed tickets. Ticket #1737 records the completed P0 split, tickets
79+
1,757 completed tickets. Ticket #1737 records the completed P0 split, tickets
7780
#1738/#1739 the MemoryStream and generic-continuation repairs, ticket #1740 the
7881
XML whitespace repair, #1741 the completed cross-build revalidation and
7982
`WebProxy` portability fix, #1742 focused sanitizer evidence, and #1743 the
@@ -84,8 +87,9 @@ UTF-8 `BinaryReader` batch-character APIs, #1749 `ImmutableList<T>` copying,
8487
and #1750 its custom comparison sort, and #1751 its range reverse. The
8588
database also records #1752 for its `IComparer<T>` sorting overloads, #1753
8689
for its equality-based item mutations, #1754 for its equality-based range
87-
queries, #1755 for its comparer-aware binary search, and #1756 for its default
88-
range sort; it is git-ignored and is not part of a fresh clone.
90+
queries, #1755 for its comparer-aware binary search, #1756 for its default
91+
range sort, and #1757 for `BigInteger` bitwise operators; it is git-ignored
92+
and is not part of a fresh clone.
8993

9094
## P0 completion: restore Collections isolation
9195

@@ -231,8 +235,8 @@ slice, create a ticket, and keep the changes isolated:
231235
1. **`ImmutableList<T>` builder support.** Treat `ToBuilder`/`Builder` as a
232236
separately scoped API design, not an opportunistic extension of the
233237
vector-backed immutable-list methods.
234-
2. **Other documented partial surfaces.** Examples include `BigInteger`
235-
bitwise operations, fuller UTF-7 behavior, and wider debugger/process/XML
238+
2. **Other documented partial surfaces.** Examples include `BigInteger` shift
239+
or byte-array APIs, fuller UTF-7 behavior, and wider debugger/process/XML
236240
surfaces.
237241
3. **Doxygen warning baseline.** Establish a reproducible warning count, then
238242
keep touched public APIs from increasing it; do not do a mass comment-only

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ collector, or the complete .NET platform.
77

88
The repository currently builds as 41 independently selectable CMake
99
components. The verified Linux baseline on 2026-07-25 is a warning-free build
10-
with **12,650 passing tests across 37 test executables**.
10+
with **12,654 passing tests across 37 test executables**.
1111

1212
## What is included
1313

@@ -181,7 +181,7 @@ Other platform evidence is narrower:
181181

182182
| Platform/toolchain | Verified scope |
183183
|---|---|
184-
| Linux/GCC | Current full component build and all 12,650 tests. |
184+
| Linux/GCC | Current full component build and all 12,654 tests. |
185185
| Windows/MinGW | MinGW-w64 GCC 14-win32/CMake 3.31.6 compiled the post-component `All` and selective `Text.Json` library graphs under ticket #1741. GoogleTest was not cross-built and repository CI remains Ubuntu-only. |
186186
| Emscripten | Emscripten 5.0.7/CMake 3.31.6 compiled the post-component `All` and selective `Text.Json` library graphs under ticket #1741. Tests were not cross-built or run, and some runtime APIs deliberately throw `PlatformNotSupportedException`. |
187187
| macOS/Apple Clang | Real downstream Xcode 15.4 builds drove portability fixes on 2026-07-20; this repository has no macOS job or recorded full standalone test baseline. |

‎docs/CMakeComponents.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ are named `SharpRuntimeTests_<Component>`; genuinely cross-module scenarios
131131
are in `SharpRuntimeIntegrationTests`. CTest also discovers every individual
132132
GoogleTest case.
133133

134-
The verified 2026-07-25 `All` baseline contains 12,650 tests across 36
134+
The verified 2026-07-25 `All` baseline contains 12,654 tests across 36
135135
component executables and one integration executable.
136136

137137
## Boundary validation and CI

‎modules/numerics/include/System/Numerics/BigInteger.hpp‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ namespace System::Numerics {
2020
*
2121
* Partial C++ counterpart of .NET System.Numerics.BigInteger.
2222
*
23-
* @note Status: Full arithmetic — add, subtract, multiply, divide, modulo,
24-
* comparisons, ToString, Parse, TryParse all implemented.
25-
* Bitwise operations are not implemented.
23+
* @note Status: Arithmetic, comparisons, formatting/parsing, and bitwise
24+
* operators are implemented. Shift and byte-array conversion APIs remain
25+
* outside this focused port.
2626
*/
2727
class BigInteger {
2828
bool negative_ = false;
@@ -52,6 +52,11 @@ namespace System::Numerics {
5252
divmodMag(std::vector<uint32_t> a, std::vector<uint32_t> b);
5353

5454
static std::vector<uint32_t> fromUInt64(uint64_t v);
55+
static std::vector<uint16_t> magnitudeToBinary16(const std::vector<uint32_t>& magnitude);
56+
static std::vector<uint32_t> binary16ToMagnitude(const std::vector<uint16_t>& words);
57+
static std::vector<uint16_t> toTwosComplement16(const BigInteger& value, size_t wordCount);
58+
static BigInteger fromTwosComplement16(std::vector<uint16_t> words);
59+
[[nodiscard]] BigInteger bitwise(const BigInteger& other, char operation) const;
5560

5661
public:
5762
// ------------------------------------------------------------------
@@ -129,11 +134,23 @@ namespace System::Numerics {
129134
*/
130135
BigInteger operator%(const BigInteger& o) const;
131136

137+
/** @brief Returns the bitwise AND using infinite two's-complement semantics. */
138+
BigInteger operator&(const BigInteger& o) const;
139+
/** @brief Returns the bitwise OR using infinite two's-complement semantics. */
140+
BigInteger operator|(const BigInteger& o) const;
141+
/** @brief Returns the bitwise XOR using infinite two's-complement semantics. */
142+
BigInteger operator^(const BigInteger& o) const;
143+
/** @brief Returns the bitwise complement using infinite two's-complement semantics. */
144+
BigInteger operator~() const;
145+
132146
BigInteger& operator+=(const BigInteger& o);
133147
BigInteger& operator-=(const BigInteger& o);
134148
BigInteger& operator*=(const BigInteger& o);
135149
BigInteger& operator/=(const BigInteger& o);
136150
BigInteger& operator%=(const BigInteger& o);
151+
BigInteger& operator&=(const BigInteger& o);
152+
BigInteger& operator|=(const BigInteger& o);
153+
BigInteger& operator^=(const BigInteger& o);
137154

138155
// ------------------------------------------------------------------
139156
// Comparison operators

‎modules/numerics/src/System/Numerics/BigInteger.cpp‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,93 @@ std::vector<uint32_t> BigInteger::fromUInt64(uint64_t v) {
175175
return r;
176176
}
177177

178+
std::vector<uint16_t> BigInteger::magnitudeToBinary16(const std::vector<uint32_t>& magnitude) {
179+
std::vector<uint32_t> remaining = magnitude;
180+
std::vector<uint16_t> words;
181+
while (!(remaining.size() == 1 && remaining[0] == 0)) {
182+
uint32_t remainder = 0;
183+
remaining = divMagByLimb(remaining, 65536u, remainder);
184+
words.push_back(static_cast<uint16_t>(remainder));
185+
}
186+
if (words.empty()) {
187+
words.push_back(0);
188+
}
189+
return words;
190+
}
191+
192+
std::vector<uint32_t> BigInteger::binary16ToMagnitude(const std::vector<uint16_t>& words) {
193+
std::vector<uint32_t> magnitude = {0};
194+
for (auto it = words.rbegin(); it != words.rend(); ++it) {
195+
magnitude = mulMag(magnitude, {65536u});
196+
if (*it != 0) {
197+
magnitude = addMag(magnitude, {static_cast<uint32_t>(*it)});
198+
}
199+
}
200+
return magnitude;
201+
}
202+
203+
std::vector<uint16_t> BigInteger::toTwosComplement16(const BigInteger& value, size_t wordCount) {
204+
std::vector<uint16_t> words = magnitudeToBinary16(value.mag_);
205+
words.resize(wordCount, 0);
206+
if (!value.negative_) {
207+
return words;
208+
}
209+
210+
for (auto& word : words) {
211+
word = static_cast<uint16_t>(~word);
212+
}
213+
uint32_t carry = 1;
214+
for (auto& word : words) {
215+
const uint32_t sum = static_cast<uint32_t>(word) + carry;
216+
word = static_cast<uint16_t>(sum);
217+
carry = sum >> 16;
218+
if (carry == 0) {
219+
break;
220+
}
221+
}
222+
return words;
223+
}
224+
225+
BigInteger BigInteger::fromTwosComplement16(std::vector<uint16_t> words) {
226+
const bool negative = (words.back() & 0x8000u) != 0;
227+
if (negative) {
228+
for (auto& word : words) {
229+
word = static_cast<uint16_t>(~word);
230+
}
231+
uint32_t carry = 1;
232+
for (auto& word : words) {
233+
const uint32_t sum = static_cast<uint32_t>(word) + carry;
234+
word = static_cast<uint16_t>(sum);
235+
carry = sum >> 16;
236+
if (carry == 0) {
237+
break;
238+
}
239+
}
240+
}
241+
242+
BigInteger result;
243+
result.mag_ = binary16ToMagnitude(words);
244+
result.negative_ = negative;
245+
result.trim();
246+
return result;
247+
}
248+
249+
BigInteger BigInteger::bitwise(const BigInteger& other, char operation) const {
250+
const size_t wordCount = std::max(magnitudeToBinary16(mag_).size(),
251+
magnitudeToBinary16(other.mag_).size()) + 1;
252+
const std::vector<uint16_t> left = toTwosComplement16(*this, wordCount);
253+
const std::vector<uint16_t> right = toTwosComplement16(other, wordCount);
254+
std::vector<uint16_t> result(wordCount);
255+
for (size_t index = 0; index < wordCount; ++index) {
256+
switch (operation) {
257+
case '&': result[index] = static_cast<uint16_t>(left[index] & right[index]); break;
258+
case '|': result[index] = static_cast<uint16_t>(left[index] | right[index]); break;
259+
default: result[index] = static_cast<uint16_t>(left[index] ^ right[index]); break;
260+
}
261+
}
262+
return fromTwosComplement16(std::move(result));
263+
}
264+
178265
// ---------------------------------------------------------------------------
179266
// Constructors
180267
// ---------------------------------------------------------------------------
@@ -338,11 +425,27 @@ BigInteger BigInteger::operator%(const BigInteger& o) const {
338425
return r;
339426
}
340427

428+
BigInteger BigInteger::operator&(const BigInteger& o) const { return bitwise(o, '&'); }
429+
BigInteger BigInteger::operator|(const BigInteger& o) const { return bitwise(o, '|'); }
430+
BigInteger BigInteger::operator^(const BigInteger& o) const { return bitwise(o, '^'); }
431+
432+
BigInteger BigInteger::operator~() const {
433+
const size_t wordCount = magnitudeToBinary16(mag_).size() + 1;
434+
std::vector<uint16_t> words = toTwosComplement16(*this, wordCount);
435+
for (auto& word : words) {
436+
word = static_cast<uint16_t>(~word);
437+
}
438+
return fromTwosComplement16(std::move(words));
439+
}
440+
341441
BigInteger& BigInteger::operator+=(const BigInteger& o) { *this = *this + o; return *this; }
342442
BigInteger& BigInteger::operator-=(const BigInteger& o) { *this = *this - o; return *this; }
343443
BigInteger& BigInteger::operator*=(const BigInteger& o) { *this = *this * o; return *this; }
344444
BigInteger& BigInteger::operator/=(const BigInteger& o) { *this = *this / o; return *this; }
345445
BigInteger& BigInteger::operator%=(const BigInteger& o) { *this = *this % o; return *this; }
446+
BigInteger& BigInteger::operator&=(const BigInteger& o) { *this = *this & o; return *this; }
447+
BigInteger& BigInteger::operator|=(const BigInteger& o) { *this = *this | o; return *this; }
448+
BigInteger& BigInteger::operator^=(const BigInteger& o) { *this = *this ^ o; return *this; }
346449

347450
// ---------------------------------------------------------------------------
348451
// Comparison
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) Robert Vokac and contributors
3+
// Portions based on .NET runtime API (MIT License, Copyright .NET Foundation and Contributors)
4+
#include <gtest/gtest.h>
5+
#include "System/Numerics/BigInteger.hpp"
6+
7+
using System::Numerics::BigInteger;
8+
9+
TEST(BigIntegerBitwiseTest, PositiveOperandsProduceExpectedValues) {
10+
const BigInteger left(5);
11+
const BigInteger right(3);
12+
13+
EXPECT_EQ((left & right).ToString(), "1");
14+
EXPECT_EQ((left | right).ToString(), "7");
15+
EXPECT_EQ((left ^ right).ToString(), "6");
16+
EXPECT_EQ((~left).ToString(), "-6");
17+
}
18+
19+
TEST(BigIntegerBitwiseTest, NegativeOperandsUseTwosComplementSemantics) {
20+
const BigInteger negativeFive(-5);
21+
const BigInteger three(3);
22+
23+
EXPECT_EQ((negativeFive & three).ToString(), "3");
24+
EXPECT_EQ((negativeFive | three).ToString(), "-5");
25+
EXPECT_EQ((negativeFive ^ three).ToString(), "-8");
26+
EXPECT_EQ((~negativeFive).ToString(), "4");
27+
}
28+
29+
TEST(BigIntegerBitwiseTest, NegativePairsAndZeroRetainInfiniteSignExtension) {
30+
const BigInteger negativeFive(-5);
31+
const BigInteger negativeThree(-3);
32+
33+
EXPECT_EQ((negativeFive & negativeThree).ToString(), "-7");
34+
EXPECT_EQ((negativeFive | negativeThree).ToString(), "-1");
35+
EXPECT_EQ((negativeFive ^ negativeThree).ToString(), "6");
36+
EXPECT_EQ((BigInteger::Zero & negativeFive).ToString(), "0");
37+
EXPECT_EQ((BigInteger::Zero | negativeFive).ToString(), "-5");
38+
EXPECT_EQ((~BigInteger::Zero).ToString(), "-1");
39+
}
40+
41+
TEST(BigIntegerBitwiseTest, OperatesBeyondNativeIntegerWidthAndSupportsAssignments) {
42+
const BigInteger highBit = BigInteger::Parse("18446744073709551616");
43+
const BigInteger lowBits = BigInteger::Parse("4294967295");
44+
45+
EXPECT_EQ((highBit & lowBits).ToString(), "0");
46+
EXPECT_EQ((highBit | lowBits).ToString(), "18446744078004518911");
47+
EXPECT_EQ((highBit ^ lowBits).ToString(), "18446744078004518911");
48+
49+
BigInteger assigned = highBit;
50+
assigned |= lowBits;
51+
EXPECT_EQ(assigned.ToString(), "18446744078004518911");
52+
assigned &= highBit;
53+
EXPECT_EQ(assigned.ToString(), "18446744073709551616");
54+
assigned ^= highBit;
55+
EXPECT_EQ(assigned.ToString(), "0");
56+
}

‎plan.md‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Sharp Runtime plan
22

33
*Last verified: 2026-07-25 — 41 physical components, 90 direct production
4-
dependency edges, a clean native build, 12,650 passing tests across 37
4+
dependency edges, a clean native build, 12,654 passing tests across 37
55
executables, and a green ten-job selective matrix.*
66

77
Sharp Runtime is in a consumer-driven expansion phase. The original type
@@ -32,7 +32,7 @@ was never created. Neither file should be linked as current documentation.
3232
### Code and validation
3333

3434
- Native Linux/GCC build: zero errors and zero warnings.
35-
- Tests: 12,650 passing across 36 component binaries plus one integration
35+
- Tests: 12,654 passing across 36 component binaries plus one integration
3636
binary.
3737
- Component graph: 41 physical modules and 90 direct production edges.
3838
- Boundary validator: no cycles, duplicate public include paths, orphan
@@ -52,7 +52,7 @@ The 2026-07-25 local snapshot contains:
5252
| Table | State |
5353
|---|---|
5454
| `task` | 16,201 rows: 1,082 `ported`, 140 `ignore`, 14,979 legacy `ignored`; no unclassified or `tobedecided` rows |
55-
| `ticket` | 1,756 rows, all `done`; no `todo`, `doing`, `blocked`, or `needs_user` rows |
55+
| `ticket` | 1,757 rows, all `done`; no `todo`, `doing`, `blocked`, or `needs_user` rows |
5656

5757
Because `plan.sqlite3` is git-ignored, these counts describe the maintainer
5858
snapshot, not data shipped in a fresh clone.
@@ -130,6 +130,10 @@ assertion without an explicit architecture decision.
130130
- Added default-comparison `ImmutableList<T>::Sort(index, count)` under
131131
ticket #1756. It sorts only a valid range, preserves the source and outside
132132
elements, and accepts zero-length boundary ranges.
133+
- Added `BigInteger` bitwise AND, OR, XOR, complement, and compound assignment
134+
operators under ticket #1757. The base-10^9 backing converts internally to
135+
a sign-extended two's-complement form so negative and large operands retain
136+
.NET semantics.
133137
- Added consumer-driven coverage across core, collections, IO, networking,
134138
threading/tasks, text/JSON, XML, numerics, globalization, and cryptographic
135139
hashing/random APIs.
@@ -190,7 +194,7 @@ The first consumer-driven ports after modularization added:
190194
- XML schema exception types.
191195

192196
The verified test baseline grew from 12,494 at the modularization checkpoint
193-
to 12,650.
197+
to 12,654.
194198

195199
## Candidate roadmap
196200

@@ -211,7 +215,7 @@ acceptance criteria and a validation command before changing code.
211215
behavior.
212216

213217
3. **Review other documented partial surfaces by demand.**
214-
Examples include `BigInteger` bitwise operations, full UTF-7 behavior,
218+
Examples include `BigInteger` shift or byte-array APIs, full UTF-7 behavior,
215219
debugger/process breadth, and richer XML reader/writer functionality.
216220
A documented partial API is not automatically higher priority than a
217221
consumer-visible bug.

0 commit comments

Comments
 (0)