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

stillwater-sc / universal / 24937806439

25 Apr 2026 06:35PM UTC coverage: 84.326% (-0.007%) from 84.333%
24937806439

Pull #760

github

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

61 of 81 new or added lines in 3 files covered. (75.31%)

3 existing lines in 1 file now uncovered.

44983 of 53344 relevant lines covered (84.33%)

6356992.83 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,741✔
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,446✔
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,391,991✔
166
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
167
                        _block[MSU] &= MSU_MASK;
1,391,991✔
168
                }
169
                return *this;
10,208,446✔
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,535✔
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,882✔
212
                blockbinary increment;
213
                increment.setbits(0x1);
2,241,882✔
214
                *this += increment;
2,241,882✔
215
                return *this;
2,241,882✔
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
        // arithmetic operators
228
        constexpr blockbinary& operator+=(const blockbinary& rhs) {
229,019,259✔
229
                if constexpr (nrBlocks == 1) {
230
                        _block[0] = static_cast<bt>(_block[0] + rhs.block(0));
115,897,833✔
231
                        // null any leading bits that fall outside of nbits
232
                        _block[MSU] = static_cast<bt>(MSU_MASK & _block[MSU]);
115,897,833✔
233
                }
234
                else if constexpr (bitsInBlock == 64) {
235
                        // uint64_t limbs: addcarry uses platform intrinsics (not constexpr on MSVC).
236
                        // In a constant-evaluated context, fall back to portable carry detection.
237
                        blockbinary sum;
238
                        if (std::is_constant_evaluated()) {
4,584✔
239
                                uint64_t carry = 0;
×
240
                                for (unsigned i = 0; i < nrBlocks; ++i) {
×
241
                                        uint64_t a = _block[i];
×
242
                                        uint64_t b = rhs._block[i];
×
243
                                        uint64_t s1 = a + carry;
×
244
                                        uint64_t c1 = (s1 < a) ? 1ull : 0ull;
×
245
                                        uint64_t r  = s1 + b;
×
246
                                        uint64_t c2 = (r < s1) ? 1ull : 0ull;
×
247
                                        sum._block[i] = r;
×
248
                                        carry = c1 + c2;
×
249
                                }
250
                        }
251
                        else {
252
                                uint64_t carry = 0;
4,584✔
253
                                for (unsigned i = 0; i < nrBlocks; ++i) {
34,845✔
254
                                        sum._block[i] = addcarry(_block[i], rhs._block[i], carry, carry);
30,261✔
255
                                }
256
                        }
257
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
258
                        sum._block[MSU] = static_cast<bt>(MSU_MASK & sum._block[MSU]);
4,584✔
259
                        *this = sum;
4,584✔
260
                }
261
                else {
262
                        // Multi-limb path for bt = uint8_t/uint16_t/uint32_t.
263
                        // Index-based loop (constexpr-friendly).
264
                        blockbinary sum;
265
                        std::uint64_t carry = 0;
113,116,842✔
266
                        for (unsigned i = 0; i < nrBlocks; ++i) {
367,087,976✔
267
                                carry += static_cast<std::uint64_t>(_block[i]) + static_cast<std::uint64_t>(rhs._block[i]);
253,971,134✔
268
                                sum._block[i] = static_cast<bt>(carry);
253,971,134✔
269
                                carry >>= bitsInBlock;
253,971,134✔
270
                        }
271
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
272
                        sum._block[MSU] = static_cast<bt>(MSU_MASK & sum._block[MSU]);
113,116,842✔
273
                        *this = sum;
113,116,842✔
274
                }
275
                return *this;
229,019,259✔
276
        }
277
        constexpr blockbinary& operator-=(const blockbinary& rhs) {
27,144,769✔
278
                return operator+=(sw::universal::twosComplement(rhs));
27,144,769✔
279
        }
280
#define BLOCKBINARY_FAST_MUL
281
#ifdef BLOCKBINARY_FAST_MUL
282
        constexpr blockbinary& operator*=(const blockbinary& rhs) {
72,451,320✔
283
                if constexpr (NumberType == BinaryNumberType::Signed) {
284
                        if constexpr (nrBlocks == 1) {
285
                                _block[0] = static_cast<bt>(static_cast<std::uint64_t>(_block[0]) * static_cast<std::uint64_t>(rhs.block(0)));
47,451,584✔
286
                        }
287
                        else if constexpr (bitsInBlock == 64) {
288
                                // uint64_t limbs: mul128/addcarry use platform intrinsics that are not
289
                                // constexpr on MSVC. In a constant-evaluated context, fall back to a
290
                                // portable __uint128_t-based carry-propagation loop (gcc/clang only).
291
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
614✔
292
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
614✔
293
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
614✔
294
                                if (base.isneg()) {
614✔
295
                                        base.twosComplement();
1✔
296
                                }
297
                                if (multiplicant.isneg()) {
614✔
298
                                        multiplicant.twosComplement();
×
299
                                }
300
                                clear();
614✔
301
                                if (std::is_constant_evaluated()) {
614✔
302
#if defined(__SIZEOF_INT128__)
NEW
303
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
×
NEW
304
                                                uint128_t carry = 0;
×
NEW
305
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
×
NEW
306
                                                        if (i + j < nrBlocks) {
×
NEW
307
                                                                uint128_t product = static_cast<uint128_t>(base.block(i)) * multiplicant.block(j);
×
NEW
308
                                                                uint128_t sum = product + _block[i + j] + carry;
×
NEW
309
                                                                _block[i + j] = static_cast<bt>(sum);
×
NEW
310
                                                                carry = sum >> 64;
×
311
                                                        }
312
                                                }
313
                                        }
314
#else
315
                                        // Constexpr eval of uint64-limb mul without __int128 is unsupported
316
                                        // (MSVC). Forcing a throw in the constant-evaluated branch turns this
317
                                        // into a compile error rather than a silent zero-result. The branch
318
                                        // is unreachable at runtime because is_constant_evaluated() is false
319
                                        // there; the runtime intrinsic path below handles all execution.
320
                                        // Users on MSVC needing compile-time eval should use uint32_t limbs.
321
                                        throw "blockbinary<N, uint64_t> constexpr multiply requires __int128 support (gcc/clang); on MSVC use uint32_t limbs for compile-time evaluation";
322
#endif
323
                                }
324
                                else {
325
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
2,618✔
326
                                                uint64_t carry = 0;
2,004✔
327
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
10,844✔
328
                                                        if (i + j < nrBlocks) {
8,840✔
329
                                                                uint64_t lo, hi;
330
                                                                mul128(base.block(i), multiplicant.block(j), lo, hi);
5,422✔
331
                                                                // accumulate: _block[i+j] += lo + carry
332
                                                                // use two separate additions to avoid passing multi-bit carry
333
                                                                // to addcarry (MSVC _addcarry_u64 only accepts 0/1 carry_in)
334
                                                                uint64_t c1 = 0;
5,422✔
335
                                                                uint64_t sum = addcarry(_block[i + j], lo, 0, c1);
5,422✔
336
                                                                uint64_t c2 = 0;
5,422✔
337
                                                                sum = addcarry(sum, carry, 0, c2);
5,422✔
338
                                                                _block[i + j] = sum;
5,422✔
339
                                                                carry = hi + c1 + c2;
5,422✔
340
                                                        }
341
                                                }
342
                                        }
343
                                }
344
                                if (resultIsNeg) twosComplement();
614✔
345
                        }
346
                        else {
347
                                // is there a better way than upconverting to deal with maxneg in a 2's complement encoding?
348
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
24,999,122✔
349
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
24,999,122✔
350
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
24,999,122✔
351
                                if (base.isneg()) {
24,999,122✔
352
                                        base.twosComplement();
12,484,610✔
353
                                }
354
                                if (multiplicant.isneg()) {
24,999,122✔
355
                                        multiplicant.twosComplement();
12,484,609✔
356
                                }
357
                                clear();
24,999,122✔
358
                                for (unsigned i = 0; i < static_cast<unsigned>(nrBlocks); ++i) {
76,357,496✔
359
                                        std::uint64_t segment(0);
51,358,374✔
360
                                        for (unsigned j = 0; j < static_cast<unsigned>(nrBlocks); ++j) {
158,707,406✔
361
                                                segment += static_cast<std::uint64_t>(base.block(i)) * static_cast<std::uint64_t>(multiplicant.block(j));
107,349,032✔
362

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

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

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

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

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

638
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
639
                _block[MSU] &= MSU_MASK;
5,867,972✔
640
                return *this;
5,867,972✔
641
        }
642

643

644
        ///////////////////////////////////////////////////////////////////
645
        ///                  modifiers
646

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

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

787
        // selectors
788
        constexpr bool sign() const noexcept { return _block[MSU] & SIGN_BIT_MASK; }
481,436,891✔
789
        constexpr bool ispos() const noexcept { return !sign(); }
44,487,121✔
790
        constexpr bool isneg() const noexcept { return sign(); }
161,315,875✔
791
        constexpr bool iszero() const noexcept {
651,371,720✔
792
                for (unsigned i = 0; i < nrBlocks; ++i) if (_block[i] != 0) return false;
709,383,674✔
793
                return true;
45,468,127✔
794
        }
795
        constexpr bool isodd() const noexcept { return _block[0] & 0x1;        }
796
        constexpr bool iseven() const noexcept { return !isodd(); }
797

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

814
        constexpr bool none() const noexcept {
746,297,961✔
815
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != 0) return false;
137,320,710✔
816
                if (_block[MSU] & MSU_MASK) return false;
711,509,344✔
817
                return true;
15,997,126✔
818
        }
819
        constexpr unsigned count() const noexcept { // TODO: optimize for limbs
69,904✔
820
                unsigned nrOnes = 0;
69,904✔
821
                for (unsigned i = 0; i < nbits; ++i) {
1,169,744✔
822
                        if (test(i)) ++nrOnes;
1,099,840✔
823
                }
824
                return nrOnes;
69,904✔
825
        }
826
        constexpr bool test(unsigned bitIndex) const noexcept { return at(bitIndex); }
1,902,990,655✔
827
        constexpr bool at(unsigned bitIndex) const noexcept {
2,147,483,647✔
828
                if (bitIndex >= nbits) return false; // fail silently as no-op
2,147,483,647✔
829
                unsigned blockIndex = bitIndex / bitsInBlock;
2,147,483,647✔
830
                bt limb = _block[blockIndex];
2,147,483,647✔
831
                bt mask = bt(1ull << (bitIndex % bitsInBlock));
2,147,483,647✔
832
                return (limb & mask);
2,147,483,647✔
833
        }
834
        constexpr uint8_t nibble(unsigned n) const noexcept {
360✔
835
                uint8_t retval{ 0 };
360✔
836
                if (n < (1 + ((nbits - 1) >> 2))) {
360✔
837
                        bt word = _block[(n * 4) / bitsInBlock];
360✔
838
                        unsigned nibbleIndexInWord = n % (bitsInBlock >> 2);
360✔
839
                        bt mask = static_cast<bt>(bt(0x0F) << (nibbleIndexInWord*4));
360✔
840
                        bt nibblebits = static_cast<bt>(mask & word);
360✔
841
                        retval = static_cast<uint8_t>(nibblebits >> (nibbleIndexInWord*4));
360✔
842
                }
843
                else { // nop when nibble index out of bounds
844
                        retval = 0;
×
845
                }
846
                return retval;
360✔
847
        }
848
        constexpr bt block(unsigned b) const noexcept {
613,342,036✔
849
                if (b < nrBlocks) return _block[b]; 
613,342,036✔
850
                return bt(0); // return 0 when block index out of bounds
×
851
        }
852

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

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

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

969
protected:
970
        // HELPER methods
971
        // none
972

973
private:
974
        bt _block[nrBlocks];
975

976
        //////////////////////////////////////////////////////////////////////////////
977
        // friend functions
978

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

986
        template<unsigned N, typename B, BinaryNumberType T>
987
        friend std::ostream& operator<<(std::ostream& ostr, const blockbinary<N, B, T>& v);
988
};
989

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

1001
//////////////////////////////////////////////////////////////////////////////////
1002
// logic operators
1003

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

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

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

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

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

1136
///////////////////////////////////////////////////////////////////////////////
1137
// specialty binary operators
1138

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

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

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

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

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

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

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

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

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

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

1252
        // initialize the long division
1253
        blockbinary<2 * nbits + roundingBits + 1, B, T> decimator(a_new);
1254
        blockbinary<2 * nbits + roundingBits + 1, B, T> subtractand(b_new); // prepare the subtractand
1255
        blockbinary<2 * nbits + roundingBits + 1, B, T> result;
1256

1257
        constexpr unsigned msp = nbits + roundingBits; // msp = most significant position
1258
        decimator <<= msp; // scale the decimator to the largest possible positive value
1259

1260
        int msb_b = subtractand.msb();
1261
        int msb_a = decimator.msb();
1262
        int shift = msb_a - msb_b;
1263
        subtractand <<= shift;
1264
        int offset = msb_a - static_cast<int>(msp);  // msb of the result
1265
        int scale  = shift - static_cast<int>(msp);  // scale of the result quotient
1266

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

1274
                if (subtractand <= decimator) {
1275
                        decimator -= subtractand;
1276
                        result.setbit(static_cast<unsigned>(i));
1277
                }
1278
                else {
1279
                        result.setbit(static_cast<unsigned>(i), false);
1280
                }
1281
                subtractand >>= 1;
1282

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

1296
//////////////////////////////////////////////////////////////////////////////
1297
// conversions to string representations
1298

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

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

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

1337
        std::string result;
1,693✔
1338
        blockbinary<nbits, BlockType, NumberType> dividend(number);
1,693✔
1339
        bool isNegative = false;
1,693✔
1340

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

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

1367
        if (isNegative) {
1,693✔
1368
                result = "-" + result;
475✔
1369
        }
1370

1371
        return result;
1,693✔
1372
}
1,693✔
1373

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

1381

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