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

stillwater-sc / universal / 24936322271

25 Apr 2026 05:08PM UTC coverage: 84.34% (+0.007%) from 84.333%
24936322271

Pull #760

github

web-flow
Merge 81283ac28 into 5182a1d7b
Pull Request #760: feat(internal): blockbinary mul/div/mod constexpr foundation

55 of 63 new or added lines in 2 files covered. (87.3%)

3 existing lines in 2 files now uncovered.

44988 of 53341 relevant lines covered (84.34%)

6423032.42 hits per line

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

94.67
/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> constexpr 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,066✔
62
        blockbinary<nbits, BlockType, NumberType> twosC(orig);
41,088,066✔
63
        blockbinary<nbits, BlockType, NumberType> plusOne(1);
41,088,066✔
64
        twosC.flip();
41,088,066✔
65
        twosC += plusOne;
41,088,066✔
66
        return twosC;
41,088,066✔
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) {
7,834,117✔
72
        static_assert(tgtbits < srcbits, "truncate requires source posit to be bigger than target posit");
73
        constexpr unsigned diff = srcbits - tgtbits;
7,834,117✔
74
        for (unsigned i = 0; i < tgtbits; ++i) { // TODO: optimize for limbs
165,598,091✔
75
                tgt.setbit(i, src.test(i + diff));
157,763,974✔
76
        }
77
}
7,834,117✔
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
        constexpr blockbinary(const blockbinary<nnbits, BlockType, NumberType>& rhs) : _block{} { this->assign(rhs); }
150,673,971✔
118

119
        // initializer for long long
120
        constexpr blockbinary(long long initial_value) noexcept : _block{} { *this = initial_value; }
1,578,981✔
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 {
10,208,613✔
151
                if constexpr (1 < nrBlocks) {
152
                        for (unsigned i = 0; i < nrBlocks; ++i) {
39,853,306✔
153
                                _block[i] = rhs & storageMask;
31,036,851✔
154
                                if constexpr (bitsInBlock < 64) {
155
                                        rhs >>= bitsInBlock;
31,036,851✔
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;
8,816,455✔
163
                }
164
                else if constexpr (1 == nrBlocks) {
165
                        _block[0] = rhs & storageMask;
1,392,158✔
166
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
167
                        _block[MSU] &= MSU_MASK;
1,392,158✔
168
                }
169
                return *this;
10,208,613✔
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,968,529✔
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,074,855✔
190

191
        // prefix operators
192
        constexpr 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
        constexpr 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++() {
2,241,889✔
212
                blockbinary increment;
213
                increment.setbits(0x1);
2,241,889✔
214
                *this += increment;
2,241,889✔
215
                return *this;
2,241,889✔
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
        constexpr 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) {
229,019,266✔
235
                if constexpr (nrBlocks == 1) {
236
                        _block[0] = static_cast<bt>(_block[0] + rhs.block(0));
115,897,832✔
237
                        // null any leading bits that fall outside of nbits
238
                        _block[MSU] = static_cast<bt>(MSU_MASK & _block[MSU]);
115,897,832✔
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✔
245
                                uint64_t carry = 0;
×
246
                                for (unsigned i = 0; i < nrBlocks; ++i) {
×
247
                                        uint64_t a = _block[i];
×
248
                                        uint64_t b = rhs._block[i];
×
249
                                        uint64_t s1 = a + carry;
×
250
                                        uint64_t c1 = (s1 < a) ? 1ull : 0ull;
×
251
                                        uint64_t r  = s1 + b;
×
252
                                        uint64_t c2 = (r < s1) ? 1ull : 0ull;
×
253
                                        sum._block[i] = r;
×
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;
113,116,850✔
272
                        for (unsigned i = 0; i < nrBlocks; ++i) {
367,088,000✔
273
                                carry += static_cast<std::uint64_t>(_block[i]) + static_cast<std::uint64_t>(rhs._block[i]);
253,971,150✔
274
                                sum._block[i] = static_cast<bt>(carry);
253,971,150✔
275
                                carry >>= bitsInBlock;
253,971,150✔
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]);
113,116,850✔
279
                        *this = sum;
113,116,850✔
280
                }
281
                return *this;
229,019,266✔
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
        constexpr 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: mul128/addcarry use platform intrinsics that are not
295
                                // constexpr on MSVC. In a constant-evaluated context, fall back to a
296
                                // portable __uint128_t-based carry-propagation loop (gcc/clang only).
297
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
614✔
298
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
614✔
299
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
614✔
300
                                if (base.isneg()) {
614✔
301
                                        base.twosComplement();
1✔
302
                                }
303
                                if (multiplicant.isneg()) {
614✔
304
                                        multiplicant.twosComplement();
×
305
                                }
306
                                clear();
614✔
307
                                if (std::is_constant_evaluated()) {
614✔
308
#if defined(__SIZEOF_INT128__)
NEW
309
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
×
NEW
310
                                                uint128_t carry = 0;
×
NEW
311
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
×
NEW
312
                                                        if (i + j < nrBlocks) {
×
NEW
313
                                                                uint128_t product = static_cast<uint128_t>(base.block(i)) * multiplicant.block(j);
×
NEW
314
                                                                uint128_t sum = product + _block[i + j] + carry;
×
NEW
315
                                                                _block[i + j] = static_cast<bt>(sum);
×
NEW
316
                                                                carry = sum >> 64;
×
317
                                                        }
318
                                                }
319
                                        }
320
#else
321
                                        // Constexpr eval of uint64-limb mul without __int128 is unsupported
322
                                        // (MSVC). Forcing a throw in the constant-evaluated branch turns this
323
                                        // into a compile error rather than a silent zero-result. The branch
324
                                        // is unreachable at runtime because is_constant_evaluated() is false
325
                                        // there; the runtime intrinsic path below handles all execution.
326
                                        // Users on MSVC needing compile-time eval should use uint32_t limbs.
327
                                        throw "blockbinary<N, uint64_t> constexpr multiply requires __int128 support (gcc/clang); on MSVC use uint32_t limbs for compile-time evaluation";
328
#endif
329
                                }
330
                                else {
331
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
2,618✔
332
                                                uint64_t carry = 0;
2,004✔
333
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
10,844✔
334
                                                        if (i + j < nrBlocks) {
8,840✔
335
                                                                uint64_t lo, hi;
336
                                                                mul128(base.block(i), multiplicant.block(j), lo, hi);
5,422✔
337
                                                                // accumulate: _block[i+j] += lo + carry
338
                                                                // use two separate additions to avoid passing multi-bit carry
339
                                                                // to addcarry (MSVC _addcarry_u64 only accepts 0/1 carry_in)
340
                                                                uint64_t c1 = 0;
5,422✔
341
                                                                uint64_t sum = addcarry(_block[i + j], lo, 0, c1);
5,422✔
342
                                                                uint64_t c2 = 0;
5,422✔
343
                                                                sum = addcarry(sum, carry, 0, c2);
5,422✔
344
                                                                _block[i + j] = sum;
5,422✔
345
                                                                carry = hi + c1 + c2;
5,422✔
346
                                                        }
347
                                                }
348
                                        }
349
                                }
350
                                if (resultIsNeg) twosComplement();
614✔
351
                        }
352
                        else {
353
                                // is there a better way than upconverting to deal with maxneg in a 2's complement encoding?
354
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
24,999,122✔
355
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
24,999,122✔
356
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
24,999,122✔
357
                                if (base.isneg()) {
24,999,122✔
358
                                        base.twosComplement();
12,484,610✔
359
                                }
360
                                if (multiplicant.isneg()) {
24,999,122✔
361
                                        multiplicant.twosComplement();
12,484,609✔
362
                                }
363
                                clear();
24,999,122✔
364
                                for (unsigned i = 0; i < static_cast<unsigned>(nrBlocks); ++i) {
76,357,496✔
365
                                        std::uint64_t segment(0);
51,358,374✔
366
                                        for (unsigned j = 0; j < static_cast<unsigned>(nrBlocks); ++j) {
158,707,406✔
367
                                                segment += static_cast<std::uint64_t>(base.block(i)) * static_cast<std::uint64_t>(multiplicant.block(j));
107,349,032✔
368

369
                                                if (i + j < static_cast<unsigned>(nrBlocks)) {
107,349,032✔
370
                                                        segment += _block[i + j];
79,353,703✔
371
                                                        _block[i + j] = static_cast<bt>(segment);
79,353,703✔
372
                                                        segment >>= bitsInBlock;
79,353,703✔
373
                                                }
374
                                        }
375
                                }
376
                                if (resultIsNeg) twosComplement();
24,999,122✔
377
                        }
378
                }
379
                else {  // unsigned
380
                        if constexpr (nrBlocks == 1) {
381
                                _block[0] = static_cast<bt>(static_cast<std::uint64_t>(_block[0]) * static_cast<std::uint64_t>(rhs.block(0)));
382
                        }
383
                        else if constexpr (bitsInBlock == 64) {
384
                                // uint64_t limbs: same is_constant_evaluated dispatch as the signed path.
385
                                blockbinary base(*this);
386
                                blockbinary multiplicant(rhs);
387
                                clear();
388
                                if (std::is_constant_evaluated()) {
389
#if defined(__SIZEOF_INT128__)
390
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
391
                                                uint128_t carry = 0;
392
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
393
                                                        if (i + j < nrBlocks) {
394
                                                                uint128_t product = static_cast<uint128_t>(base.block(i)) * multiplicant.block(j);
395
                                                                uint128_t sum = product + _block[i + j] + carry;
396
                                                                _block[i + j] = static_cast<bt>(sum);
397
                                                                carry = sum >> 64;
398
                                                        }
399
                                                }
400
                                        }
401
#else
402
                                        // See the signed branch: forcing a throw turns silent zero-result
403
                                        // into a compile error on platforms without __int128 (MSVC).
404
                                        throw "blockbinary<N, uint64_t> constexpr multiply requires __int128 support (gcc/clang); on MSVC use uint32_t limbs for compile-time evaluation";
405
#endif
406
                                }
407
                                else {
408
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
409
                                                uint64_t carry = 0;
410
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
411
                                                        if (i + j < nrBlocks) {
412
                                                                uint64_t lo, hi;
413
                                                                mul128(base.block(i), multiplicant.block(j), lo, hi);
414
                                                                // accumulate: _block[i+j] += lo + carry
415
                                                                // use two separate additions to avoid passing multi-bit carry
416
                                                                // to addcarry (MSVC _addcarry_u64 only accepts 0/1 carry_in)
417
                                                                uint64_t c1 = 0;
418
                                                                uint64_t sum = addcarry(_block[i + j], lo, 0, c1);
419
                                                                uint64_t c2 = 0;
420
                                                                sum = addcarry(sum, carry, 0, c2);
421
                                                                _block[i + j] = sum;
422
                                                                carry = hi + c1 + c2;
423
                                                        }
424
                                                }
425
                                        }
426
                                }
427
                        }
428
                        else {
429
                                blockbinary base(*this);
430
                                blockbinary multiplicant(rhs);
431
                                clear();
432
                                for (unsigned i = 0; i < static_cast<unsigned>(nrBlocks); ++i) {
433
                                        std::uint64_t segment(0);
434
                                        for (unsigned j = 0; j < static_cast<unsigned>(nrBlocks); ++j) {
435
                                                segment += static_cast<std::uint64_t>(base.block(i)) * static_cast<std::uint64_t>(multiplicant.block(j));
436

437
                                                if (i + j < static_cast<unsigned>(nrBlocks)) {
438
                                                        segment += _block[i + j];
439
                                                        _block[i + j] = static_cast<bt>(segment);
440
                                                        segment >>= bitsInBlock;
441
                                                }
442
                                        }
443
                                }
444
                        }
445
                }
446
                // null any leading bits that fall outside of nbits
447
                _block[MSU] = static_cast<bt>(MSU_MASK & _block[MSU]);
72,451,320✔
448
                return *this;
72,451,320✔
449
        }
450
#else
451
        constexpr blockbinary& operator*=(const blockbinary& rhs) { // modulo in-place
452
                blockbinary base(*this);
453
                blockbinary multiplicant(rhs);
454
                clear();
455
                for (unsigned i = 0; i < nbits; ++i) {
456
                        if (base.at(i)) {
457
                                operator+=(multiplicant);
458
                        }
459
                        multiplicant <<= 1;
460
                }
461
                // since we used operator+=, which enforces the nulling of leading bits
462
                // we don't need to null here
463
                return *this;
464
        }
465
#endif
466
        constexpr blockbinary& operator/=(const blockbinary& rhs) {
465,392✔
467
                if constexpr (nbits == (sizeof(BlockType) * 8)) {
468
                        if (rhs.iszero()) {
461,155✔
469
                                *this = 0;
281✔
470
                                return *this;
281✔
471
                        }
472
                        if constexpr (sizeof(BlockType) == 1) {
473
                                _block[0] = static_cast<bt>(std::int8_t(_block[0]) / std::int8_t(rhs._block[0]));
67,148✔
474
                        }
475
                        else if constexpr (sizeof(BlockType) == 2) {
476
                                _block[0] = static_cast<bt>(std::int16_t(_block[0]) / std::int16_t(rhs._block[0]));
393,702✔
477
                        }
478
                        else if constexpr (sizeof(BlockType) == 4) {
479
                                _block[0] = static_cast<bt>(std::int32_t(_block[0]) / std::int32_t(rhs._block[0]));
12✔
480
                        }
481
                        else if constexpr (sizeof(BlockType) == 8) {
482
                                _block[0] = static_cast<bt>(std::int64_t(_block[0]) / std::int64_t(rhs._block[0]));
12✔
483
                        }
484
                        _block[0] = static_cast<bt>(MSU_MASK & _block[0]);
460,874✔
485
                }
486
                else {
487
                        quorem<nbits, BlockType, NumberType> result = longdivision(*this, rhs);
4,237✔
488
                        *this = result.quo;
4,237✔
489
                }
490
                return *this;
465,111✔
491
        }
492
        constexpr blockbinary& operator%=(const blockbinary& rhs) {
1,598,742✔
493
                if constexpr (nbits == (sizeof(BlockType) * 8)) {
494
                        if (rhs.iszero()) {
264,257✔
495
                                *this = 0;
268✔
496
                                return *this;
268✔
497
                        }
498
                        if constexpr (sizeof(BlockType) == 1) {
499
                                _block[0] = static_cast<bt>(std::int8_t(_block[0]) % std::int8_t(rhs._block[0]));
66,384✔
500
                        }
501
                        else if constexpr (sizeof(BlockType) == 2) {
502
                                _block[0] = static_cast<bt>(std::int16_t(_block[0]) % std::int16_t(rhs._block[0]));
197,571✔
503
                        }
504
                        else if constexpr (sizeof(BlockType) == 4) {
505
                                _block[0] = static_cast<bt>(std::int32_t(_block[0]) % std::int32_t(rhs._block[0]));
16✔
506
                        }
507
                        else if constexpr (sizeof(BlockType) == 8) {
508
                                _block[0] = static_cast<bt>(std::int64_t(_block[0]) % std::int64_t(rhs._block[0]));
18✔
509
                        }
510
                        _block[0] = static_cast<bt>(MSU_MASK & _block[0]);
263,989✔
511
                }
512
                else {
513
                        quorem<nbits, BlockType, NumberType> result = longdivision(*this, rhs);
1,334,485✔
514
                        *this = result.rem;
1,334,485✔
515
                }
516
                return *this;
1,598,474✔
517
        }
518
        
519
        ///////////////////////////////////////////////////////////////////
520
        ///              logic operators
521

522
        constexpr blockbinary& operator|=(const blockbinary& rhs) noexcept {
31,341,914✔
523
                for (unsigned i = 0; i < nrBlocks; ++i) {
142,078,908✔
524
                        _block[i] |= rhs._block[i];
110,736,994✔
525
                }
526
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
31,341,914✔
527
                return *this;
31,341,914✔
528
        }
529
        constexpr blockbinary& operator&=(const blockbinary& rhs) noexcept {
530
                for (unsigned i = 0; i < nrBlocks; ++i) {
531
                        _block[i] &= rhs._block[i];
532
                }
533
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
534
                return *this;
535
        }
536
        constexpr blockbinary& operator^=(const blockbinary& rhs) noexcept {
892✔
537
                for (unsigned i = 0; i < nrBlocks; ++i) {
13,352✔
538
                        _block[i] ^= rhs._block[i];
12,460✔
539
                }
540
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
892✔
541
                return *this;
892✔
542
        }
543

544
        // shift left operator
545
        constexpr blockbinary& operator<<=(int bitsToShift) {
37,405,707✔
546
                if (bitsToShift == 0) return *this;
37,405,707✔
547
                if (bitsToShift < 0) return operator>>=(-bitsToShift);
37,181,566✔
548
                if (bitsToShift > static_cast<int>(nbits)) {
37,181,566✔
549
                        setzero();
2,097,152✔
550
                        return *this;
2,097,152✔
551
                }
552
                if (bitsToShift >= static_cast<int>(bitsInBlock)) {
35,084,414✔
553
                        int blockShift = bitsToShift / static_cast<int>(bitsInBlock);
9,567,098✔
554
                        for (int i = static_cast<int>(MSU); i >= blockShift; --i) {
58,580,875✔
555
                                _block[i] = _block[i - blockShift];
49,013,777✔
556
                        }
557
                        for (int i = blockShift - 1; i >= 0; --i) {
41,218,783✔
558
                                _block[i] = bt(0);
31,651,685✔
559
                        }
560
                        // adjust the shift
561
                        bitsToShift -= static_cast<int>(blockShift * bitsInBlock);
9,567,098✔
562
                        if (bitsToShift == 0) return *this;
9,567,098✔
563
                }
564
                if constexpr (MSU > 0) {
565
                        // construct the mask for the upper bits in the block that needs to move to the higher word
566
                        bt mask = 0xFFFFFFFFFFFFFFFF << (bitsInBlock - bitsToShift);
34,707,386✔
567
                        for (unsigned i = MSU; i > 0; --i) {
156,305,755✔
568
                                _block[i] <<= bitsToShift;
121,598,369✔
569
                                // mix in the bits from the right
570
                                bt bits = bt(mask & _block[i - 1]);
121,598,369✔
571
                                _block[i] |= (bits >> (bitsInBlock - bitsToShift));
121,598,369✔
572
                        }
573
                }
574
                _block[0] <<= bitsToShift;
35,021,818✔
575
                return *this;
35,021,818✔
576
        }
577
        // arithmetic shift right operator
578
        constexpr blockbinary& operator>>=(int bitsToShift) {
5,876,797✔
579
                if (bitsToShift == 0) return *this;
5,876,797✔
580
                if (bitsToShift < 0) return operator<<=(-bitsToShift);
5,868,077✔
581
                if (bitsToShift >= static_cast<int>(nbits)) {
5,868,077✔
582
                        setzero();
16✔
583
                        return *this;
16✔
584
                }
585
                bool signext = sign();
5,868,061✔
586
                unsigned blockShift = 0;
5,868,061✔
587
                if (bitsToShift >= static_cast<int>(bitsInBlock)) {
5,868,061✔
588
                        blockShift = bitsToShift / bitsInBlock;
4,128,970✔
589
                        if (MSU >= blockShift) {
4,128,970✔
590
                                // shift by blocks
591
                                for (unsigned i = 0; i <= MSU - blockShift; ++i) {
48,242,600✔
592
                                        _block[i] = _block[i + blockShift];
44,113,630✔
593
                                }
594
                        }
595
                        // adjust the shift
596
                        bitsToShift -= static_cast<int>(blockShift * bitsInBlock);
4,128,970✔
597
                        if (bitsToShift == 0) {
4,128,970✔
598
                                // fix up the leading zeros if we have a negative number
599
                                if (signext) {
89✔
600
                                        // bitsToShift is guaranteed to be less than nbits
601
                                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
10✔
602
                                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
98✔
603
                                                this->setbit(i);
88✔
604
                                        }
605
                                }
606
                                else {
607
                                        // clean up the blocks we have shifted clean
608
                                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
79✔
609
                                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
5,183✔
610
                                                this->setbit(i, false);
5,104✔
611
                                        }
612
                                }
613
                                return *this;
89✔
614
                        }
615
                }
616
                if constexpr (MSU > 0) {
617
                        bt mask = ALL_ONES;
5,841,530✔
618
                        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✔
619
                        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✔
620
                                _block[i] >>= bitsToShift;
48,412,376✔
621
                                // mix in the bits from the left
622
                                bt bits = bt(mask & _block[i + 1]);
48,412,376✔
623
                                _block[i] |= (bits << (bitsInBlock - bitsToShift));
48,412,376✔
624
                        }
625
                }
626
                _block[MSU] >>= bitsToShift;
5,867,972✔
627

628
                // fix up the leading zeros if we have a negative number
629
                if (signext) {
5,867,972✔
630
                        // bitsToShift is guaranteed to be less than nbits
631
                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
57,811✔
632
                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
275,420✔
633
                                this->setbit(i);
217,609✔
634
                        }
635
                }
636
                else {
637
                        // clean up the blocks we have shifted clean
638
                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
5,810,161✔
639
                        for (unsigned i = nbits - bitsToShift; i < nbits; ++i) {
61,347,777✔
640
                                this->setbit(i, false);
55,537,616✔
641
                        }
642
                }
643

644
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
645
                _block[MSU] &= MSU_MASK;
5,867,972✔
646
                return *this;
5,867,972✔
647
        }
648

649

650
        ///////////////////////////////////////////////////////////////////
651
        ///                  modifiers
652

653
        constexpr void clear() noexcept {
1,306,474,841✔
654
                for (unsigned i = 0; i < nrBlocks; ++i) {
2,147,483,647✔
655
                        _block[i] = bt(0ull);
1,491,661,203✔
656
                }
657
        }
1,306,474,841✔
658
        constexpr void setzero() noexcept { clear(); }
2,097,168✔
659
        constexpr void set() noexcept { // set all bits to 1
10,004,045✔
660
                if constexpr (nrBlocks > 1) {
661
                        for (unsigned i = 0; i < nrBlocks - 1; ++i) {
7,778,995✔
662
                                _block[i] = ALL_ONES;
5,734,898✔
663
                        }
664
                }
665
                _block[MSU] = ALL_ONES & MSU_MASK;
10,004,045✔
666
        }
10,004,045✔
667
        constexpr void reset() noexcept { clear(); } // set all bits to 0
668
        constexpr void set(unsigned i) noexcept {        setbit(i, true); }
32✔
669
        constexpr void reset(unsigned i) noexcept { setbit(i, false); }
23,096,473✔
670
        constexpr void setbit(unsigned i, bool v = true) noexcept {
2,147,483,647✔
671
                if (i >= nbits) return;
2,147,483,647✔
672
                unsigned blockIndex = i / bitsInBlock;
2,147,483,647✔
673
                if (blockIndex < nrBlocks) {
2,147,483,647✔
674
                        // GCC -O3 produces false-positive -Warray-bounds when inlining
675
                        // setbit across blockbinary instantiations with different nbits
676
                        // (e.g., blockbinary<129> calling into blockbinary<128> context).
677
                        // The bounds checks above are correct but GCC's interprocedural
678
                        // analysis loses track of which instantiation's _block is accessed.
679
#if defined(__GNUC__) && !defined(__clang__)
680
#pragma GCC diagnostic push
681
#pragma GCC diagnostic ignored "-Warray-bounds"
682
#if __GNUC__ >= 12
683
#pragma GCC diagnostic ignored "-Wstringop-overflow"
684
#endif
685
#endif
686
                        bt blockBits = _block[blockIndex];
2,147,483,647✔
687
                        bt null = ~(1ull << (i % bitsInBlock));
2,147,483,647✔
688
                        bt bit = bt(v ? 1 : 0);
2,147,483,647✔
689
                        bt mask = bt(bit << (i % bitsInBlock));
2,147,483,647✔
690
                        _block[blockIndex] = bt((blockBits & null) | mask);
2,147,483,647✔
691
#if defined(__GNUC__) && !defined(__clang__)
692
#pragma GCC diagnostic pop
693
#endif
694
                }
695
        }
696
        constexpr void setbits(uint64_t value) noexcept {
973,320,147✔
697
                if constexpr (1 == nrBlocks) {
698
                        _block[0] = value & storageMask;
854,862,782✔
699
                }
700
                else if constexpr (1 < nrBlocks) {
701
                        for (unsigned i = 0; i < nrBlocks; ++i) {
364,606,104✔
702
                                _block[i] = value & storageMask;
246,148,739✔
703
                                if constexpr (bitsInBlock < 64) {
704
                                        value >>= bitsInBlock;
246,109,817✔
705
                                }
706
                                else {
707
                                        value = 0;  // avoid shift overflow when bitsInBlock >= 64
38,922✔
708
                                }
709
                        }
710
                }
711
                _block[MSU] &= MSU_MASK; // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
973,320,147✔
712
        }
973,320,147✔
713
        constexpr void setblock(unsigned b, const bt& blockBits) noexcept {
80,028,269✔
714
                if (b < nrBlocks) _block[b] = blockBits; // nop if b is out of range
80,028,269✔
715
        }        
80,028,269✔
716
        constexpr blockbinary& flip() noexcept { // in-place one's complement
94,287,764✔
717
                for (unsigned i = 0; i < nrBlocks; ++i) {
267,931,801✔
718
                        _block[i] = bt(~_block[i]);
173,644,037✔
719
                }                
720
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
94,287,764✔
721
                return *this;
94,287,764✔
722
        }
723
        /// <summary>
724
        /// in-place 2's complement
725
        /// </summary>
726
        /// <returns>2's complement of original</returns>
727
        constexpr blockbinary& twosComplement() noexcept {
51,462,281✔
728
                blockbinary plusOne(1);
51,462,281✔
729
                if constexpr (NumberType == BinaryNumberType::Signed) {
730
                        flip();
51,462,281✔
731
                }
732
                else {
733
                        static_assert(NumberType == BinaryNumberType::Signed, "calling in-place 2's complement on an unsigned blockbinary"); // should this be allowed?
734
                }
735
                return *this += plusOne;
51,462,281✔
736
        }
737

738
        // minimum positive value of the blockbinary configuration
739
        constexpr blockbinary& minpos() noexcept {
740
                // minpos = 0000....00001
741
                clear();
742
                setbit(0, true);
743
                return *this;
744
        }
745
        // maximum positive value of the blockbinary configuration
746
        constexpr blockbinary& maxpos() noexcept {
63✔
747
                if constexpr (NumberType == BinaryNumberType::Signed) {
748
                        // maxpos = 01111....1111
749
                        clear();
63✔
750
                        flip();
63✔
751
                        setbit(nbits - 1, false);
63✔
752
                }
753
                else {
754
                        // maxpos = 11111....1111
755
                        clear();
756
                        flip();
757
                }
758
                return *this;
63✔
759
        }
760
        // zero
761
        constexpr blockbinary& zero() noexcept {
762
                clear();
763
                return *this;
764
        }
765
        // minimum negative value of the blockbinary configuration
766
        constexpr blockbinary& minneg() noexcept {
767
                if constexpr (NumberType == BinaryNumberType::Signed) {
768
                        // minneg = 11111....11111
769
                        clear();
770
                        flip();
771
                }
772
                else {
773
                        // minneg = 00000....00000
774
                        clear();
775
                }
776
                return *this;
777
        }
778
        // maximum negative value of the blockbinary configuration
779
        constexpr blockbinary& maxneg() noexcept {
1,407✔
780
                if constexpr (NumberType == BinaryNumberType::Signed) {
781
                        // maxneg = 10000....0000
782
                        clear();
1,407✔
783
                        setbit(nbits - 1);
1,407✔
784
                }
785
                else {
786
                        // maxneg = 00000....00000
787
                        clear();
788
                }
789
                                
790
                return *this;
1,407✔
791
        }
792

793
        // selectors
794
        constexpr bool sign() const noexcept { return _block[MSU] & SIGN_BIT_MASK; }
481,436,891✔
795
        constexpr bool ispos() const noexcept { return !sign(); }
44,487,121✔
796
        constexpr bool isneg() const noexcept { return sign(); }
161,315,875✔
797
        constexpr bool iszero() const noexcept {
667,791,358✔
798
                for (unsigned i = 0; i < nrBlocks; ++i) if (_block[i] != 0) return false;
725,803,390✔
799
                return true;
45,468,182✔
800
        }
801
        constexpr bool isodd() const noexcept { return _block[0] & 0x1;        }
802
        constexpr bool iseven() const noexcept { return !isodd(); }
803

804
        constexpr bool all() const noexcept {
502,189,331✔
805
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != ALL_ONES) return false;
71,838✔
806
                if (_block[MSU] != MSU_MASK) return false;
502,119,125✔
807
                return true;
33,977,478✔
808
        }
809
        constexpr bool any() const noexcept {
209,680✔
810
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != 0) return true;
74,016✔
811
                if (_block[MSU] & MSU_MASK) return true;
136,240✔
812
                return false;
10✔
813
        }
814
        constexpr bool anyAfter(unsigned bitIndex) const noexcept {  // TODO: optimize for limbs
7,889,749✔
815
                unsigned limit = (bitIndex < nbits) ? bitIndex : nbits;
7,889,749✔
816
                for (unsigned i = 0; i < limit; ++i) if (test(i)) return true;
12,067,628✔
817
                return false;
3,986,035✔
818
        }
819

820
        constexpr bool none() const noexcept {
787,155,626✔
821
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != 0) return false;
137,320,758✔
822
                if (_block[MSU] & MSU_MASK) return false;
752,367,057✔
823
                return true;
15,695,117✔
824
        }
825
        constexpr unsigned count() const noexcept { // TODO: optimize for limbs
69,904✔
826
                unsigned nrOnes = 0;
69,904✔
827
                for (unsigned i = 0; i < nbits; ++i) {
1,169,744✔
828
                        if (test(i)) ++nrOnes;
1,099,840✔
829
                }
830
                return nrOnes;
69,904✔
831
        }
832
        constexpr bool test(unsigned bitIndex) const noexcept { return at(bitIndex); }
2,107,346,561✔
833
        constexpr bool at(unsigned bitIndex) const noexcept {
2,147,483,647✔
834
                if (bitIndex >= nbits) return false; // fail silently as no-op
2,147,483,647✔
835
                unsigned blockIndex = bitIndex / bitsInBlock;
2,147,483,647✔
836
                bt limb = _block[blockIndex];
2,147,483,647✔
837
                bt mask = bt(1ull << (bitIndex % bitsInBlock));
2,147,483,647✔
838
                return (limb & mask);
2,147,483,647✔
839
        }
840
        constexpr uint8_t nibble(unsigned n) const noexcept {
360✔
841
                uint8_t retval{ 0 };
360✔
842
                if (n < (1 + ((nbits - 1) >> 2))) {
360✔
843
                        bt word = _block[(n * 4) / bitsInBlock];
360✔
844
                        unsigned nibbleIndexInWord = n % (bitsInBlock >> 2);
360✔
845
                        bt mask = static_cast<bt>(bt(0x0F) << (nibbleIndexInWord*4));
360✔
846
                        bt nibblebits = static_cast<bt>(mask & word);
360✔
847
                        retval = static_cast<uint8_t>(nibblebits >> (nibbleIndexInWord*4));
360✔
848
                }
849
                else { // nop when nibble index out of bounds
850
                        retval = 0;
×
851
                }
852
                return retval;
360✔
853
        }
854
        constexpr bt block(unsigned b) const noexcept {
613,342,035✔
855
                if (b < nrBlocks) return _block[b]; 
613,342,035✔
856
                return bt(0); // return 0 when block index out of bounds
×
857
        }
858

859
        // copy a value over from one blockbinary to this blockbinary
860
        // blockbinary is a 2's complement encoding, so we sign-extend by default
861
        template<unsigned srcbits>
862
        constexpr blockbinary<nbits, bt, NumberType>& assign(const blockbinary<srcbits, bt, NumberType>& rhs) {
162,193,630✔
863
                clear();
162,193,630✔
864
                // since bt is the same, we can simply copy the blocks in
865
                unsigned minNrBlocks = (this->nrBlocks < rhs.nrBlocks) ? this->nrBlocks : rhs.nrBlocks;
162,193,630✔
866
                for (unsigned i = 0; i < minNrBlocks; ++i) {
397,443,477✔
867
                        _block[i] = rhs.block(i);
235,249,847✔
868
                }
869
                if constexpr (nbits > srcbits) { // check if we need to sign extend
870
                        if (rhs.sign()) {
105,481,422✔
871
                                for (unsigned i = srcbits; i < nbits; ++i) { // TODO: replace bit-oriented sequence with block
134,213,074✔
872
                                        setbit(i);
73,189,055✔
873
                                }
874
                        }
875
                }
876
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
877
                _block[MSU] &= MSU_MASK;
162,193,630✔
878
                return *this;
162,193,630✔
879
        }
880

881
        // copy a value over from one blockbinary to this without sign-extending the value
882
        // blockbinary is a 2's complement encoding, so we sign-extend by default
883
        // for fraction/significent encodings, we need to turn off sign-extending.
884
        template<unsigned srcbits>
885
        constexpr blockbinary<nbits, bt, NumberType>& assignWithoutSignExtend(const blockbinary<srcbits, bt, NumberType>& rhs) {
33,284✔
886
                clear();
33,284✔
887
                // since bt is the same, we can simply copy the blocks in
888
                unsigned minNrBlocks = (this->nrBlocks < rhs.nrBlocks) ? this->nrBlocks : rhs.nrBlocks;
33,284✔
889
                for (unsigned i = 0; i < minNrBlocks; ++i) {
66,568✔
890
                        _block[i] = rhs.block(i);
33,284✔
891
                }
892
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
893
                _block[MSU] &= MSU_MASK;
33,284✔
894
                return *this;
33,284✔
895
        }
896

897
        // return the position of the most significant bit, -1 if v == 0
898
        constexpr int msb() const noexcept {
1,342,032✔
899
                for (int i = int(MSU); i >= 0; --i) {
2,150,649✔
900
                        if (_block[i] != 0) {
2,150,649✔
901
                                bt mask = (bt(1u) << (bitsInBlock-1));
1,342,032✔
902
                                for (int j = bitsInBlock - 1; j >= 0; --j) {
5,973,440✔
903
                                        if (_block[i] & mask) {
5,973,440✔
904
                                                return i * static_cast<int>(bitsInBlock) + j;
1,342,032✔
905
                                        }
906
                                        mask >>= 1;
4,631,408✔
907
                                }
908
                        }
909
                }
910
                return -1; // no significant bit found, all bits are zero
×
911
        }
912
        // conversion to native types
913
        constexpr int64_t to_sll() const {
138,755,956✔
914
                constexpr unsigned sizeoflonglong = 8 * sizeof(long long);
138,755,956✔
915
                int64_t ll{ 0 };
138,755,956✔
916
                int64_t mask{ 1 };
138,755,956✔
917
                unsigned upper = (nbits < sizeoflonglong ? nbits : sizeoflonglong);
138,755,956✔
918
                for (unsigned i = 0; i < upper; ++i) {
1,775,155,439✔
919
                        ll |= at(i) ? mask : 0;
1,636,399,483✔
920
                        mask <<= 1;
1,636,399,483✔
921
                }
922
                if (sign() && upper < sizeoflonglong) { // sign extend
138,755,956✔
923
                        for (unsigned i = upper; i < sizeoflonglong; ++i) {
2,147,483,647✔
924
                                ll |= mask;
2,147,483,647✔
925
                                mask <<= 1;
2,147,483,647✔
926
                        }
927
                }
928
                return ll;
138,755,956✔
929
        }
930
        constexpr uint64_t to_ull() const {
127,925,991✔
931
                uint64_t ull{ 0 };
127,925,991✔
932
                uint64_t mask{ 1 };
127,925,991✔
933
                uint32_t msb = nbits < 64 ? nbits : 64;
127,925,991✔
934
                for (uint32_t i = 0; i < msb; ++i) {
736,440,777✔
935
                        ull |= at(i) ? mask : 0;
608,514,786✔
936
                        mask <<= 1;
608,514,786✔
937
                }
938
                return ull;
127,925,991✔
939
        }
940
        template<typename Real,
941
                typename = typename std::enable_if< std::is_floating_point<Real>::value, Real >::type>
942
        Real to_native() const {
1,186,262✔
943
                blockbinary tmp(*this);
1,186,262✔
944
                if (isneg()) tmp.twosComplement();
1,186,262✔
945
                Real v{ 0.0 }, base{ 1.0 };
1,186,262✔
946
                for (unsigned i = 0; i < nbits; ++i) {
20,154,726✔
947
                        if (tmp.test(i)) v += base;
18,968,464✔
948
                        base *= 2.0;
18,968,464✔
949
                }
950
                return (isneg() ? -v : v);
1,186,262✔
951
        }
952
        // determine the rounding mode: result needs to be rounded up if true
953
        constexpr bool roundingMode(unsigned targetLsb) const {
129,117✔
954
                bool lsb = at(targetLsb);
129,117✔
955
                bool guard = (targetLsb == 0 ? false : at(targetLsb - 1));
129,117✔
956
                bool round = (targetLsb > 1 ? at(targetLsb - 2) : false);
129,117✔
957
                bool sticky =(targetLsb < 3 ? false : any(targetLsb - 3));
129,117✔
958
                bool tie = guard && !round && !sticky;
129,117✔
959
                return (lsb && tie) || (guard && !tie);
129,117✔
960
        }
961
        constexpr bool any(unsigned msb) const {
153,480✔
962
                msb = (msb > nbits - 1 ? nbits - 1 : msb);
153,480✔
963
                unsigned topBlock = msb / bitsInBlock;
153,480✔
964
                bt mask = bt(ALL_ONES >> (bitsInBlock - 1 - (msb % bitsInBlock)));
153,480✔
965
                for (unsigned i = 0; i < topBlock; ++i) {
153,857✔
966
                        if (_block[i] > 0) return true;
16,806✔
967
                }
968
                // process the partial block
969
                if (_block[topBlock] & mask) return true;
137,051✔
970
                return false;
53,813✔
971
        }
972

973
protected:
974
        // HELPER methods
975
        // none
976

977
private:
978
        bt _block[nrBlocks];
979

980
        //////////////////////////////////////////////////////////////////////////////
981
        // friend functions
982

983
        // integer - integer logic comparisons
984
        template<unsigned N, typename B, BinaryNumberType T>
985
        friend constexpr bool operator==(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs);
986
        template<unsigned N, typename B, BinaryNumberType T>
987
        friend constexpr bool operator!=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs);
988
        // the other logic operators are defined in terms of arithmetic terms
989

990
        template<unsigned N, typename B, BinaryNumberType T>
991
        friend std::ostream& operator<<(std::ostream& ostr, const blockbinary<N, B, T>& v);
992
};
993

994
// Generate a type tag for blockbinary
995
template<unsigned N, typename B, BinaryNumberType T>
996
std::string type_tag(const blockbinary<N, B, T>& = {}) {
×
997
        std::stringstream str;
×
998
        str << "blockbinary<"
999
                << std::setw(4) << N << ", "
×
1000
                << typeid(B).name() << ", "
1001
                << typeid(T).name() << '>';
×
1002
        return str.str();
×
1003
}
×
1004

1005
//////////////////////////////////////////////////////////////////////////////////
1006
// logic operators
1007

1008
template<unsigned N, typename B, BinaryNumberType T>
1009
constexpr bool operator==(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
231,855,907✔
1010
        for (unsigned i = 0; i < lhs.nrBlocks; ++i) {
490,644,631✔
1011
                if (lhs._block[i] != rhs._block[i]) {
309,012,182✔
1012
                        return false;
50,223,458✔
1013
                }
1014
        }
1015
        return true;
181,632,449✔
1016
}
1017
template<unsigned N, typename B, BinaryNumberType T>
1018
constexpr bool operator!=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
174,678,892✔
1019
        return !operator==(lhs, rhs);
174,678,892✔
1020
}
1021
template<unsigned N, typename B, BinaryNumberType T>
1022
constexpr bool operator<(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
30,910,944✔
1023
        if (lhs.ispos() && rhs.isneg()) return false; // need to filter out possible overflow conditions
30,910,944✔
1024
        if (lhs.isneg() && rhs.ispos()) return true;  // need to filter out possible underflow conditions
24,329,150✔
1025
        if (lhs == rhs) return false; // so the maxneg logic works
17,542,837✔
1026
        blockbinary<N, B, T> mneg; maxneg<N, B>(mneg);
17,274,600✔
1027
        if (rhs == mneg) return false; // special case: nothing is smaller than maximum negative
17,274,600✔
1028
        blockbinary<N, B, T> diff = lhs - rhs;
17,266,397✔
1029
        return diff.isneg();
17,266,397✔
1030
}
1031
template<unsigned N, typename B, BinaryNumberType T>
1032
constexpr bool operator<=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
13,710,373✔
1033
        return (lhs < rhs || lhs == rhs);
13,710,373✔
1034
}
1035
template<unsigned N, typename B, BinaryNumberType T>
1036
constexpr bool operator>(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
223,655✔
1037
        return !(lhs <= rhs);
223,655✔
1038
}
1039
template<unsigned N, typename B, BinaryNumberType T>
1040
constexpr bool operator>=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
12,087,289✔
1041
        return !(lhs < rhs);
12,087,289✔
1042
}
1043
///////////////////////////////////////////////////////////////////////////////
1044
// binary operators
1045

1046
template<unsigned N, typename B, BinaryNumberType T>
1047
constexpr blockbinary<N, B, T> operator+(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
100,800,007✔
1048
        blockbinary<N, B, T> c(a);
100,800,007✔
1049
        return c += b;
100,800,007✔
1050
}
1051
template<unsigned N, typename B, BinaryNumberType T >
1052
constexpr blockbinary<N, B, T> operator-(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
17,466,749✔
1053
        blockbinary<N, B, T> c(a);
17,466,749✔
1054
        return c -= b;
34,594,220✔
1055
}
1056
template<unsigned N, typename B, BinaryNumberType T>
1057
constexpr inline blockbinary<N, B, T> operator*(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
72,423,754✔
1058
        blockbinary<N, B, T> c(a);
72,423,754✔
1059
        return c *= b;
97,396,092✔
1060
}
1061
template<unsigned N, typename B, BinaryNumberType T>
1062
constexpr inline blockbinary<N, B, T> operator/(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
67,074✔
1063
        blockbinary<N, B, T> c(a);
67,074✔
1064
        return c /= b;
68,447✔
1065
}
1066
template<unsigned N, typename B, BinaryNumberType T>
1067
constexpr blockbinary<N, B, T> operator%(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
1,598,742✔
1068
        blockbinary<N, B, T> c(a);
1,598,742✔
1069
        return c %= b;
2,933,227✔
1070
}
1071

1072
template<unsigned N, typename B, BinaryNumberType T>
1073
blockbinary<N, B, T> operator<<(const blockbinary<N, B, T>& a, long b) {
752✔
1074
        blockbinary<N, B, T> c(a);
752✔
1075
        return c <<= b;
1,504✔
1076
}
1077
template<unsigned N, typename B, BinaryNumberType T>
1078
blockbinary<N, B, T> operator>>(const blockbinary<N, B, T>& a, long b) {
168✔
1079
        blockbinary<N, B, T> c(a);
168✔
1080
        return c >>= b;
336✔
1081
}
1082

1083
// divide a by b and return both quotient and remainder
1084
template<unsigned N, typename B, BinaryNumberType T>
1085
constexpr quorem<N, B, T> longdivision(const blockbinary<N, B, T>& dividend, const blockbinary<N, B, T>& divisor) {
1,341,250✔
1086
        static_assert(T == BinaryNumberType::Signed, "longdivision requires signed blockbinary types");
1087
        using BlockBinary = blockbinary<N + 1, B, T>;
1088
        quorem<N, B, T> result = { 0, 0, 0 };
1,341,250✔
1089
        if (divisor.iszero()) {
1,341,250✔
1090
                result.exceptionId = 1; // division by zero
1,799✔
1091
                return result;
1,799✔
1092
        }
1093
        // generate the absolute values to do long division 
1094
        // 2's complement special case -max requires an signed int that is 1 bit bigger to represent abs()
1095
        bool a_sign = dividend.sign();
1,339,451✔
1096
        bool b_sign = divisor.sign();
1,339,451✔
1097
        bool result_negative = (a_sign ^ b_sign);
1,339,451✔
1098
        // normalize both arguments to positive, which requires expansion by 1-bit to deal with maxneg
1099
        BlockBinary a(dividend);
1,339,451✔
1100
        BlockBinary b(divisor);
1,339,451✔
1101
        if (a_sign) a.twosComplement();
1,339,451✔
1102
        if (b_sign) b.twosComplement();
1,339,451✔
1103

1104
        if (a < b) { // optimization for integer numbers
1,339,451✔
1105
                result.rem = dividend; // a % b = a when a / b = 0
668,435✔
1106
                return result;         // a / b = 0 when b > a
668,435✔
1107
        }
1108
        // initialize the long division
1109
        BlockBinary accumulator = a;
671,016✔
1110
        // prepare the subtractand
1111
        BlockBinary subtractand = b;
671,016✔
1112
        int msb_b = b.msb();
671,016✔
1113
        int msb_a = a.msb();
671,016✔
1114
        int shift = msb_a - msb_b;
671,016✔
1115
        subtractand <<= shift;
671,016✔
1116
        // long division
1117
        for (int i = shift; i >= 0; --i) {
2,289,020✔
1118
                if (subtractand <= accumulator) {
1,618,004✔
1119
                        accumulator -= subtractand;
963,461✔
1120
                        result.quo.setbit(static_cast<unsigned>(i));
963,461✔
1121
                }
1122
                else {
1123
                        result.quo.setbit(static_cast<unsigned>(i), false);
654,543✔
1124
                }
1125
                subtractand >>= 1;
1,618,004✔
1126
        }
1127
        if (result_negative) {  // take 2's complement
671,016✔
1128
                result.quo.flip();
333,196✔
1129
                result.quo += 1;
333,196✔
1130
        }
1131
        if (a_sign) {
671,016✔
1132
                result.rem = -accumulator;
334,077✔
1133
        }
1134
        else {
1135
                result.rem = accumulator;
336,939✔
1136
        }
1137
        return result;
671,016✔
1138
}
1139

1140
///////////////////////////////////////////////////////////////////////////////
1141
// specialty binary operators
1142

1143
// unrounded addition, returns a blockbinary that is of size nbits+1
1144
template<unsigned N, typename B, BinaryNumberType T>
1145
constexpr blockbinary<N + 1, B, T> uradd(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
3,400,060✔
1146
        blockbinary<N + 1, B, T> result(a);
3,400,060✔
1147
        return result += blockbinary<N + 1, B, T>(b);
3,400,060✔
1148
}
1149

1150
// unrounded subtraction, returns a blockbinary that is of size nbits+1
1151
template<unsigned N, typename B, BinaryNumberType T>
1152
constexpr blockbinary<N + 1, B, T> ursub(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
8,591,552✔
1153
        blockbinary<N + 1, B, T> result(a);
8,591,552✔
1154
        return result -= blockbinary<N + 1, B, T>(b);
8,591,552✔
1155
}
1156

1157
#define TRACE_URMUL 0
1158
// unrounded multiplication, returns a blockbinary that is of size 2*nbits
1159
// using brute-force sign-extending of operands to yield correct sign-extended result for 2*nbits 2's complement.
1160
template<unsigned N, typename B, BinaryNumberType T>
1161
constexpr blockbinary<2*N, B, T> urmul(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
99✔
1162
        using BlockBinary = blockbinary<2 * N, B, T>;
1163
        BlockBinary result(0);
99✔
1164
        if (a.iszero() || b.iszero()) return result;
99✔
1165

1166
        // compute the result
1167
        BlockBinary signextended_a(a);
99✔
1168
        BlockBinary multiplicant(b);
99✔
1169
#if TRACE_URMUL
1170
        std::cout << "    " << to_binary(a) << " * " << to_binary(b) << std::endl;
1171
        std::cout << std::setw(3) << 0 << ' ' << to_binary(multiplicant) << ' ' << to_binary(result) << std::endl;
1172
#endif
1173
        for (unsigned i = 0; i < 2*N; ++i) {
9,627✔
1174
                if (signextended_a.at(i)) {
9,528✔
1175
                        result += multiplicant;
221✔
1176
                }
1177
                multiplicant <<= 1;
9,528✔
1178
#if TRACE_URMUL
1179
                std::cout << std::setw(3) << i << ' ' << to_binary(multiplicant) << ' ' << to_binary(result) << std::endl;
1180
#endif
1181

1182
        }
1183
#if TRACE_URMUL
1184
        std::cout << "fnl " << to_binary(result) << std::endl;
1185
#endif
1186
        //blockbinary<2 * nbits, bt> clipped(result);
1187
        // since we used operator+=, which enforces the nulling of leading bits
1188
        // we don't need to null here
1189
        return result;
99✔
1190
}
1191

1192
// unrounded multiplication, returns a blockbinary that is of size 2*nbits
1193
// using nbits modulo arithmetic with final sign
1194
template<unsigned N, typename B, BinaryNumberType T>
1195
constexpr blockbinary<2 * N, B, T> urmul2(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
139,964✔
1196
        blockbinary<2 * N, B, T> result(0);
139,964✔
1197
        if (a.iszero() || b.iszero()) return result;
139,964✔
1198

1199
        // compute the result
1200
        bool result_sign = a.sign() ^ b.sign();
137,364✔
1201
        // normalize both arguments to positive in new size
1202
        blockbinary<N + 1, B, T> a_new(a); // TODO optimize: now create a, create _a.bb, copy, destroy _a.bb_copy
137,364✔
1203
        blockbinary<N + 1, B, T> b_new(b);
137,364✔
1204
        if (a.sign()) a_new.twosComplement();
137,364✔
1205
        if (b.sign()) b_new.twosComplement();
137,364✔
1206
        blockbinary<2*N, B, T> multiplicant(b_new);
137,364✔
1207

1208
#if TRACE_URMUL
1209
        std::cout << "    " << a_new << " * " << b_new << std::endl;
1210
        std::cout << std::setw(3) << 0 << ' ' << multiplicant << ' ' << result << std::endl;
1211
#endif
1212
        for (unsigned i = 0; i < (N+1); ++i) {
1,370,003✔
1213
                if (a_new.at(i)) {
1,232,639✔
1214
                        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✔
1215
                }
1216
                multiplicant <<= 1;
1,232,639✔
1217
#if TRACE_URMUL
1218
                std::cout << std::setw(3) << i << ' ' << multiplicant << ' ' << result << std::endl;
1219
#endif
1220
        }
1221
        if (result_sign) result.twosComplement();
137,364✔
1222
#if TRACE_URMUL
1223
        std::cout << "fnl " << result << std::endl;
1224
#endif
1225
        return result;
137,364✔
1226
}
1227

1228
#define TRACE_DIV 0
1229
// unrounded division, returns a blockbinary that is of size 2*nbits
1230
template<unsigned nbits, unsigned roundingBits, typename B, BinaryNumberType T>
1231
blockbinary<2 * nbits + roundingBits, B, T> urdiv(const blockbinary<nbits, B, T>& a, const blockbinary<nbits, B, T>& b) {
1232
        if (b.iszero()) {
1233
                // division by zero
1234
                throw "urdiv divide by zero";
1235
        }
1236
        // generate the absolute values to do long division 
1237
        // 2's complement special case -max requires an signed int that is 1 bit bigger to represent abs()
1238
        bool a_sign = a.sign();
1239
        bool b_sign = b.sign();
1240
        bool result_negative = (a_sign ^ b_sign);
1241

1242
        // normalize both arguments to positive, which requires expansion by 1-bit to deal with maxneg
1243
        blockbinary<nbits + 1, B, T> a_new(a); // TODO optimize: now create a, create _a.bb, copy, destroy _a.bb_copy
1244
        blockbinary<nbits + 1, B, T> b_new(b);
1245
#if TRACE_DIV
1246
        std::cout << "a " << to_binary(a_new) << '\n';
1247
        std::cout << "b " << to_binary(b_new) << '\n';
1248
#endif
1249
        if (a_sign) a_new.twosComplement();
1250
        if (b_sign) b_new.twosComplement();
1251
#if TRACE_DIV
1252
        std::cout << "a " << to_binary(a_new) << '\n';
1253
        std::cout << "b " << to_binary(b_new) << '\n';
1254
#endif
1255

1256
        // initialize the long division
1257
        blockbinary<2 * nbits + roundingBits + 1, B, T> decimator(a_new);
1258
        blockbinary<2 * nbits + roundingBits + 1, B, T> subtractand(b_new); // prepare the subtractand
1259
        blockbinary<2 * nbits + roundingBits + 1, B, T> result;
1260

1261
        constexpr unsigned msp = nbits + roundingBits; // msp = most significant position
1262
        decimator <<= msp; // scale the decimator to the largest possible positive value
1263

1264
        int msb_b = subtractand.msb();
1265
        int msb_a = decimator.msb();
1266
        int shift = msb_a - msb_b;
1267
        subtractand <<= shift;
1268
        int offset = msb_a - static_cast<int>(msp);  // msb of the result
1269
        int scale  = shift - static_cast<int>(msp);  // scale of the result quotient
1270

1271
#if TRACE_DIV
1272
        std::cout << "  " << to_binary(decimator, true)   << " msp  : " << msp << '\n';
1273
        std::cout << "- " << to_binary(subtractand, true) << " shift: " << shift << '\n';
1274
#endif
1275
        // long division
1276
        for (int i = msb_a; i >= 0; --i) {
1277

1278
                if (subtractand <= decimator) {
1279
                        decimator -= subtractand;
1280
                        result.setbit(static_cast<unsigned>(i));
1281
                }
1282
                else {
1283
                        result.setbit(static_cast<unsigned>(i), false);
1284
                }
1285
                subtractand >>= 1;
1286

1287
#if TRACE_DIV
1288
                std::cout << "  " << to_binary(decimator, true) << "  current quotient: " << to_binary(result, true) << '\n';
1289
                std::cout << "- " << to_binary(subtractand, true) << '\n';
1290
#endif
1291
        }
1292
        result <<= (scale - offset);
1293
#if TRACE_DIV
1294
        std::cout << "  " << "scaled result: " << to_binary(result, true) << " scale : " << scale << " offset : " << offset << '\n';
1295
#endif
1296
        if (result_negative) result.twosComplement();
1297
        return result;
1298
}
1299

1300
//////////////////////////////////////////////////////////////////////////////
1301
// conversions to string representations
1302

1303
// create a binary representation of the storage
1304
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1305
std::string to_binary(const blockbinary<nbits, BlockType, NumberType>& number, bool nibbleMarker = false) {
1,600✔
1306
        std::stringstream s;
1,600✔
1307
        s << "0b";
1,600✔
1308
        for (unsigned i = 0; i < nbits; ++i) {
43,920✔
1309
                unsigned bitIndex = nbits - 1ull - i;
42,320✔
1310
                s << (number.at(bitIndex) ? '1' : '0');
42,320✔
1311
                if (bitIndex > 0 && (bitIndex % 4) == 0 && nibbleMarker) s << '\'';
42,320✔
1312
        }
1313
        return s.str();
3,200✔
1314
}
1,600✔
1315

1316
// local helper to display the contents of a byte array
1317
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1318
std::string to_hex(const blockbinary<nbits, BlockType, NumberType>& number, bool nibbleMarker = true) {
53✔
1319
        static constexpr unsigned bitsInByte = 8;
1320
        static constexpr unsigned bitsInBlock = sizeof(BlockType) * bitsInByte;
1321
        char hexChar[16] = {
53✔
1322
                '0', '1', '2', '3', '4', '5', '6', '7',
1323
                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
1324
        };
1325
        std::stringstream ss;
53✔
1326
        ss << "0x" << std::hex;
53✔
1327
        int nrNibbles = int(1 + ((nbits - 1) >> 2));
53✔
1328
        for (int n = nrNibbles - 1; n >= 0; --n) {
413✔
1329
                uint8_t nibble = number.nibble(static_cast<unsigned>(n));
360✔
1330
                ss << hexChar[nibble];
360✔
1331
                if (nibbleMarker && n > 0 && ((n * 4ll) % bitsInBlock) == 0) ss << '\'';
360✔
1332
        }
1333
        return ss.str();
106✔
1334
}
53✔
1335

1336
// decimal string conversion
1337
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1338
std::string to_decimal(const blockbinary<nbits, BlockType, NumberType>& number) {
1,837✔
1339
        if (number.iszero()) return "0";
2,125✔
1340

1341
        std::string result;
1,693✔
1342
        blockbinary<nbits, BlockType, NumberType> dividend(number);
1,693✔
1343
        bool isNegative = false;
1,693✔
1344

1345
        // Handle negative numbers for signed types
1346
        if constexpr (NumberType == BinaryNumberType::Signed) {
1347
                if (dividend.isneg()) {
1,691✔
1348
                        isNegative = true;
475✔
1349
                        dividend.twosComplement(); // Convert to positive
475✔
1350
                }
1351
        }
1352

1353
        // Repeatedly divide by 10 and collect remainders
1354
        blockbinary<nbits, BlockType, NumberType> ten(10);
1,693✔
1355
        while (!dividend.iszero()) {
5,083✔
1356
                if constexpr (nbits <= 64) {
1357
                        // For smaller sizes, use native division to avoid complexity
1358
                        uint64_t temp = dividend.to_ull();
2,840✔
1359
                        uint64_t remainder = temp % 10;
2,840✔
1360
                        result = char('0' + remainder) + result;
2,840✔
1361
                        dividend = temp / 10;
2,840✔
1362
                } else {
1363
                        // For larger sizes, use blockbinary division operators
1364
                        blockbinary<nbits, BlockType, NumberType> remainder = dividend % ten;
550✔
1365
                        uint64_t digit = remainder.to_ull();
550✔
1366
                        result = char('0' + digit) + result;
550✔
1367
                        dividend /= ten;
550✔
1368
                }
1369
        }
1370

1371
        if (isNegative) {
1,693✔
1372
                result = "-" + result;
475✔
1373
        }
1374

1375
        return result;
1,693✔
1376
}
1,693✔
1377

1378
// ostream operator
1379
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1380
std::ostream& operator<<(std::ostream& ostr, const blockbinary<nbits, BlockType, NumberType>& number) {
1,677✔
1381
        ostr << to_decimal(number);
1,677✔
1382
        return ostr;
1,677✔
1383
}
1384

1385

1386
}} // 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