• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stillwater-sc / universal / 24864610179

23 Apr 2026 11:47PM UTC coverage: 84.367% (-0.02%) from 84.39%
24864610179

push

github

web-flow
feat(blockbinary): promote arithmetic operators to constexpr (PR 2 of #713) (#716)

* feat(blockbinary): promote arithmetic operators to constexpr (PR 2 of #713)

Mark blockbinary's arithmetic and bitwise operators constexpr so that posit
convert_<>() and the broader Universal codebase can do constexpr blockbinary
arithmetic. This is the prerequisite for Phase 2 of issue #713 (constexpr
IEEE-754 -> posit conversion) and is independently valuable for any future
code that needs compile-time blockbinary arithmetic.

Operators promoted to constexpr:
  blockbinary::operator+= operator-= operator++ operator-- operator<<=
  operator>>= operator|= operator&= operator^=
  free truncate(...)  free twosComplement(...)
  posit_impl::convert_<>()

Implementation notes:
- The bitsInBlock==64 path of operator+= calls addcarry(), which uses
  platform-specific intrinsics that are not constexpr on MSVC. Dispatch
  via std::is_constant_evaluated() to a portable carry-detection path
  in constexpr context, while keeping the intrinsic for runtime perf.
- The general multi-block path used pointer-arithmetic loops. Rewritten
  as index-based loops, which are constexpr-friendly.
- The two `if (_trace_conversion)` runtime branches in convert_<>() that
  called std::cout were converted to `if constexpr (_trace_conversion)`
  to satisfy the constant-expression requirement.

The following blockbinary operators are intentionally left non-constexpr in
this PR (out of scope; they have separate intrinsic dependencies):
  operator*= (uses mul128), operator/=, operator%=

Tests:
- internal/blockbinary/api/constexpr.cpp expanded with static_assert smoke
  tests covering operator+=, ++, <<=, |=, twosComplement across uint8/uint64
  limbs, single-block and multi-block configurations.
- Verified constexpr posit_impl::convert_<>() compiles and produces bit-
  identical encoding to runtime invocation.

Cross-type regression validation (gcc-13 + clang-18, both -std=c++20):
  blockbinary: bb... (continued)

28 of 38 new or added lines in 3 files covered. (73.68%)

6 existing lines in 1 file now uncovered.

44907 of 53228 relevant lines covered (84.37%)

6505383.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.07
/include/sw/universal/internal/blockbinary/blockbinary.hpp
1
#pragma once
2
// blockbinary.hpp: parameterized blocked binary number system representing a 2's complement binary number
3
//
4
// Copyright (C) 2017 Stillwater Supercomputing, Inc.
5
// SPDX-License-Identifier: MIT
6
//
7
// This file is part of the universal numbers project, which is released under an MIT Open Source license.
8
#include <cstdint>
9
#include <iostream>
10
#include <iomanip>
11
#include <string>
12
#include <sstream>
13
#include <type_traits>
14
#include <universal/number/shared/specific_value_encoding.hpp>
15
#include <universal/internal/blocktype/carry.hpp>
16

17
namespace sw { namespace universal {
18

19
enum class BinaryNumberType {
20
        Signed   = 0, // { ...,-3,-2,-1,0,1,2,3,... }    // 2's complement encoding
21
        Unsigned = 1  // {              0,1,2,3,... }    // binary encoding
22
};
23

24
// forward references
25
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType> class blockbinary;
26
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType> constexpr blockbinary<nbits, BlockType, NumberType> twosComplement(const blockbinary<nbits, BlockType, NumberType>&);
27
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType> struct quorem;
28
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType> quorem<nbits, BlockType, NumberType> longdivision(const blockbinary<nbits, BlockType, NumberType>&, const blockbinary<nbits, BlockType, NumberType>&);
29

30
// idiv_t for blockbinary<nbits> to capture quotient and remainder during long division
31
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
32
struct quorem {
33
        int exceptionId;
34
        blockbinary<nbits, BlockType, NumberType> quo; // quotient
35
        blockbinary<nbits, BlockType, NumberType> rem;  // remainder
36
};
37

38
// maximum positive 2's complement number: b01111...1111
39
template<unsigned nbits, typename BlockType = uint8_t, BinaryNumberType NumberType>
40
constexpr blockbinary<nbits, BlockType, NumberType>& maxpos(blockbinary<nbits, BlockType, NumberType>& a) {
41
        a.clear();
42
        a.flip();
43
        if constexpr (NumberType == BinaryNumberType::Signed) {
44
                a.setbit(nbits - 1, false);
45
        }
46
        return a;
47
}
48

49
// maximum negative 2's complement number: b1000...0000
50
template<unsigned nbits, typename BlockType = uint8_t, BinaryNumberType NumberType>
51
constexpr blockbinary<nbits, BlockType, NumberType>& maxneg(blockbinary<nbits, BlockType, NumberType>& a) {
17,274,616✔
52
        a.clear();
17,274,616✔
53
        if constexpr (NumberType == BinaryNumberType::Signed) {
54
                a.setbit(nbits - 1);
17,274,616✔
55
        }
56
        return a;
17,274,616✔
57
}
58

59
// generate the 2's complement of the block binary number
60
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
61
constexpr blockbinary<nbits, BlockType, NumberType> twosComplement(const blockbinary<nbits, BlockType, NumberType>& orig) {
41,088,096✔
62
        blockbinary<nbits, BlockType, NumberType> twosC(orig);
41,088,096✔
63
        blockbinary<nbits, BlockType, NumberType> plusOne(1);
41,088,096✔
64
        twosC.flip();
41,088,096✔
65
        twosC += plusOne;
41,088,096✔
66
        return twosC;
41,088,096✔
67
}
68

69
// Truncate a bigger posit to fit in a smaller
70
template<unsigned srcbits, unsigned tgtbits, typename bt, BinaryNumberType nt>
71
constexpr void truncate(const blockbinary<srcbits, bt, nt>& src, blockbinary<tgtbits, bt, nt>& tgt) {
12,712,295✔
72
        static_assert(tgtbits < srcbits, "truncate requires source posit to be bigger than target posit");
73
        constexpr unsigned diff = srcbits - tgtbits;
12,712,295✔
74
        for (unsigned i = 0; i < tgtbits; ++i) { // TODO: optimize for limbs
256,845,449✔
75
                tgt.setbit(i, src.test(i + diff));
244,133,093✔
76
        }
77
}
12,712,356✔
78

79
/*
80
NOTES
81

82
For block arithmetic, we need to manage a carry bit.
83
For uint8_t, uint16_t, and uint32_t limbs, we cast up to uint64_t to detect overflow.
84
For uint64_t limbs, there is no larger native type, so we use platform-specific
85
intrinsics (see carry.hpp) for carry propagation: compiler builtins on MSVC,
86
unsigned __int128 on GCC/Clang, or a portable comparison-based fallback.
87
*/
88

89
// a block-based binary number configurable to be signed or unsigned. When signed it uses 2's complement encoding
90
template<unsigned _nbits, typename bt = uint8_t, BinaryNumberType _NumberType = BinaryNumberType::Signed>
91
class blockbinary {
92
public:
93
        static constexpr unsigned nbits = _nbits;
94
        typedef bt BlockType;
95
        static constexpr BinaryNumberType NumberType = _NumberType;
96

97
        static constexpr unsigned bitsInByte = 8;
98
        static constexpr unsigned bitsInBlock = sizeof(bt) * bitsInByte;
99
        static constexpr unsigned nrBlocks = (0 == nbits ? 1 : (1ull + ((nbits - 1ull) / bitsInBlock)));
100
        static constexpr uint64_t storageMask = (0xFFFFFFFFFFFFFFFFull >> (64 - bitsInBlock));
101
        static constexpr bt       maxBlockValue = bt(-1);
102

103
        static constexpr unsigned MSU = nrBlocks - 1; // MSU == Most Significant Unit
104
        static constexpr bt       ALL_ONES = bt(~0);
105
        static constexpr unsigned maxShift = (0 == nbits ? 0 : (nrBlocks* bitsInBlock - nbits)); // protect the shift that is >= sizeof(bt)
106
        static constexpr bt       MSU_MASK = (0 == nbits ? bt(0) : (ALL_ONES >> maxShift));      // the other side of this protection
107
        static constexpr bt       SIGN_BIT_MASK = (0 == nbits ? bt(0) : (bt(bt(1) << ((nbits - 1ull) % bitsInBlock))));
108

109
        // uint64_t multi-block arithmetic is supported via carry-detection intrinsics (see carry.hpp)
110
        static_assert(bitsInBlock <= 64, "storage unit for block arithmetic needs to be one of [uint8_t | uint16_t | uint32_t | uint64_t]");
111

112
        /// trivial constructor
113
        blockbinary() = default;
114

115
        /// construct a blockbinary from another: bt must be the same
116
        template<unsigned nnbits>
117
        blockbinary(const blockbinary<nnbits, BlockType, NumberType>& rhs) : _block{} { this->assign(rhs); }
174,380,464✔
118

119
        // initializer for long long
120
        constexpr blockbinary(long long initial_value) noexcept : _block{} { *this = initial_value; }
1,578,993✔
121

122
        // specific value constructors
123
        constexpr blockbinary(const std::string& s) noexcept : _block{} {  }  // TODO
124
        constexpr blockbinary(const SpecificValue code) : _block{} {
125
                switch (code) {
126
                case SpecificValue::infpos:
127
                case SpecificValue::maxpos:
128
                        maxpos();
129
                        break;
130
                case SpecificValue::minpos:
131
                        minpos();
132
                        break;
133
                case SpecificValue::qnan:
134
                case SpecificValue::snan:
135
                case SpecificValue::nar:
136
                case SpecificValue::zero:
137
                default:
138
                        zero();
139
                        break;
140
                case SpecificValue::minneg:
141
                        minneg();
142
                        break;
143
                case SpecificValue::infneg:
144
                case SpecificValue::maxneg:
145
                        maxneg();
146
                        break;
147
                }
148
        }
149

150
        constexpr blockbinary& operator=(long long rhs) noexcept {
15,086,762✔
151
                if constexpr (1 < nrBlocks) {
152
                        for (unsigned i = 0; i < nrBlocks; ++i) {
60,380,593✔
153
                                _block[i] = rhs & storageMask;
46,757,462✔
154
                                if constexpr (bitsInBlock < 64) {
155
                                        rhs >>= bitsInBlock;
46,757,462✔
156
                                }
157
                                else {
158
                                        rhs = (rhs < 0) ? -1LL : 0LL; // sign-extend for uint64_t limbs
×
159
                                }
160
                        }
161
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
162
                        _block[MSU] &= MSU_MASK;
13,623,131✔
163
                }
164
                else if constexpr (1 == nrBlocks) {
165
                        _block[0] = rhs & storageMask;
1,463,631✔
166
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
167
                        _block[MSU] &= MSU_MASK;
1,463,631✔
168
                }
169
                return *this;
15,086,762✔
170
        }
171

172
        // conversion operators
173
        explicit operator int() const                { return int(to_sll()); }
174
        explicit operator long() const               { return long(to_sll()); }
11✔
175
        explicit operator long long() const          { return to_sll(); }
397,089✔
176
        explicit operator unsigned int() const       { return unsigned(to_ull()); }
109,969,364✔
177
        explicit operator unsigned long() const      { return (unsigned long)to_ull(); }
178
        explicit operator unsigned long long() const { return to_ull(); }
286✔
179
        // TODO: these need proper implementations that can convert very large integers to the proper scale afforded by the floating-point formats
180
        explicit operator float() const              { return to_native<float>(); }
48✔
181
        explicit operator double() const             { return to_native<double>(); }
1,186,214✔
182

183
#if LONG_DOUBLE_SUPPORT
184
        explicit operator long double() const        { return to_native<long double>(); }
185
#endif
186

187
        // limb access operators
188
//        constexpr BlockType& operator[](unsigned index) { return _block[index]; }
189
        constexpr BlockType operator[](unsigned index) const { return _block[index]; }
172,076,923✔
190

191
        // prefix operators
192
        blockbinary operator-() const {
630,252✔
193
                blockbinary negated(*this);
630,252✔
194
                blockbinary plusOne(1);
630,252✔
195
                negated.flip();
630,252✔
196
                negated += plusOne;
630,252✔
197
                return negated;
630,252✔
198
        }
199
        // one's complement
200
        blockbinary operator~() const {
201
                blockbinary complement(*this);
202
                complement.flip();
203
                return complement;
204
        }
205
        // increment/decrement
206
        constexpr blockbinary operator++(int) {
207
                blockbinary tmp(*this);
208
                operator++();
209
                return tmp;
210
        }
211
        constexpr blockbinary& operator++() {
3,041,771✔
212
                blockbinary increment;
213
                increment.setbits(0x1);
3,041,771✔
214
                *this += increment;
3,041,771✔
215
                return *this;
3,041,771✔
216
        }
217
        constexpr blockbinary operator--(int) {
218
                blockbinary tmp(*this);
219
                operator--();
220
                return tmp;
221
        }
222
        constexpr blockbinary& operator--() {
16✔
223
                blockbinary decrement;
224
                decrement.setbits(0x1);
16✔
225
                return *this -= decrement;
32✔
226
        }
227
        // logic operators
228
        blockbinary  operator~() {
229
                blockbinary<nbits, bt> complement(*this);
230
                complement.flip();
231
                return complement;
232
        }
233
        // arithmetic operators
234
        constexpr blockbinary& operator+=(const blockbinary& rhs) {
231,999,095✔
235
                if constexpr (nrBlocks == 1) {
236
                        _block[0] = static_cast<bt>(_block[0] + rhs.block(0));
117,808,319✔
237
                        // null any leading bits that fall outside of nbits
238
                        _block[MSU] = static_cast<bt>(MSU_MASK & _block[MSU]);
117,808,319✔
239
                }
240
                else if constexpr (bitsInBlock == 64) {
241
                        // uint64_t limbs: addcarry uses platform intrinsics (not constexpr on MSVC).
242
                        // In a constant-evaluated context, fall back to portable carry detection.
243
                        blockbinary sum;
244
                        if (std::is_constant_evaluated()) {
4,584✔
NEW
245
                                uint64_t carry = 0;
×
NEW
246
                                for (unsigned i = 0; i < nrBlocks; ++i) {
×
NEW
247
                                        uint64_t a = _block[i];
×
NEW
248
                                        uint64_t b = rhs._block[i];
×
NEW
249
                                        uint64_t s1 = a + carry;
×
NEW
250
                                        uint64_t c1 = (s1 < a) ? 1ull : 0ull;
×
NEW
251
                                        uint64_t r  = s1 + b;
×
NEW
252
                                        uint64_t c2 = (r < s1) ? 1ull : 0ull;
×
NEW
253
                                        sum._block[i] = r;
×
NEW
254
                                        carry = c1 + c2;
×
255
                                }
256
                        }
257
                        else {
258
                                uint64_t carry = 0;
4,584✔
259
                                for (unsigned i = 0; i < nrBlocks; ++i) {
34,845✔
260
                                        sum._block[i] = addcarry(_block[i], rhs._block[i], carry, carry);
30,261✔
261
                                }
262
                        }
263
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
264
                        sum._block[MSU] = static_cast<bt>(MSU_MASK & sum._block[MSU]);
4,584✔
265
                        *this = sum;
4,584✔
266
                }
267
                else {
268
                        // Multi-limb path for bt = uint8_t/uint16_t/uint32_t.
269
                        // Index-based loop (constexpr-friendly).
270
                        blockbinary sum;
271
                        std::uint64_t carry = 0;
114,186,192✔
272
                        for (unsigned i = 0; i < nrBlocks; ++i) {
372,245,607✔
273
                                carry += static_cast<std::uint64_t>(_block[i]) + static_cast<std::uint64_t>(rhs._block[i]);
258,059,415✔
274
                                sum._block[i] = static_cast<bt>(carry);
258,059,415✔
275
                                carry >>= bitsInBlock;
258,059,415✔
276
                        }
277
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
278
                        sum._block[MSU] = static_cast<bt>(MSU_MASK & sum._block[MSU]);
114,186,192✔
279
                        *this = sum;
114,186,192✔
280
                }
281
                return *this;
231,999,095✔
282
        }
283
        constexpr blockbinary& operator-=(const blockbinary& rhs) {
27,144,769✔
284
                return operator+=(sw::universal::twosComplement(rhs));
27,144,769✔
285
        }
286
#define BLOCKBINARY_FAST_MUL
287
#ifdef BLOCKBINARY_FAST_MUL
288
        blockbinary& operator*=(const blockbinary& rhs) {
72,451,320✔
289
                if constexpr (NumberType == BinaryNumberType::Signed) {
290
                        if constexpr (nrBlocks == 1) {
291
                                _block[0] = static_cast<bt>(static_cast<std::uint64_t>(_block[0]) * static_cast<std::uint64_t>(rhs.block(0)));
47,451,584✔
292
                        }
293
                        else if constexpr (bitsInBlock == 64) {
294
                                // uint64_t limbs: use mul128/addcarry intrinsics
295
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
614✔
296
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
614✔
297
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
614✔
298
                                if (base.isneg()) {
614✔
299
                                        base.twosComplement();
1✔
300
                                }
301
                                if (multiplicant.isneg()) {
614✔
302
                                        multiplicant.twosComplement();
×
303
                                }
304
                                clear();
614✔
305
                                for (unsigned i = 0; i < nrBlocks; ++i) {
2,618✔
306
                                        uint64_t carry = 0;
2,004✔
307
                                        for (unsigned j = 0; j < nrBlocks; ++j) {
10,844✔
308
                                                if (i + j < nrBlocks) {
8,840✔
309
                                                        uint64_t lo, hi;
310
                                                        mul128(base.block(i), multiplicant.block(j), lo, hi);
5,422✔
311
                                                        // accumulate: _block[i+j] += lo + carry
312
                                                        // use two separate additions to avoid passing multi-bit carry
313
                                                        // to addcarry (MSVC _addcarry_u64 only accepts 0/1 carry_in)
314
                                                        uint64_t c1 = 0;
5,422✔
315
                                                        uint64_t sum = addcarry(_block[i + j], lo, 0, c1);
5,422✔
316
                                                        uint64_t c2 = 0;
5,422✔
317
                                                        sum = addcarry(sum, carry, 0, c2);
5,422✔
318
                                                        _block[i + j] = sum;
5,422✔
319
                                                        carry = hi + c1 + c2;
5,422✔
320
                                                }
321
                                        }
322
                                }
323
                                if (resultIsNeg) twosComplement();
614✔
324
                        }
325
                        else {
326
                                // is there a better way than upconverting to deal with maxneg in a 2's complement encoding?
327
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
24,999,122✔
328
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
24,999,122✔
329
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
24,999,122✔
330
                                if (base.isneg()) {
24,999,122✔
331
                                        base.twosComplement();
12,484,610✔
332
                                }
333
                                if (multiplicant.isneg()) {
24,999,122✔
334
                                        multiplicant.twosComplement();
12,484,609✔
335
                                }
336
                                clear();
24,999,122✔
337
                                for (unsigned i = 0; i < static_cast<unsigned>(nrBlocks); ++i) {
76,357,496✔
338
                                        std::uint64_t segment(0);
51,358,374✔
339
                                        for (unsigned j = 0; j < static_cast<unsigned>(nrBlocks); ++j) {
158,707,406✔
340
                                                segment += static_cast<std::uint64_t>(base.block(i)) * static_cast<std::uint64_t>(multiplicant.block(j));
107,349,032✔
341

342
                                                if (i + j < static_cast<unsigned>(nrBlocks)) {
107,349,032✔
343
                                                        segment += _block[i + j];
79,353,703✔
344
                                                        _block[i + j] = static_cast<bt>(segment);
79,353,703✔
345
                                                        segment >>= bitsInBlock;
79,353,703✔
346
                                                }
347
                                        }
348
                                }
349
                                if (resultIsNeg) twosComplement();
24,999,122✔
350
                        }
351
                }
352
                else {  // unsigned
353
                        if constexpr (nrBlocks == 1) {
354
                                _block[0] = static_cast<bt>(static_cast<std::uint64_t>(_block[0]) * static_cast<std::uint64_t>(rhs.block(0)));
355
                        }
356
                        else if constexpr (bitsInBlock == 64) {
357
                                // uint64_t limbs: use mul128/addcarry intrinsics
358
                                blockbinary base(*this);
359
                                blockbinary multiplicant(rhs);
360
                                clear();
361
                                for (unsigned i = 0; i < nrBlocks; ++i) {
362
                                        uint64_t carry = 0;
363
                                        for (unsigned j = 0; j < nrBlocks; ++j) {
364
                                                if (i + j < nrBlocks) {
365
                                                        uint64_t lo, hi;
366
                                                        mul128(base.block(i), multiplicant.block(j), lo, hi);
367
                                                        // accumulate: _block[i+j] += lo + carry
368
                                                        // use two separate additions to avoid passing multi-bit carry
369
                                                        // to addcarry (MSVC _addcarry_u64 only accepts 0/1 carry_in)
370
                                                        uint64_t c1 = 0;
371
                                                        uint64_t sum = addcarry(_block[i + j], lo, 0, c1);
372
                                                        uint64_t c2 = 0;
373
                                                        sum = addcarry(sum, carry, 0, c2);
374
                                                        _block[i + j] = sum;
375
                                                        carry = hi + c1 + c2;
376
                                                }
377
                                        }
378
                                }
379
                        }
380
                        else {
381
                                blockbinary base(*this);
382
                                blockbinary multiplicant(rhs);
383
                                clear();
384
                                for (unsigned i = 0; i < static_cast<unsigned>(nrBlocks); ++i) {
385
                                        std::uint64_t segment(0);
386
                                        for (unsigned j = 0; j < static_cast<unsigned>(nrBlocks); ++j) {
387
                                                segment += static_cast<std::uint64_t>(base.block(i)) * static_cast<std::uint64_t>(multiplicant.block(j));
388

389
                                                if (i + j < static_cast<unsigned>(nrBlocks)) {
390
                                                        segment += _block[i + j];
391
                                                        _block[i + j] = static_cast<bt>(segment);
392
                                                        segment >>= bitsInBlock;
393
                                                }
394
                                        }
395
                                }
396
                        }
397
                }
398
                // null any leading bits that fall outside of nbits
399
                _block[MSU] = static_cast<bt>(MSU_MASK & _block[MSU]);
72,451,320✔
400
                return *this;
72,451,320✔
401
        }
402
#else
403
        blockbinary& operator*=(const blockbinary& rhs) { // modulo in-place
404
                blockbinary base(*this);
405
                blockbinary multiplicant(rhs);
406
                clear();
407
                for (unsigned i = 0; i < nbits; ++i) {
408
                        if (base.at(i)) {
409
                                operator+=(multiplicant);
410
                        }
411
                        multiplicant <<= 1;
412
                }
413
                // since we used operator+=, which enforces the nulling of leading bits
414
                // we don't need to null here
415
                return *this;
416
        }
417
#endif
418
        blockbinary& operator/=(const blockbinary& rhs) {
465,392✔
419
                if constexpr (nbits == (sizeof(BlockType) * 8)) {
420
                        if (rhs.iszero()) {
461,155✔
421
                                *this = 0;
281✔
422
                                return *this;
281✔
423
                        }
424
                        if constexpr (sizeof(BlockType) == 1) {
425
                                _block[0] = static_cast<bt>(std::int8_t(_block[0]) / std::int8_t(rhs._block[0]));
67,148✔
426
                        }
427
                        else if constexpr (sizeof(BlockType) == 2) {
428
                                _block[0] = static_cast<bt>(std::int16_t(_block[0]) / std::int16_t(rhs._block[0]));
393,702✔
429
                        }
430
                        else if constexpr (sizeof(BlockType) == 4) {
431
                                _block[0] = static_cast<bt>(std::int32_t(_block[0]) / std::int32_t(rhs._block[0]));
12✔
432
                        }
433
                        else if constexpr (sizeof(BlockType) == 8) {
434
                                _block[0] = static_cast<bt>(std::int64_t(_block[0]) / std::int64_t(rhs._block[0]));
12✔
435
                        }
436
                        _block[0] = static_cast<bt>(MSU_MASK & _block[0]);
460,874✔
437
                }
438
                else {
439
                        quorem<nbits, BlockType, NumberType> result = longdivision(*this, rhs);
4,237✔
440
                        *this = result.quo;
4,237✔
441
                }
442
                return *this;
465,111✔
443
        }
444
        blockbinary& operator%=(const blockbinary& rhs) {
1,598,742✔
445
                if constexpr (nbits == (sizeof(BlockType) * 8)) {
446
                        if (rhs.iszero()) {
264,257✔
447
                                *this = 0;
268✔
448
                                return *this;
268✔
449
                        }
450
                        if constexpr (sizeof(BlockType) == 1) {
451
                                _block[0] = static_cast<bt>(std::int8_t(_block[0]) % std::int8_t(rhs._block[0]));
66,384✔
452
                        }
453
                        else if constexpr (sizeof(BlockType) == 2) {
454
                                _block[0] = static_cast<bt>(std::int16_t(_block[0]) % std::int16_t(rhs._block[0]));
197,571✔
455
                        }
456
                        else if constexpr (sizeof(BlockType) == 4) {
457
                                _block[0] = static_cast<bt>(std::int32_t(_block[0]) % std::int32_t(rhs._block[0]));
16✔
458
                        }
459
                        else if constexpr (sizeof(BlockType) == 8) {
460
                                _block[0] = static_cast<bt>(std::int64_t(_block[0]) % std::int64_t(rhs._block[0]));
18✔
461
                        }
462
                        _block[0] = static_cast<bt>(MSU_MASK & _block[0]);
263,989✔
463
                }
464
                else {
465
                        quorem<nbits, BlockType, NumberType> result = longdivision(*this, rhs);
1,334,485✔
466
                        *this = result.rem;
1,334,485✔
467
                }
468
                return *this;
1,598,474✔
469
        }
470
        
471
        ///////////////////////////////////////////////////////////////////
472
        ///              logic operators
473

474
        constexpr blockbinary& operator|=(const blockbinary& rhs) noexcept {
50,854,259✔
475
                for (unsigned i = 0; i < nrBlocks; ++i) {
224,758,705✔
476
                        _block[i] |= rhs._block[i];
173,904,446✔
477
                }
478
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
50,854,259✔
479
                return *this;
50,854,259✔
480
        }
481
        constexpr blockbinary& operator&=(const blockbinary& rhs) noexcept {
482
                for (unsigned i = 0; i < nrBlocks; ++i) {
483
                        _block[i] &= rhs._block[i];
484
                }
485
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
486
                return *this;
487
        }
488
        constexpr blockbinary& operator^=(const blockbinary& rhs) noexcept {
892✔
489
                for (unsigned i = 0; i < nrBlocks; ++i) {
13,352✔
490
                        _block[i] ^= rhs._block[i];
12,460✔
491
                }
492
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
892✔
493
                return *this;
892✔
494
        }
495

496
        // shift left operator
497
        constexpr blockbinary& operator<<=(int bitsToShift) {
56,918,215✔
498
                if (bitsToShift == 0) return *this;
56,918,215✔
499
                if (bitsToShift < 0) return operator>>=(-bitsToShift);
56,694,074✔
500
                if (bitsToShift > static_cast<int>(nbits)) {
56,694,074✔
501
                        setzero();
2,097,152✔
502
                        return *this;
2,097,152✔
503
                }
504
                if (bitsToShift >= static_cast<int>(bitsInBlock)) {
54,596,922✔
505
                        int blockShift = bitsToShift / static_cast<int>(bitsInBlock);
13,759,354✔
506
                        for (int i = static_cast<int>(MSU); i >= blockShift; --i) {
71,125,461✔
507
                                _block[i] = _block[i - blockShift];
57,366,107✔
508
                        }
509
                        for (int i = blockShift - 1; i >= 0; --i) {
57,525,660✔
510
                                _block[i] = bt(0);
43,766,306✔
511
                        }
512
                        // adjust the shift
513
                        bitsToShift -= static_cast<int>(blockShift * bitsInBlock);
13,759,354✔
514
                        if (bitsToShift == 0) return *this;
13,759,354✔
515
                }
516
                if constexpr (MSU > 0) {
517
                        // construct the mask for the upper bits in the block that needs to move to the higher word
518
                        bt mask = 0xFFFFFFFFFFFFFFFF << (bitsInBlock - bitsToShift);
53,923,064✔
519
                        for (unsigned i = MSU; i > 0; --i) {
219,164,630✔
520
                                _block[i] <<= bitsToShift;
165,241,566✔
521
                                // mix in the bits from the right
522
                                bt bits = bt(mask & _block[i - 1]);
165,241,566✔
523
                                _block[i] |= (bits >> (bitsInBlock - bitsToShift));
165,241,566✔
524
                        }
525
                }
526
                _block[0] <<= bitsToShift;
54,523,436✔
527
                return *this;
54,523,436✔
528
        }
529
        // arithmetic shift right operator
530
        constexpr blockbinary& operator>>=(int bitsToShift) {
5,876,797✔
531
                if (bitsToShift == 0) return *this;
5,876,797✔
532
                if (bitsToShift < 0) return operator<<=(-bitsToShift);
5,868,077✔
533
                if (bitsToShift >= static_cast<int>(nbits)) {
5,868,077✔
534
                        setzero();
16✔
535
                        return *this;
16✔
536
                }
537
                bool signext = sign();
5,868,061✔
538
                unsigned blockShift = 0;
5,868,061✔
539
                if (bitsToShift >= static_cast<int>(bitsInBlock)) {
5,868,061✔
540
                        blockShift = bitsToShift / bitsInBlock;
4,128,970✔
541
                        if (MSU >= blockShift) {
4,128,970✔
542
                                // shift by blocks
543
                                for (unsigned i = 0; i <= MSU - blockShift; ++i) {
48,242,600✔
544
                                        _block[i] = _block[i + blockShift];
44,113,630✔
545
                                }
546
                        }
547
                        // adjust the shift
548
                        bitsToShift -= static_cast<int>(blockShift * bitsInBlock);
4,128,970✔
549
                        if (bitsToShift == 0) {
4,128,970✔
550
                                // fix up the leading zeros if we have a negative number
551
                                if (signext) {
89✔
552
                                        // bitsToShift is guaranteed to be less than nbits
553
                                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
10✔
554
                                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
98✔
555
                                                this->setbit(i);
88✔
556
                                        }
557
                                }
558
                                else {
559
                                        // clean up the blocks we have shifted clean
560
                                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
79✔
561
                                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
5,183✔
562
                                                this->setbit(i, false);
5,104✔
563
                                        }
564
                                }
565
                                return *this;
89✔
566
                        }
567
                }
568
                if constexpr (MSU > 0) {
569
                        bt mask = ALL_ONES;
5,841,530✔
570
                        mask >>= (bitsInBlock - bitsToShift); // this is a mask for the lower bits in the block that need to move to the lower word
5,841,530✔
571
                        for (unsigned i = 0; i < MSU; ++i) {  // TODO: can this be improved? we should not have to work on the upper blocks in case we block shifted
54,253,906✔
572
                                _block[i] >>= bitsToShift;
48,412,376✔
573
                                // mix in the bits from the left
574
                                bt bits = bt(mask & _block[i + 1]);
48,412,376✔
575
                                _block[i] |= (bits << (bitsInBlock - bitsToShift));
48,412,376✔
576
                        }
577
                }
578
                _block[MSU] >>= bitsToShift;
5,867,972✔
579

580
                // fix up the leading zeros if we have a negative number
581
                if (signext) {
5,867,972✔
582
                        // bitsToShift is guaranteed to be less than nbits
583
                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
57,811✔
584
                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
275,420✔
585
                                this->setbit(i);
217,609✔
586
                        }
587
                }
588
                else {
589
                        // clean up the blocks we have shifted clean
590
                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
5,810,161✔
591
                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
61,347,777✔
592
                                this->setbit(i, false);
55,537,616✔
593
                        }
594
                }
595

596
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
597
                _block[MSU] &= MSU_MASK;
5,867,972✔
598
                return *this;
5,867,972✔
599
        }
600

601

602
        ///////////////////////////////////////////////////////////////////
603
        ///                  modifiers
604

605
        constexpr void clear() noexcept {
1,337,071,187✔
606
                for (unsigned i = 0; i < nrBlocks; ++i) {
2,147,483,647✔
607
                        _block[i] = bt(0ull);
1,533,526,236✔
608
                }
609
        }
1,337,071,187✔
610
        constexpr void setzero() noexcept { clear(); }
2,097,168✔
611
        constexpr void set() noexcept { // set all bits to 1
10,004,088✔
612
                if constexpr (nrBlocks > 1) {
613
                        for (unsigned i = 0; i < nrBlocks - 1; ++i) {
7,779,135✔
614
                                _block[i] = ALL_ONES;
5,735,001✔
615
                        }
616
                }
617
                _block[MSU] = ALL_ONES & MSU_MASK;
10,004,088✔
618
        }
10,004,088✔
619
        constexpr void reset() noexcept { clear(); } // set all bits to 0
620
        constexpr void set(unsigned i) noexcept {        setbit(i, true); }
32✔
621
        constexpr void reset(unsigned i) noexcept { setbit(i, false); }
23,096,503✔
622
        constexpr void setbit(unsigned i, bool v = true) noexcept {
2,147,483,647✔
623
                if (i >= nbits) return;
2,147,483,647✔
624
                unsigned blockIndex = i / bitsInBlock;
2,147,483,647✔
625
                if (blockIndex < nrBlocks) {
2,147,483,647✔
626
                        // GCC -O3 produces false-positive -Warray-bounds when inlining
627
                        // setbit across blockbinary instantiations with different nbits
628
                        // (e.g., blockbinary<129> calling into blockbinary<128> context).
629
                        // The bounds checks above are correct but GCC's interprocedural
630
                        // analysis loses track of which instantiation's _block is accessed.
631
#if defined(__GNUC__) && !defined(__clang__)
632
#pragma GCC diagnostic push
633
#pragma GCC diagnostic ignored "-Warray-bounds"
634
#if __GNUC__ >= 12
635
#pragma GCC diagnostic ignored "-Wstringop-overflow"
636
#endif
637
#endif
638
                        bt blockBits = _block[blockIndex];
2,147,483,647✔
639
                        bt null = ~(1ull << (i % bitsInBlock));
2,147,483,647✔
640
                        bt bit = bt(v ? 1 : 0);
2,147,483,647✔
641
                        bt mask = bt(bit << (i % bitsInBlock));
2,147,483,647✔
642
                        _block[blockIndex] = bt((blockBits & null) | mask);
2,147,483,647✔
643
#if defined(__GNUC__) && !defined(__clang__)
644
#pragma GCC diagnostic pop
645
#endif
646
                }
647
        }
648
        constexpr void setbits(uint64_t value) noexcept {
969,170,466✔
649
                if constexpr (1 == nrBlocks) {
650
                        _block[0] = value & storageMask;
852,788,048✔
651
                }
652
                else if constexpr (1 < nrBlocks) {
653
                        for (unsigned i = 0; i < nrBlocks; ++i) {
354,425,995✔
654
                                _block[i] = value & storageMask;
238,043,577✔
655
                                if constexpr (bitsInBlock < 64) {
656
                                        value >>= bitsInBlock;
238,004,655✔
657
                                }
658
                                else {
659
                                        value = 0;  // avoid shift overflow when bitsInBlock >= 64
38,922✔
660
                                }
661
                        }
662
                }
663
                _block[MSU] &= MSU_MASK; // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
969,170,466✔
664
        }
969,170,466✔
665
        constexpr void setblock(unsigned b, const bt& blockBits) noexcept {
80,028,350✔
666
                if (b < nrBlocks) _block[b] = blockBits; // nop if b is out of range
80,028,350✔
667
        }        
80,028,350✔
668
        constexpr blockbinary& flip() noexcept { // in-place one's complement
96,496,397✔
669
                for (unsigned i = 0; i < nrBlocks; ++i) {
275,330,349✔
670
                        _block[i] = bt(~_block[i]);
178,833,952✔
671
                }                
672
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
96,496,397✔
673
                return *this;
96,496,397✔
674
        }
675
        /// <summary>
676
        /// in-place 2's complement
677
        /// </summary>
678
        /// <returns>2's complement of original</returns>
679
        constexpr blockbinary& twosComplement() noexcept {
53,642,198✔
680
                blockbinary plusOne(1);
53,642,198✔
681
                if constexpr (NumberType == BinaryNumberType::Signed) {
682
                        flip();
53,642,198✔
683
                }
684
                else {
685
                        static_assert(NumberType == BinaryNumberType::Signed, "calling in-place 2's complement on an unsigned blockbinary"); // should this be allowed?
686
                }
687
                return *this += plusOne;
53,642,198✔
688
        }
689

690
        // minimum positive value of the blockbinary configuration
691
        constexpr blockbinary& minpos() noexcept {
692
                // minpos = 0000....00001
693
                clear();
694
                setbit(0, true);
695
                return *this;
696
        }
697
        // maximum positive value of the blockbinary configuration
698
        constexpr blockbinary& maxpos() noexcept {
63✔
699
                if constexpr (NumberType == BinaryNumberType::Signed) {
700
                        // maxpos = 01111....1111
701
                        clear();
63✔
702
                        flip();
63✔
703
                        setbit(nbits - 1, false);
63✔
704
                }
705
                else {
706
                        // maxpos = 11111....1111
707
                        clear();
708
                        flip();
709
                }
710
                return *this;
63✔
711
        }
712
        // zero
713
        constexpr blockbinary& zero() noexcept {
714
                clear();
715
                return *this;
716
        }
717
        // minimum negative value of the blockbinary configuration
718
        constexpr blockbinary& minneg() noexcept {
719
                if constexpr (NumberType == BinaryNumberType::Signed) {
720
                        // minneg = 11111....11111
721
                        clear();
722
                        flip();
723
                }
724
                else {
725
                        // minneg = 00000....00000
726
                        clear();
727
                }
728
                return *this;
729
        }
730
        // maximum negative value of the blockbinary configuration
731
        constexpr blockbinary& maxneg() noexcept {
1,407✔
732
                if constexpr (NumberType == BinaryNumberType::Signed) {
733
                        // maxneg = 10000....0000
734
                        clear();
1,407✔
735
                        setbit(nbits - 1);
1,407✔
736
                }
737
                else {
738
                        // maxneg = 00000....00000
739
                        clear();
740
                }
741
                                
742
                return *this;
1,407✔
743
        }
744

745
        // selectors
746
        constexpr bool sign() const noexcept { return _block[MSU] & SIGN_BIT_MASK; }
505,143,384✔
747
        constexpr bool ispos() const noexcept { return !sign(); }
44,487,121✔
748
        constexpr bool isneg() const noexcept { return sign(); }
161,315,875✔
749
        constexpr bool iszero() const noexcept {
661,450,915✔
750
                for (unsigned i = 0; i < nrBlocks; ++i) if (_block[i] != 0) return false;
719,462,788✔
751
                return true;
45,467,984✔
752
        }
753
        constexpr bool isodd() const noexcept { return _block[0] & 0x1;        }
754
        constexpr bool iseven() const noexcept { return !isodd(); }
755

756
        constexpr bool all() const noexcept {
502,175,460✔
757
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != ALL_ONES) return false;
71,836✔
758
                if (_block[MSU] != MSU_MASK) return false;
502,105,250✔
759
                return true;
33,977,476✔
760
        }
761
        constexpr bool any() const noexcept {
209,680✔
762
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != 0) return true;
74,016✔
763
                if (_block[MSU] & MSU_MASK) return true;
136,240✔
764
                return false;
10✔
765
        }
766
        constexpr bool anyAfter(unsigned bitIndex) const noexcept {  // TODO: optimize for limbs
12,767,910✔
767
                unsigned limit = (bitIndex < nbits) ? bitIndex : nbits;
12,767,910✔
768
                for (unsigned i = 0; i < limit; ++i) if (test(i)) return true;
20,240,870✔
769
                return false;
6,964,590✔
770
        }
771

772
        constexpr bool none() const noexcept {
791,029,884✔
773
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != 0) return false;
137,321,174✔
774
                if (_block[MSU] & MSU_MASK) return false;
756,241,289✔
775
                return true;
15,695,118✔
776
        }
777
        constexpr unsigned count() const noexcept { // TODO: optimize for limbs
69,904✔
778
                unsigned nrOnes = 0;
69,904✔
779
                for (unsigned i = 0; i < nbits; ++i) {
1,169,744✔
780
                        if (test(i)) ++nrOnes;
1,099,840✔
781
                }
782
                return nrOnes;
69,904✔
783
        }
784
        constexpr bool test(unsigned bitIndex) const noexcept { return at(bitIndex); }
2,147,483,647✔
785
        constexpr bool at(unsigned bitIndex) const noexcept {
2,147,483,647✔
786
                if (bitIndex >= nbits) return false; // fail silently as no-op
2,147,483,647✔
787
                unsigned blockIndex = bitIndex / bitsInBlock;
2,147,483,647✔
788
                bt limb = _block[blockIndex];
2,147,483,647✔
789
                bt mask = bt(1ull << (bitIndex % bitsInBlock));
2,147,483,647✔
790
                return (limb & mask);
2,147,483,647✔
791
        }
792
        constexpr uint8_t nibble(unsigned n) const noexcept {
360✔
793
                uint8_t retval{ 0 };
360✔
794
                if (n < (1 + ((nbits - 1) >> 2))) {
360✔
795
                        bt word = _block[(n * 4) / bitsInBlock];
360✔
796
                        unsigned nibbleIndexInWord = n % (bitsInBlock >> 2);
360✔
797
                        bt mask = static_cast<bt>(bt(0x0F) << (nibbleIndexInWord*4));
360✔
798
                        bt nibblebits = static_cast<bt>(mask & word);
360✔
799
                        retval = static_cast<uint8_t>(nibblebits >> (nibbleIndexInWord*4));
360✔
800
                }
801
                else { // nop when nibble index out of bounds
802
                        retval = 0;
×
803
                }
804
                return retval;
360✔
805
        }
806
        constexpr bt block(unsigned b) const noexcept {
641,057,826✔
807
                if (b < nrBlocks) return _block[b]; 
641,057,826✔
808
                return bt(0); // return 0 when block index out of bounds
×
809
        }
810

811
        // copy a value over from one blockbinary to this blockbinary
812
        // blockbinary is a 2's complement encoding, so we sign-extend by default
813
        template<unsigned srcbits>
814
        blockbinary<nbits, bt, NumberType>& assign(const blockbinary<srcbits, bt, NumberType>& rhs) {
185,900,123✔
815
                clear();
185,900,123✔
816
                // since bt is the same, we can simply copy the blocks in
817
                unsigned minNrBlocks = (this->nrBlocks < rhs.nrBlocks) ? this->nrBlocks : rhs.nrBlocks;
185,900,123✔
818
                for (unsigned i = 0; i < minNrBlocks; ++i) {
446,955,400✔
819
                        _block[i] = rhs.block(i);
261,055,277✔
820
                }
821
                if constexpr (nbits > srcbits) { // check if we need to sign extend
822
                        if (rhs.sign()) {
129,187,518✔
823
                                for (unsigned i = srcbits; i < nbits; ++i) { // TODO: replace bit-oriented sequence with block
157,919,136✔
824
                                        setbit(i);
85,042,086✔
825
                                }
826
                        }
827
                }
828
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
829
                _block[MSU] &= MSU_MASK;
185,900,123✔
830
                return *this;
185,900,123✔
831
        }
832

833
        // copy a value over from one blockbinary to this without sign-extending the value
834
        // blockbinary is a 2's complement encoding, so we sign-extend by default
835
        // for fraction/significent encodings, we need to turn off sign-extending.
836
        template<unsigned srcbits>
837
        blockbinary<nbits, bt, NumberType>& assignWithoutSignExtend(const blockbinary<srcbits, bt, NumberType>& rhs) {
33,284✔
838
                clear();
33,284✔
839
                // since bt is the same, we can simply copy the blocks in
840
                unsigned minNrBlocks = (this->nrBlocks < rhs.nrBlocks) ? this->nrBlocks : rhs.nrBlocks;
33,284✔
841
                for (unsigned i = 0; i < minNrBlocks; ++i) {
66,568✔
842
                        _block[i] = rhs.block(i);
33,284✔
843
                }
844
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
845
                _block[MSU] &= MSU_MASK;
33,284✔
846
                return *this;
33,284✔
847
        }
848

849
        // return the position of the most significant bit, -1 if v == 0
850
        int msb() const noexcept {
1,342,032✔
851
                for (int i = int(MSU); i >= 0; --i) {
2,150,649✔
852
                        if (_block[i] != 0) {
2,150,649✔
853
                                bt mask = (bt(1u) << (bitsInBlock-1));
1,342,032✔
854
                                for (int j = bitsInBlock - 1; j >= 0; --j) {
5,973,440✔
855
                                        if (_block[i] & mask) {
5,973,440✔
856
                                                return i * static_cast<int>(bitsInBlock) + j;
1,342,032✔
857
                                        }
858
                                        mask >>= 1;
4,631,408✔
859
                                }
860
                        }
861
                }
862
                return -1; // no significant bit found, all bits are zero
×
863
        }
864
        // conversion to native types
865
        int64_t to_sll() const {
138,755,956✔
866
                constexpr unsigned sizeoflonglong = 8 * sizeof(long long);
138,755,956✔
867
                int64_t ll{ 0 };
138,755,956✔
868
                int64_t mask{ 1 };
138,755,956✔
869
                unsigned upper = (nbits < sizeoflonglong ? nbits : sizeoflonglong);
138,755,956✔
870
                for (unsigned i = 0; i < upper; ++i) {
1,775,155,439✔
871
                        ll |= at(i) ? mask : 0;
1,636,399,483✔
872
                        mask <<= 1;
1,636,399,483✔
873
                }
874
                if (sign() && upper < sizeoflonglong) { // sign extend
138,755,956✔
875
                        for (unsigned i = upper; i < sizeoflonglong; ++i) {
2,147,483,647✔
876
                                ll |= mask;
2,147,483,647✔
877
                                mask <<= 1;
2,147,483,647✔
878
                        }
879
                }
880
                return ll;
138,755,956✔
881
        }
882
        uint64_t to_ull() const {
127,926,852✔
883
                uint64_t ull{ 0 };
127,926,852✔
884
                uint64_t mask{ 1 };
127,926,852✔
885
                uint32_t msb = nbits < 64 ? nbits : 64;
127,926,852✔
886
                for (uint32_t i = 0; i < msb; ++i) {
736,448,363✔
887
                        ull |= at(i) ? mask : 0;
608,521,511✔
888
                        mask <<= 1;
608,521,511✔
889
                }
890
                return ull;
127,926,852✔
891
        }
892
        template<typename Real,
893
                typename = typename std::enable_if< std::is_floating_point<Real>::value, Real >::type>
894
        Real to_native() const {
1,186,262✔
895
                blockbinary tmp(*this);
1,186,262✔
896
                if (isneg()) tmp.twosComplement();
1,186,262✔
897
                Real v{ 0.0 }, base{ 1.0 };
1,186,262✔
898
                for (unsigned i = 0; i < nbits; ++i) {
20,154,726✔
899
                        if (tmp.test(i)) v += base;
18,968,464✔
900
                        base *= 2.0;
18,968,464✔
901
                }
902
                return (isneg() ? -v : v);
1,186,262✔
903
        }
904
        // determine the rounding mode: result needs to be rounded up if true
905
        bool roundingMode(unsigned targetLsb) const {
129,117✔
906
                bool lsb = at(targetLsb);
129,117✔
907
                bool guard = (targetLsb == 0 ? false : at(targetLsb - 1));
129,117✔
908
                bool round = (targetLsb > 1 ? at(targetLsb - 2) : false);
129,117✔
909
                bool sticky =(targetLsb < 3 ? false : any(targetLsb - 3));
129,117✔
910
                bool tie = guard && !round && !sticky;
129,117✔
911
                return (lsb && tie) || (guard && !tie);
129,117✔
912
        }
913
        bool any(unsigned msb) const {
153,480✔
914
                msb = (msb > nbits - 1 ? nbits - 1 : msb);
153,480✔
915
                unsigned topBlock = msb / bitsInBlock;
153,480✔
916
                bt mask = bt(ALL_ONES >> (bitsInBlock - 1 - (msb % bitsInBlock)));
153,480✔
917
                for (unsigned i = 0; i < topBlock; ++i) {
153,857✔
918
                        if (_block[i] > 0) return true;
16,806✔
919
                }
920
                // process the partial block
921
                if (_block[topBlock] & mask) return true;
137,051✔
922
                return false;
53,813✔
923
        }
924

925
protected:
926
        // HELPER methods
927
        // none
928

929
private:
930
        bt _block[nrBlocks];
931

932
        //////////////////////////////////////////////////////////////////////////////
933
        // friend functions
934

935
        // integer - integer logic comparisons
936
        template<unsigned N, typename B, BinaryNumberType T>
937
        friend bool operator==(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs);
938
        template<unsigned N, typename B, BinaryNumberType T>
939
        friend bool operator!=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs);
940
        // the other logic operators are defined in terms of arithmetic terms
941

942
        template<unsigned N, typename B, BinaryNumberType T>
943
        friend std::ostream& operator<<(std::ostream& ostr, const blockbinary<N, B, T>& v);
944
};
945

946
// Generate a type tag for blockbinary
947
template<unsigned N, typename B, BinaryNumberType T>
948
std::string type_tag(const blockbinary<N, B, T>& = {}) {
×
949
        std::stringstream str;
×
950
        str << "blockbinary<"
951
                << std::setw(4) << N << ", "
×
952
                << typeid(B).name() << ", "
953
                << typeid(T).name() << '>';
×
954
        return str.str();
×
955
}
×
956

957
//////////////////////////////////////////////////////////////////////////////////
958
// logic operators
959

960
template<unsigned N, typename B, BinaryNumberType T>
961
bool operator==(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
231,855,907✔
962
        for (unsigned i = 0; i < lhs.nrBlocks; ++i) {
490,644,631✔
963
                if (lhs._block[i] != rhs._block[i]) {
309,012,182✔
964
                        return false;
50,223,458✔
965
                }
966
        }
967
        return true;
181,632,449✔
968
}
969
template<unsigned N, typename B, BinaryNumberType T>
970
bool operator!=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
174,678,892✔
971
        return !operator==(lhs, rhs);
174,678,892✔
972
}
973
template<unsigned N, typename B, BinaryNumberType T>
974
bool operator<(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
30,910,944✔
975
        if (lhs.ispos() && rhs.isneg()) return false; // need to filter out possible overflow conditions
30,910,944✔
976
        if (lhs.isneg() && rhs.ispos()) return true;  // need to filter out possible underflow conditions
24,329,150✔
977
        if (lhs == rhs) return false; // so the maxneg logic works
17,542,837✔
978
        blockbinary<N, B, T> mneg; maxneg<N, B>(mneg);
17,274,600✔
979
        if (rhs == mneg) return false; // special case: nothing is smaller than maximum negative
17,274,600✔
980
        blockbinary<N, B, T> diff = lhs - rhs;
17,266,397✔
981
        return diff.isneg();
17,266,397✔
982
}
983
template<unsigned N, typename B, BinaryNumberType T>
984
bool operator<=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
13,710,373✔
985
        return (lhs < rhs || lhs == rhs);
13,710,373✔
986
}
987
template<unsigned N, typename B, BinaryNumberType T>
988
bool operator>(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
223,655✔
989
        return !(lhs <= rhs);
223,655✔
990
}
991
template<unsigned N, typename B, BinaryNumberType T>
992
bool operator>=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
12,087,289✔
993
        return !(lhs < rhs);
12,087,289✔
994
}
995
///////////////////////////////////////////////////////////////////////////////
996
// binary operators
997

998
template<unsigned N, typename B, BinaryNumberType T>
999
blockbinary<N, B, T> operator+(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
100,800,007✔
1000
        blockbinary<N, B, T> c(a);
100,800,007✔
1001
        return c += b;
100,800,007✔
1002
}
1003
template<unsigned N, typename B, BinaryNumberType T >
1004
blockbinary<N, B, T> operator-(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
17,466,749✔
1005
        blockbinary<N, B, T> c(a);
17,466,749✔
1006
        return c -= b;
34,588,473✔
1007
}
1008
template<unsigned N, typename B, BinaryNumberType T>
1009
blockbinary<N, B, T> operator*(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
72,423,754✔
1010
        blockbinary<N, B, T> c(a);
72,423,754✔
1011
        return c *= b;
97,396,092✔
1012
}
1013
template<unsigned N, typename B, BinaryNumberType T>
1014
blockbinary<N, B, T> operator/(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
67,074✔
1015
        blockbinary<N, B, T> c(a);
67,074✔
1016
        return c /= b;
68,447✔
1017
}
1018
template<unsigned N, typename B, BinaryNumberType T>
1019
blockbinary<N, B, T> operator%(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
1,598,742✔
1020
        blockbinary<N, B, T> c(a);
1,598,742✔
1021
        return c %= b;
2,933,227✔
1022
}
1023

1024
template<unsigned N, typename B, BinaryNumberType T>
1025
blockbinary<N, B, T> operator<<(const blockbinary<N, B, T>& a, long b) {
752✔
1026
        blockbinary<N, B, T> c(a);
752✔
1027
        return c <<= b;
1,504✔
1028
}
1029
template<unsigned N, typename B, BinaryNumberType T>
1030
blockbinary<N, B, T> operator>>(const blockbinary<N, B, T>& a, long b) {
168✔
1031
        blockbinary<N, B, T> c(a);
168✔
1032
        return c >>= b;
336✔
1033
}
1034

1035
// divide a by b and return both quotient and remainder
1036
template<unsigned N, typename B, BinaryNumberType T>
1037
quorem<N, B, T> longdivision(const blockbinary<N, B, T>& dividend, const blockbinary<N, B, T>& divisor) {
1,341,250✔
1038
        static_assert(T == BinaryNumberType::Signed, "longdivision requires signed blockbinary types");
1039
        using BlockBinary = blockbinary<N + 1, B, T>;
1040
        quorem<N, B, T> result = { 0, 0, 0 };
1,341,250✔
1041
        if (divisor.iszero()) {
1,341,250✔
1042
                result.exceptionId = 1; // division by zero
1,799✔
1043
                return result;
1,799✔
1044
        }
1045
        // generate the absolute values to do long division 
1046
        // 2's complement special case -max requires an signed int that is 1 bit bigger to represent abs()
1047
        bool a_sign = dividend.sign();
1,339,451✔
1048
        bool b_sign = divisor.sign();
1,339,451✔
1049
        bool result_negative = (a_sign ^ b_sign);
1,339,451✔
1050
        // normalize both arguments to positive, which requires expansion by 1-bit to deal with maxneg
1051
        BlockBinary a(dividend);
1,339,451✔
1052
        BlockBinary b(divisor);
1,339,451✔
1053
        if (a_sign) a.twosComplement();
1,339,451✔
1054
        if (b_sign) b.twosComplement();
1,339,451✔
1055

1056
        if (a < b) { // optimization for integer numbers
1,339,451✔
1057
                result.rem = dividend; // a % b = a when a / b = 0
668,435✔
1058
                return result;         // a / b = 0 when b > a
668,435✔
1059
        }
1060
        // initialize the long division
1061
        BlockBinary accumulator = a;
671,016✔
1062
        // prepare the subtractand
1063
        BlockBinary subtractand = b;
671,016✔
1064
        int msb_b = b.msb();
671,016✔
1065
        int msb_a = a.msb();
671,016✔
1066
        int shift = msb_a - msb_b;
671,016✔
1067
        subtractand <<= shift;
671,016✔
1068
        // long division
1069
        for (int i = shift; i >= 0; --i) {
2,289,020✔
1070
                if (subtractand <= accumulator) {
1,618,004✔
1071
                        accumulator -= subtractand;
963,461✔
1072
                        result.quo.setbit(static_cast<unsigned>(i));
963,461✔
1073
                }
1074
                else {
1075
                        result.quo.setbit(static_cast<unsigned>(i), false);
654,543✔
1076
                }
1077
                subtractand >>= 1;
1,618,004✔
1078
        }
1079
        if (result_negative) {  // take 2's complement
671,016✔
1080
                result.quo.flip();
333,196✔
1081
                result.quo += 1;
333,196✔
1082
        }
1083
        if (a_sign) {
671,016✔
1084
                result.rem = -accumulator;
334,077✔
1085
        }
1086
        else {
1087
                result.rem = accumulator;
336,939✔
1088
        }
1089
        return result;
671,016✔
1090
}
1091

1092
///////////////////////////////////////////////////////////////////////////////
1093
// specialty binary operators
1094

1095
// unrounded addition, returns a blockbinary that is of size nbits+1
1096
template<unsigned N, typename B, BinaryNumberType T>
1097
blockbinary<N + 1, B, T> uradd(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
3,400,060✔
1098
        blockbinary<N + 1, B, T> result(a);
3,400,060✔
1099
        return result += blockbinary<N + 1, B, T>(b);
3,400,060✔
1100
}
1101

1102
// unrounded subtraction, returns a blockbinary that is of size nbits+1
1103
template<unsigned N, typename B, BinaryNumberType T>
1104
blockbinary<N + 1, B, T> ursub(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
8,591,552✔
1105
        blockbinary<N + 1, B, T> result(a);
8,591,552✔
1106
        return result -= blockbinary<N + 1, B, T>(b);
8,591,552✔
1107
}
1108

1109
#define TRACE_URMUL 0
1110
// unrounded multiplication, returns a blockbinary that is of size 2*nbits
1111
// using brute-force sign-extending of operands to yield correct sign-extended result for 2*nbits 2's complement.
1112
template<unsigned N, typename B, BinaryNumberType T>
1113
blockbinary<2*N, B, T> urmul(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
99✔
1114
        using BlockBinary = blockbinary<2 * N, B, T>;
1115
        BlockBinary result(0);
99✔
1116
        if (a.iszero() || b.iszero()) return result;
99✔
1117

1118
        // compute the result
1119
        BlockBinary signextended_a(a);
99✔
1120
        BlockBinary multiplicant(b);
99✔
1121
#if TRACE_URMUL
1122
        std::cout << "    " << to_binary(a) << " * " << to_binary(b) << std::endl;
1123
        std::cout << std::setw(3) << 0 << ' ' << to_binary(multiplicant) << ' ' << to_binary(result) << std::endl;
1124
#endif
1125
        for (unsigned i = 0; i < 2*N; ++i) {
9,627✔
1126
                if (signextended_a.at(i)) {
9,528✔
1127
                        result += multiplicant;
221✔
1128
                }
1129
                multiplicant <<= 1;
9,528✔
1130
#if TRACE_URMUL
1131
                std::cout << std::setw(3) << i << ' ' << to_binary(multiplicant) << ' ' << to_binary(result) << std::endl;
1132
#endif
1133

1134
        }
1135
#if TRACE_URMUL
1136
        std::cout << "fnl " << to_binary(result) << std::endl;
1137
#endif
1138
        //blockbinary<2 * nbits, bt> clipped(result);
1139
        // since we used operator+=, which enforces the nulling of leading bits
1140
        // we don't need to null here
1141
        return result;
99✔
1142
}
1143

1144
// unrounded multiplication, returns a blockbinary that is of size 2*nbits
1145
// using nbits modulo arithmetic with final sign
1146
template<unsigned N, typename B, BinaryNumberType T>
1147
blockbinary<2 * N, B, T> urmul2(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
139,964✔
1148
        blockbinary<2 * N, B, T> result(0);
139,964✔
1149
        if (a.iszero() || b.iszero()) return result;
139,964✔
1150

1151
        // compute the result
1152
        bool result_sign = a.sign() ^ b.sign();
137,364✔
1153
        // normalize both arguments to positive in new size
1154
        blockbinary<N + 1, B, T> a_new(a); // TODO optimize: now create a, create _a.bb, copy, destroy _a.bb_copy
137,364✔
1155
        blockbinary<N + 1, B, T> b_new(b);
137,364✔
1156
        if (a.sign()) a_new.twosComplement();
137,364✔
1157
        if (b.sign()) b_new.twosComplement();
137,364✔
1158
        blockbinary<2*N, B, T> multiplicant(b_new);
137,364✔
1159

1160
#if TRACE_URMUL
1161
        std::cout << "    " << a_new << " * " << b_new << std::endl;
1162
        std::cout << std::setw(3) << 0 << ' ' << multiplicant << ' ' << result << std::endl;
1163
#endif
1164
        for (unsigned i = 0; i < (N+1); ++i) {
1,370,003✔
1165
                if (a_new.at(i)) {
1,232,639✔
1166
                        result += multiplicant;  // if multiplicant is not the same size as result, the assignment will get sign-extended if the MSB is true, this is not correct because we are assuming unsigned binaries in this loop
411,482✔
1167
                }
1168
                multiplicant <<= 1;
1,232,639✔
1169
#if TRACE_URMUL
1170
                std::cout << std::setw(3) << i << ' ' << multiplicant << ' ' << result << std::endl;
1171
#endif
1172
        }
1173
        if (result_sign) result.twosComplement();
137,364✔
1174
#if TRACE_URMUL
1175
        std::cout << "fnl " << result << std::endl;
1176
#endif
1177
        return result;
137,364✔
1178
}
1179

1180
#define TRACE_DIV 0
1181
// unrounded division, returns a blockbinary that is of size 2*nbits
1182
template<unsigned nbits, unsigned roundingBits, typename B, BinaryNumberType T>
1183
blockbinary<2 * nbits + roundingBits, B, T> urdiv(const blockbinary<nbits, B, T>& a, const blockbinary<nbits, B, T>& b) {
1184
        if (b.iszero()) {
1185
                // division by zero
1186
                throw "urdiv divide by zero";
1187
        }
1188
        // generate the absolute values to do long division 
1189
        // 2's complement special case -max requires an signed int that is 1 bit bigger to represent abs()
1190
        bool a_sign = a.sign();
1191
        bool b_sign = b.sign();
1192
        bool result_negative = (a_sign ^ b_sign);
1193

1194
        // normalize both arguments to positive, which requires expansion by 1-bit to deal with maxneg
1195
        blockbinary<nbits + 1, B, T> a_new(a); // TODO optimize: now create a, create _a.bb, copy, destroy _a.bb_copy
1196
        blockbinary<nbits + 1, B, T> b_new(b);
1197
#if TRACE_DIV
1198
        std::cout << "a " << to_binary(a_new) << '\n';
1199
        std::cout << "b " << to_binary(b_new) << '\n';
1200
#endif
1201
        if (a_sign) a_new.twosComplement();
1202
        if (b_sign) b_new.twosComplement();
1203
#if TRACE_DIV
1204
        std::cout << "a " << to_binary(a_new) << '\n';
1205
        std::cout << "b " << to_binary(b_new) << '\n';
1206
#endif
1207

1208
        // initialize the long division
1209
        blockbinary<2 * nbits + roundingBits + 1, B, T> decimator(a_new);
1210
        blockbinary<2 * nbits + roundingBits + 1, B, T> subtractand(b_new); // prepare the subtractand
1211
        blockbinary<2 * nbits + roundingBits + 1, B, T> result;
1212

1213
        constexpr unsigned msp = nbits + roundingBits; // msp = most significant position
1214
        decimator <<= msp; // scale the decimator to the largest possible positive value
1215

1216
        int msb_b = subtractand.msb();
1217
        int msb_a = decimator.msb();
1218
        int shift = msb_a - msb_b;
1219
        subtractand <<= shift;
1220
        int offset = msb_a - static_cast<int>(msp);  // msb of the result
1221
        int scale  = shift - static_cast<int>(msp);  // scale of the result quotient
1222

1223
#if TRACE_DIV
1224
        std::cout << "  " << to_binary(decimator, true)   << " msp  : " << msp << '\n';
1225
        std::cout << "- " << to_binary(subtractand, true) << " shift: " << shift << '\n';
1226
#endif
1227
        // long division
1228
        for (int i = msb_a; i >= 0; --i) {
1229

1230
                if (subtractand <= decimator) {
1231
                        decimator -= subtractand;
1232
                        result.setbit(static_cast<unsigned>(i));
1233
                }
1234
                else {
1235
                        result.setbit(static_cast<unsigned>(i), false);
1236
                }
1237
                subtractand >>= 1;
1238

1239
#if TRACE_DIV
1240
                std::cout << "  " << to_binary(decimator, true) << "  current quotient: " << to_binary(result, true) << '\n';
1241
                std::cout << "- " << to_binary(subtractand, true) << '\n';
1242
#endif
1243
        }
1244
        result <<= (scale - offset);
1245
#if TRACE_DIV
1246
        std::cout << "  " << "scaled result: " << to_binary(result, true) << " scale : " << scale << " offset : " << offset << '\n';
1247
#endif
1248
        if (result_negative) result.twosComplement();
1249
        return result;
1250
}
1251

1252
//////////////////////////////////////////////////////////////////////////////
1253
// conversions to string representations
1254

1255
// create a binary representation of the storage
1256
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1257
std::string to_binary(const blockbinary<nbits, BlockType, NumberType>& number, bool nibbleMarker = false) {
1,600✔
1258
        std::stringstream s;
1,600✔
1259
        s << "0b";
1,600✔
1260
        for (unsigned i = 0; i < nbits; ++i) {
43,920✔
1261
                unsigned bitIndex = nbits - 1ull - i;
42,320✔
1262
                s << (number.at(bitIndex) ? '1' : '0');
42,320✔
1263
                if (bitIndex > 0 && (bitIndex % 4) == 0 && nibbleMarker) s << '\'';
42,320✔
1264
        }
1265
        return s.str();
3,200✔
1266
}
1,600✔
1267

1268
// local helper to display the contents of a byte array
1269
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1270
std::string to_hex(const blockbinary<nbits, BlockType, NumberType>& number, bool nibbleMarker = true) {
53✔
1271
        static constexpr unsigned bitsInByte = 8;
1272
        static constexpr unsigned bitsInBlock = sizeof(BlockType) * bitsInByte;
1273
        char hexChar[16] = {
53✔
1274
                '0', '1', '2', '3', '4', '5', '6', '7',
1275
                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
1276
        };
1277
        std::stringstream ss;
53✔
1278
        ss << "0x" << std::hex;
53✔
1279
        int nrNibbles = int(1 + ((nbits - 1) >> 2));
53✔
1280
        for (int n = nrNibbles - 1; n >= 0; --n) {
413✔
1281
                uint8_t nibble = number.nibble(static_cast<unsigned>(n));
360✔
1282
                ss << hexChar[nibble];
360✔
1283
                if (nibbleMarker && n > 0 && ((n * 4ll) % bitsInBlock) == 0) ss << '\'';
360✔
1284
        }
1285
        return ss.str();
106✔
1286
}
53✔
1287

1288
// decimal string conversion
1289
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1290
std::string to_decimal(const blockbinary<nbits, BlockType, NumberType>& number) {
1,837✔
1291
        if (number.iszero()) return "0";
2,125✔
1292

1293
        std::string result;
1,693✔
1294
        blockbinary<nbits, BlockType, NumberType> dividend(number);
1,693✔
1295
        bool isNegative = false;
1,693✔
1296

1297
        // Handle negative numbers for signed types
1298
        if constexpr (NumberType == BinaryNumberType::Signed) {
1299
                if (dividend.isneg()) {
1,691✔
1300
                        isNegative = true;
475✔
1301
                        dividend.twosComplement(); // Convert to positive
475✔
1302
                }
1303
        }
1304

1305
        // Repeatedly divide by 10 and collect remainders
1306
        blockbinary<nbits, BlockType, NumberType> ten(10);
1,693✔
1307
        while (!dividend.iszero()) {
5,083✔
1308
                if constexpr (nbits <= 64) {
1309
                        // For smaller sizes, use native division to avoid complexity
1310
                        uint64_t temp = dividend.to_ull();
2,840✔
1311
                        uint64_t remainder = temp % 10;
2,840✔
1312
                        result = char('0' + remainder) + result;
2,840✔
1313
                        dividend = temp / 10;
2,840✔
1314
                } else {
1315
                        // For larger sizes, use blockbinary division operators
1316
                        blockbinary<nbits, BlockType, NumberType> remainder = dividend % ten;
550✔
1317
                        uint64_t digit = remainder.to_ull();
550✔
1318
                        result = char('0' + digit) + result;
550✔
1319
                        dividend /= ten;
550✔
1320
                }
1321
        }
1322

1323
        if (isNegative) {
1,693✔
1324
                result = "-" + result;
475✔
1325
        }
1326

1327
        return result;
1,693✔
1328
}
1,693✔
1329

1330
// ostream operator
1331
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1332
std::ostream& operator<<(std::ostream& ostr, const blockbinary<nbits, BlockType, NumberType>& number) {
1,677✔
1333
        ostr << to_decimal(number);
1,677✔
1334
        return ostr;
1,677✔
1335
}
1336

1337

1338
}} // namespace sw::universal
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc