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

stillwater-sc / universal / 30672136635

31 Jul 2026 11:10PM UTC coverage: 85.23% (-0.02%) from 85.249%
30672136635

push

github

web-flow
fix(posit): clear -Wsign-conversion in posit_impl convert_ (#1281) (#1286)

Sub-issue of Epic #1279. Four sites in the posit encode/rounding path
(convert_), all value-preserving:

- `exponent = e % (1ull << es)`: the signed scale `e` was already promoted
  to unsigned long long by the modulo (the other operand is 1ull); make it
  explicit. This is correct for a power-of-two modulus -- 2^64 mod 2^es is
  0, so the unsigned reinterpretation of a two's-complement negative e
  yields exactly the low es bits, which is the posit exponent field (and
  matches the arithmetic-shift regime split e >> es).
- `regime <<= es + nrFbits + 1u`, `exponent <<= nrFbits + 1u`,
  `pt_bits <<= pt_len - len`: unsigned shift-amount / left-align distance
  flowing into operator<<=(int); each is >= 1 and small by construction.
  Cast to int explicitly.

Proven behavior-neutral: optimized assembly (g++ -O2) for a TU
instantiating convert_ across posit<8,0>/<16,1>/<32,2>/<64,3> is
byte-for-byte identical before and after -- the casts are explicit forms
of conversions the compiler already performed implicitly.

The dead `convert_to_bb` (inside `#ifdef TBD`, never called) has the same
idiom but does not compile, so it is intentionally left untouched.

Verified posit_impl -Wsign-conversion AND -Wconversion clean on gcc AND
clang; posit conversion/assignment/multiplication regressions PASS on
both. (The posit<64,2..4> division failures are pre-existing on main --
identical counts with the fix reverted -- and out of scope here.)

Relates to #1279
Resolves #1281

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

4 of 4 new or added lines in 1 file covered. (100.0%)

36 existing lines in 4 files now uncovered.

41708 of 48936 relevant lines covered (85.23%)

7271799.74 hits per line

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

94.69
/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
#include <universal/internal/bit_manipulation.hpp>
18

19
namespace sw { namespace universal {
20

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

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

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

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

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

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

71
// Truncate a bigger posit to fit in a smaller
72
template<unsigned srcbits, unsigned tgtbits, typename bt, BinaryNumberType nt>
73
constexpr void truncate(const blockbinary<srcbits, bt, nt>& src, blockbinary<tgtbits, bt, nt>& tgt) {
7,213,201✔
74
        static_assert(tgtbits < srcbits, "truncate requires source posit to be bigger than target posit");
75
        constexpr unsigned diff = srcbits - tgtbits;
7,213,201✔
76
        for (unsigned i = 0; i < tgtbits; ++i) { // TODO: optimize for limbs
150,683,591✔
77
                tgt.setbit(i, src.test(i + diff));
143,470,390✔
78
        }
79
}
7,213,201✔
80

81
/*
82
NOTES
83

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

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

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

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

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

114
        /// trivial constructor
115
        blockbinary() = default;
116

117
        /// construct a blockbinary from another: bt must be the same
118
        template<unsigned nnbits>
119
        constexpr blockbinary(const blockbinary<nnbits, BlockType, NumberType>& rhs) : _block{} { this->assign(rhs); }
153,925,431✔
120

121
        // initializer for long long
122
        constexpr blockbinary(long long initial_value) noexcept : _block{} { *this = initial_value; }
1,265,088✔
123

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

152
        constexpr blockbinary& operator=(long long rhs) noexcept {
9,275,240✔
153
                if constexpr (1 < nrBlocks) {
154
                        for (unsigned i = 0; i < nrBlocks; ++i) {
36,472,776✔
155
                                _block[i] = static_cast<uint64_t>(rhs) & storageMask;
28,368,502✔
156
                                if constexpr (bitsInBlock < 64) {
157
                                        rhs >>= bitsInBlock;
28,368,502✔
158
                                }
159
                                else {
160
                                        rhs = (rhs < 0) ? -1LL : 0LL; // sign-extend for uint64_t limbs
×
161
                                }
162
                        }
163
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
164
                        _block[MSU] &= MSU_MASK;
8,104,274✔
165
                }
166
                else if constexpr (1 == nrBlocks) {
167
                        _block[0] = static_cast<uint64_t>(rhs) & storageMask;
1,170,966✔
168
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
169
                        _block[MSU] &= MSU_MASK;
1,170,966✔
170
                }
171
                return *this;
9,275,240✔
172
        }
173

174
        // conversion operators
175
        constexpr explicit operator int() const                { return int(to_sll()); }
142✔
176
        constexpr explicit operator long() const               { return long(to_sll()); }
177
        constexpr explicit operator long long() const          { return to_sll(); }
398,790✔
178
        constexpr explicit operator unsigned int() const       { return unsigned(to_ull()); }
115,844,268✔
179
        constexpr explicit operator unsigned long() const      { return (unsigned long)to_ull(); }
1✔
180
        constexpr explicit operator unsigned long long() const { return to_ull(); }
418✔
181
        // TODO: these need proper implementations that can convert very large integers to the proper scale afforded by the floating-point formats
182
        explicit operator float() const              { return to_native<float>(); }
48✔
183
        explicit operator double() const             { return to_native<double>(); }
1,186,214✔
184

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

189
        // limb access operators
190
//        constexpr BlockType& operator[](unsigned index) { return _block[index]; }
191
        constexpr BlockType operator[](unsigned index) const { return _block[index]; }
173,725,566✔
192

193
        // prefix operators
194
        constexpr blockbinary operator-() const {
630,252✔
195
                blockbinary negated(*this);
630,252✔
196
                blockbinary plusOne(1);
630,252✔
197
                negated.flip();
630,252✔
198
                negated += plusOne;
630,252✔
199
                return negated;
630,252✔
200
        }
201
        // one's complement
202
        constexpr blockbinary operator~() const {
203
                blockbinary complement(*this);
204
                complement.flip();
205
                return complement;
206
        }
207
        // increment/decrement
208
        constexpr blockbinary operator++(int) {
209
                blockbinary tmp(*this);
210
                operator++();
211
                return tmp;
212
        }
213
        constexpr blockbinary& operator++() {
2,275,507✔
214
                blockbinary increment;
215
                increment.setbits(0x1);
2,275,507✔
216
                *this += increment;
2,275,507✔
217
                return *this;
2,275,507✔
218
        }
219
        constexpr blockbinary operator--(int) {
220
                blockbinary tmp(*this);
221
                operator--();
222
                return tmp;
223
        }
224
        constexpr blockbinary& operator--() {
20,002✔
225
                blockbinary decrement;
226
                decrement.setbits(0x1);
20,002✔
227
                return *this -= decrement;
40,004✔
228
        }
229
        // arithmetic operators
230
        constexpr blockbinary& operator+=(const blockbinary& rhs) {
234,704,198✔
231
                if constexpr (nrBlocks == 1) {
232
                        _block[0] = static_cast<bt>(_block[0] + rhs.block(0));
126,514,953✔
233
                        // null any leading bits that fall outside of nbits
234
                        _block[MSU] = static_cast<bt>(MSU_MASK & _block[MSU]);
126,514,953✔
235
                }
236
                else if constexpr (bitsInBlock == 64) {
237
                        // uint64_t limbs: addcarry uses platform intrinsics (not constexpr on MSVC).
238
                        // In a constant-evaluated context, fall back to portable carry detection.
239
                        blockbinary sum;
240
                        if (std::is_constant_evaluated()) {
4,584✔
241
                                uint64_t carry = 0;
×
242
                                for (unsigned i = 0; i < nrBlocks; ++i) {
×
243
                                        uint64_t a = _block[i];
×
244
                                        uint64_t b = rhs._block[i];
×
245
                                        uint64_t s1 = a + carry;
×
246
                                        uint64_t c1 = (s1 < a) ? 1ull : 0ull;
×
247
                                        uint64_t r  = s1 + b;
×
248
                                        uint64_t c2 = (r < s1) ? 1ull : 0ull;
×
249
                                        sum._block[i] = r;
×
250
                                        carry = c1 + c2;
×
251
                                }
252
                        }
253
                        else {
254
                                uint64_t carry = 0;
4,584✔
255
                                for (unsigned i = 0; i < nrBlocks; ++i) {
34,845✔
256
                                        sum._block[i] = addcarry(_block[i], rhs._block[i], carry, carry);
30,261✔
257
                                }
258
                        }
259
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
260
                        sum._block[MSU] = static_cast<bt>(MSU_MASK & sum._block[MSU]);
4,584✔
261
                        *this = sum;
4,584✔
262
                }
263
                else {
264
                        // Multi-limb path for bt = uint8_t/uint16_t/uint32_t.
265
                        // Index-based loop (constexpr-friendly).
266
                        blockbinary sum;
267
                        std::uint64_t carry = 0;
108,184,661✔
268
                        for (unsigned i = 0; i < nrBlocks; ++i) {
348,246,865✔
269
                                carry += static_cast<std::uint64_t>(_block[i]) + static_cast<std::uint64_t>(rhs._block[i]);
240,062,204✔
270
                                sum._block[i] = static_cast<bt>(carry);
240,062,204✔
271
                                carry >>= bitsInBlock;
240,062,204✔
272
                        }
273
                        // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
274
                        sum._block[MSU] = static_cast<bt>(MSU_MASK & sum._block[MSU]);
108,184,661✔
275
                        *this = sum;
108,184,661✔
276
                }
277
                return *this;
234,704,198✔
278
        }
279
        constexpr blockbinary& operator-=(const blockbinary& rhs) {
25,893,104✔
280
                return operator+=(sw::universal::twosComplement(rhs));
25,893,104✔
281
        }
282
#define BLOCKBINARY_FAST_MUL
283
#ifdef BLOCKBINARY_FAST_MUL
284
        constexpr blockbinary& operator*=(const blockbinary& rhs) {
72,458,453✔
285
                if constexpr (NumberType == BinaryNumberType::Signed) {
286
                        if constexpr (nrBlocks == 1) {
287
                                _block[0] = static_cast<bt>(static_cast<std::uint64_t>(_block[0]) * static_cast<std::uint64_t>(rhs.block(0)));
47,451,584✔
288
                        }
289
                        else if constexpr (bitsInBlock == 64) {
290
                                // uint64_t limbs: mul128/addcarry use platform intrinsics that are not
291
                                // constexpr on MSVC. In a constant-evaluated context, fall back to a
292
                                // portable __uint128_t-based carry-propagation loop (gcc/clang only).
293
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
614✔
294
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
614✔
295
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
614✔
296
                                if (base.isneg()) {
614✔
297
                                        base.twosComplement();
1✔
298
                                }
299
                                if (multiplicant.isneg()) {
614✔
300
                                        multiplicant.twosComplement();
×
301
                                }
302
                                clear();
614✔
303
                                if (std::is_constant_evaluated()) {
614✔
304
#if defined(__SIZEOF_INT128__)
305
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
×
306
                                                uint128_t carry = 0;
×
307
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
×
308
                                                        if (i + j < nrBlocks) {
×
309
                                                                uint128_t product = static_cast<uint128_t>(base.block(i)) * multiplicant.block(j);
×
310
                                                                uint128_t sum = product + _block[i + j] + carry;
×
311
                                                                _block[i + j] = static_cast<bt>(sum);
×
312
                                                                carry = sum >> 64;
×
313
                                                        }
314
                                                }
315
                                        }
316
#else
317
                                        // Constexpr eval of uint64-limb mul without __int128 is unsupported
318
                                        // (MSVC). Forcing a throw in the constant-evaluated branch turns this
319
                                        // into a compile error rather than a silent zero-result. The branch
320
                                        // is unreachable at runtime because is_constant_evaluated() is false
321
                                        // there; the runtime intrinsic path below handles all execution.
322
                                        // Users on MSVC needing compile-time eval should use uint32_t limbs.
323
                                        throw "blockbinary<N, uint64_t> constexpr multiply requires __int128 support (gcc/clang); on MSVC use uint32_t limbs for compile-time evaluation";
324
#endif
325
                                }
326
                                else {
327
                                        for (unsigned i = 0; i < nrBlocks; ++i) {
2,618✔
328
                                                uint64_t carry = 0;
2,004✔
329
                                                for (unsigned j = 0; j < nrBlocks; ++j) {
10,844✔
330
                                                        if (i + j < nrBlocks) {
8,840✔
331
                                                                uint64_t lo, hi;
332
                                                                mul128(base.block(i), multiplicant.block(j), lo, hi);
5,422✔
333
                                                                // accumulate: _block[i+j] += lo + carry
334
                                                                // use two separate additions to avoid passing multi-bit carry
335
                                                                // to addcarry (MSVC _addcarry_u64 only accepts 0/1 carry_in)
336
                                                                uint64_t c1 = 0;
5,422✔
337
                                                                uint64_t sum = addcarry(_block[i + j], lo, 0, c1);
5,422✔
338
                                                                uint64_t c2 = 0;
5,422✔
339
                                                                sum = addcarry(sum, carry, 0, c2);
5,422✔
340
                                                                _block[i + j] = sum;
5,422✔
341
                                                                carry = hi + c1 + c2;
5,422✔
342
                                                        }
343
                                                }
344
                                        }
345
                                }
346
                                if (resultIsNeg) twosComplement();
614✔
347
                        }
348
                        else {
349
                                // is there a better way than upconverting to deal with maxneg in a 2's complement encoding?
350
                                blockbinary<nbits + 1, BlockType, NumberType> base(*this);
25,006,255✔
351
                                blockbinary<nbits + 1, BlockType, NumberType> multiplicant(rhs);
25,006,255✔
352
                                bool resultIsNeg = (base.isneg() ^ multiplicant.isneg());
25,006,255✔
353
                                if (base.isneg()) {
25,006,255✔
354
                                        base.twosComplement();
12,484,610✔
355
                                }
356
                                if (multiplicant.isneg()) {
25,006,255✔
357
                                        multiplicant.twosComplement();
12,484,609✔
358
                                }
359
                                clear();
25,006,255✔
360
                                for (unsigned i = 0; i < static_cast<unsigned>(nrBlocks); ++i) {
76,391,524✔
361
                                        std::uint64_t segment(0);
51,385,269✔
362
                                        for (unsigned j = 0; j < static_cast<unsigned>(nrBlocks); ++j) {
158,846,780✔
363
                                                segment += static_cast<std::uint64_t>(base.block(i)) * static_cast<std::uint64_t>(multiplicant.block(j));
107,461,511✔
364

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

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

518
        constexpr blockbinary& operator|=(const blockbinary& rhs) noexcept {
28,858,250✔
519
                for (unsigned i = 0; i < nrBlocks; ++i) {
130,099,608✔
520
                        _block[i] |= rhs._block[i];
101,241,358✔
521
                }
522
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
28,858,250✔
523
                return *this;
28,858,250✔
524
        }
525
        constexpr blockbinary& operator&=(const blockbinary& rhs) noexcept {
526
                for (unsigned i = 0; i < nrBlocks; ++i) {
527
                        _block[i] &= rhs._block[i];
528
                }
529
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
530
                return *this;
531
        }
532
        constexpr blockbinary& operator^=(const blockbinary& rhs) noexcept {
892✔
533
                for (unsigned i = 0; i < nrBlocks; ++i) {
13,352✔
534
                        _block[i] ^= rhs._block[i];
12,460✔
535
                }
536
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
892✔
537
                return *this;
892✔
538
        }
539

540
        // shift left operator
541
        constexpr blockbinary& operator<<=(int bitsToShift) {
47,427,601✔
542
                if (bitsToShift == 0) return *this;
47,427,601✔
543
                if (bitsToShift < 0) return operator>>=(-bitsToShift);
46,810,082✔
544
                if (bitsToShift > static_cast<int>(nbits)) {
46,810,082✔
545
                        setzero();
2,097,152✔
546
                        return *this;
2,097,152✔
547
                }
548
                if (bitsToShift >= static_cast<int>(bitsInBlock)) {
44,712,930✔
549
                        int blockShift = bitsToShift / static_cast<int>(bitsInBlock);
8,675,792✔
550
                        for (int i = static_cast<int>(MSU); i >= blockShift; --i) {
55,797,064✔
551
                                _block[i] = _block[i - blockShift];
47,121,272✔
552
                        }
553
                        for (int i = blockShift - 1; i >= 0; --i) {
38,562,396✔
554
                                _block[i] = bt(0);
29,886,604✔
555
                        }
556
                        // adjust the shift
557
                        bitsToShift -= static_cast<int>(static_cast<unsigned>(blockShift) * bitsInBlock);
8,675,792✔
558
                        // enforce the invariant that bits outside nbits are nulled before the early return
559
                        if (bitsToShift == 0) { _block[MSU] &= MSU_MASK; return *this; }
8,675,792✔
560
                }
561
                if constexpr (MSU > 0) {
562
                        // construct the mask for the upper bits in the block that needs to move to the higher word
563
                        bt mask = bit_high_mask<bt>(static_cast<unsigned>(bitsToShift), bitsInBlock);
32,602,018✔
564
                        for (unsigned i = MSU; i > 0; --i) {
147,662,534✔
565
                                _block[i] <<= bitsToShift;
115,060,516✔
566
                                // mix in the bits from the right
567
                                bt bits = bt(mask & _block[i - 1]);
115,060,516✔
568
                                _block[i] |= (bits >> (bitsInBlock - static_cast<unsigned>(bitsToShift)));
115,060,516✔
569
                        }
570
                }
571
                _block[0] <<= bitsToShift;
44,704,650✔
572
                // left shifts can push valid bits into the unused MSU storage bits; null them
573
                // so iszero()/operator== (which compare raw blocks) stay correct
574
                _block[MSU] &= MSU_MASK;
44,704,650✔
575
                return *this;
44,704,650✔
576
        }
577
        // arithmetic shift right operator
578
        constexpr blockbinary& operator>>=(int bitsToShift) {
7,101,282✔
579
                if (bitsToShift == 0) return *this;
7,101,282✔
580
                if (bitsToShift < 0) return operator<<=(-bitsToShift);
6,699,346✔
581
                if (bitsToShift >= static_cast<int>(nbits)) {
6,699,346✔
582
                        setzero();
16✔
583
                        return *this;
16✔
584
                }
585
                bool signext = sign();
6,699,330✔
586
                unsigned blockShift = 0;
6,699,330✔
587
                if (bitsToShift >= static_cast<int>(bitsInBlock)) {
6,699,330✔
588
                        blockShift = static_cast<unsigned>(bitsToShift) / bitsInBlock;
4,129,107✔
589
                        if (MSU >= blockShift) {
4,129,107✔
590
                                // shift by blocks
591
                                for (unsigned i = 0; i <= MSU - blockShift; ++i) {
48,243,016✔
592
                                        _block[i] = _block[i + blockShift];
44,113,909✔
593
                                }
594
                        }
595
                        // adjust the shift
596
                        bitsToShift -= static_cast<int>(blockShift * bitsInBlock);
4,129,107✔
597
                        if (bitsToShift == 0) {
4,129,107✔
598
                                // fix up the leading zeros if we have a negative number
599
                                if (signext) {
226✔
600
                                        // bitsToShift is guaranteed to be less than nbits
601
                                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
74✔
602
                                        for (unsigned i = nbits - static_cast<unsigned>(bitsToShift); i < nbits; ++i) {
1,186✔
603
                                                this->setbit(i);
1,112✔
604
                                        }
605
                                }
606
                                else {
607
                                        // clean up the blocks we have shifted clean
608
                                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
152✔
609
                                        for (unsigned i = nbits - static_cast<unsigned>(bitsToShift); i < nbits; ++i) {
6,384✔
610
                                                this->setbit(i, false);
6,232✔
611
                                        }
612
                                }
613
                                return *this;
226✔
614
                        }
615
                }
616
                if constexpr (MSU > 0) {
617
                        bt mask = ALL_ONES;
5,878,729✔
618
                        // mask for the lower bits in the block that need to move to the lower word
619
                        mask >>= (bitsInBlock - static_cast<unsigned>(bitsToShift));
5,878,729✔
620
                        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,350,649✔
621
                                _block[i] >>= bitsToShift;
48,471,920✔
622
                                // mix in the bits from the left
623
                                bt bits = bt(mask & _block[i + 1]);
48,471,920✔
624
                                _block[i] |= (bits << (bitsInBlock - static_cast<unsigned>(bitsToShift)));
48,471,920✔
625
                        }
626
                }
627
                _block[MSU] >>= bitsToShift;
6,699,104✔
628

629
                // fix up the leading zeros if we have a negative number
630
                if (signext) {
6,699,104✔
631
                        // bitsToShift is guaranteed to be less than nbits
632
                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
505,536✔
633
                        for (unsigned i = nbits - static_cast<unsigned>(bitsToShift); i < nbits; ++i) {
2,625,093✔
634
                                this->setbit(i);
2,119,557✔
635
                        }
636
                }
637
                else {
638
                        // clean up the blocks we have shifted clean
639
                        bitsToShift += static_cast<int>(blockShift * bitsInBlock);
6,193,568✔
640
                        for (unsigned i = nbits - static_cast<unsigned>(bitsToShift); i < nbits; ++i) {
63,359,743✔
641
                                this->setbit(i, false);
57,166,175✔
642
                        }
643
                }
644

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

650

651
        ///////////////////////////////////////////////////////////////////
652
        ///                  modifiers
653

654
        constexpr void clear() noexcept {
1,182,494,177✔
655
                for (unsigned i = 0; i < nrBlocks; ++i) {
2,147,483,647✔
656
                        _block[i] = bt(0ull);
1,341,572,422✔
657
                }
658
        }
1,182,494,177✔
659
        constexpr void setzero() noexcept { clear(); }
3,309,325✔
660
        constexpr void set() noexcept { // set all bits to 1
9,800,603✔
661
                if constexpr (nrBlocks > 1) {
662
                        for (unsigned i = 0; i < nrBlocks - 1; ++i) {
7,591,021✔
663
                                _block[i] = ALL_ONES;
5,651,717✔
664
                        }
665
                }
666
                _block[MSU] = ALL_ONES & MSU_MASK;
9,800,603✔
667
        }
9,800,603✔
668
        constexpr void reset() noexcept { clear(); } // set all bits to 0
669
        constexpr void set(unsigned i) noexcept {        setbit(i, true); }
32✔
670
        constexpr void reset(unsigned i) noexcept { setbit(i, false); }
23,102,655✔
671
        constexpr void setbit(unsigned i, bool v = true) noexcept {
1,439,112,423✔
672
                if (i >= nbits) return;
1,439,112,423✔
673
                unsigned blockIndex = i / bitsInBlock;
1,439,112,423✔
674
                if (blockIndex < nrBlocks) {
1,439,112,423✔
675
                        // GCC -O3 produces false-positive -Warray-bounds when inlining
676
                        // setbit across blockbinary instantiations with different nbits
677
                        // (e.g., blockbinary<129> calling into blockbinary<128> context).
678
                        // The bounds checks above are correct but GCC's interprocedural
679
                        // analysis loses track of which instantiation's _block is accessed.
680
#if defined(__GNUC__) && !defined(__clang__)
681
#pragma GCC diagnostic push
682
#pragma GCC diagnostic ignored "-Warray-bounds"
683
#if __GNUC__ >= 12
684
#pragma GCC diagnostic ignored "-Wstringop-overflow"
685
#endif
686
#endif
687
                        bt blockBits = _block[blockIndex];
1,439,112,423✔
688
                        bt null = bit_clear_mask<bt>(i, bitsInBlock);
1,439,112,423✔
689
                        bt bit = bt(v ? 1 : 0);
1,439,112,423✔
690
                        bt mask = bt(bit << (i % bitsInBlock));
1,439,112,423✔
691
                        _block[blockIndex] = bt((blockBits & null) | mask);
1,439,112,423✔
692
#if defined(__GNUC__) && !defined(__clang__)
693
#pragma GCC diagnostic pop
694
#endif
695
                }
696
        }
697
        constexpr void setbits(uint64_t value) noexcept {
1,087,454,640✔
698
                if constexpr (1 == nrBlocks) {
699
                        _block[0] = value & storageMask;
968,484,689✔
700
                }
701
                else if constexpr (1 < nrBlocks) {
702
                        for (unsigned i = 0; i < nrBlocks; ++i) {
367,296,335✔
703
                                _block[i] = value & storageMask;
248,326,384✔
704
                                if constexpr (bitsInBlock < 64) {
705
                                        value >>= bitsInBlock;
248,287,462✔
706
                                }
707
                                else {
708
                                        value = 0;  // avoid shift overflow when bitsInBlock >= 64
38,922✔
709
                                }
710
                        }
711
                }
712
                _block[MSU] &= MSU_MASK; // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
1,087,454,640✔
713
        }
1,087,454,640✔
714
        constexpr void setblock(unsigned b, const bt& blockBits) noexcept {
78,587,022✔
715
                if (b < nrBlocks) _block[b] = blockBits; // nop if b is out of range
78,587,022✔
716
        }        
78,587,022✔
717
        constexpr blockbinary& flip() noexcept { // in-place one's complement
96,591,287✔
718
                for (unsigned i = 0; i < nrBlocks; ++i) {
267,969,635✔
719
                        _block[i] = bt(~_block[i]);
171,378,348✔
720
                }                
721
                _block[MSU] &= MSU_MASK; // assert precondition of properly nulled leading non-bits
96,591,287✔
722
                return *this;
96,591,287✔
723
        }
724
        /// <summary>
725
        /// in-place 2's complement
726
        /// </summary>
727
        /// <returns>2's complement of original</returns>
728
        constexpr blockbinary& twosComplement() noexcept {
55,029,027✔
729
                blockbinary plusOne(1);
55,029,027✔
730
                if constexpr (NumberType == BinaryNumberType::Signed) {
731
                        flip();
55,029,027✔
732
                }
733
                else {
734
                        static_assert(NumberType == BinaryNumberType::Signed, "calling in-place 2's complement on an unsigned blockbinary"); // should this be allowed?
735
                }
736
                return *this += plusOne;
55,029,027✔
737
        }
738

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

794
        // selectors
795
        constexpr bool sign() const noexcept { return _block[MSU] & SIGN_BIT_MASK; }
488,920,602✔
796
        constexpr bool ispos() const noexcept { return !sign(); }
42,497,291✔
797
        constexpr bool isneg() const noexcept { return sign(); }
157,574,826✔
798
        constexpr bool iszero() const noexcept {
478,448,987✔
799
                for (unsigned i = 0; i < nrBlocks; ++i) if (_block[i] != 0) return false;
538,099,535✔
800
                return true;
48,079,243✔
801
        }
802
        constexpr bool isodd() const noexcept { return _block[0] & 0x1;        }
803
        constexpr bool iseven() const noexcept { return !isodd(); }
804

805
        constexpr bool all() const noexcept {
610,365,607✔
806
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != ALL_ONES) return false;
71,820✔
807
                if (_block[MSU] != MSU_MASK) return false;
610,295,409✔
808
                return true;
34,642,452✔
809
        }
810
        constexpr bool any() const noexcept {
209,680✔
811
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != 0) return true;
74,016✔
812
                if (_block[MSU] & MSU_MASK) return true;
136,240✔
813
                return false;
10✔
814
        }
815
        constexpr bool anyAfter(unsigned bitIndex) const noexcept {  // TODO: optimize for limbs
7,268,833✔
816
                unsigned limit = (bitIndex < nbits) ? bitIndex : nbits;
7,268,833✔
817
                for (unsigned i = 0; i < limit; ++i) if (test(i)) return true;
11,143,998✔
818
                return false;
3,683,424✔
819
        }
820

821
        constexpr bool none() const noexcept {
113,747,178✔
822
                if constexpr (nrBlocks > 1) for (unsigned i = 0; i < nrBlocks - 1; ++i) if (_block[i] != 0) return false;
90,440,103✔
823
                if (_block[MSU] & MSU_MASK) return false;
81,534,815✔
824
                return true;
4,071,322✔
825
        }
826
        constexpr unsigned count() const noexcept { // TODO: optimize for limbs
69,904✔
827
                unsigned nrOnes = 0;
69,904✔
828
                for (unsigned i = 0; i < nbits; ++i) {
1,169,744✔
829
                        if (test(i)) ++nrOnes;
1,099,840✔
830
                }
831
                return nrOnes;
69,904✔
832
        }
833
        constexpr bool test(unsigned bitIndex) const noexcept { return at(bitIndex); }
432,249,592✔
834
        constexpr bool at(unsigned bitIndex) const noexcept {
2,147,483,647✔
835
                if (bitIndex >= nbits) return false; // fail silently as no-op
2,147,483,647✔
836
                unsigned blockIndex = bitIndex / bitsInBlock;
2,147,483,647✔
837
                bt limb = _block[blockIndex];
2,147,483,647✔
838
                bt mask = bt(1ull << (bitIndex % bitsInBlock));
2,147,483,647✔
839
                return (limb & mask);
2,147,483,647✔
840
        }
841
        constexpr uint8_t nibble(unsigned n) const noexcept {
353✔
842
                uint8_t retval{ 0 };
353✔
843
                if (n < (1 + ((nbits - 1) >> 2))) {
353✔
844
                        bt word = _block[(n * 4) / bitsInBlock];
353✔
845
                        unsigned nibbleIndexInWord = n % (bitsInBlock >> 2);
353✔
846
                        bt mask = static_cast<bt>(bt(0x0F) << (nibbleIndexInWord*4));
353✔
847
                        bt nibblebits = static_cast<bt>(mask & word);
353✔
848
                        retval = static_cast<uint8_t>(nibblebits >> (nibbleIndexInWord*4));
353✔
849
                }
850
                else { // nop when nibble index out of bounds
UNCOV
851
                        retval = 0;
×
852
                }
853
                return retval;
353✔
854
        }
855
        constexpr bt block(unsigned b) const noexcept {
623,462,367✔
856
                if (b < nrBlocks) return _block[b]; 
623,462,367✔
UNCOV
857
                return bt(0); // return 0 when block index out of bounds
×
858
        }
859

860
        // copy a value over from one blockbinary to this blockbinary
861
        // blockbinary is a 2's complement encoding when Signed, so we sign-extend
862
        // by default for Signed; for Unsigned, the high bit is just data and must
863
        // be zero-extended on widening (clear() above already zeroed the storage).
864
        template<unsigned srcbits>
865
        constexpr blockbinary<nbits, bt, NumberType>& assign(const blockbinary<srcbits, bt, NumberType>& rhs) {
164,735,436✔
866
                clear();
164,735,436✔
867
                // since bt is the same, we can simply copy the blocks in
868
                unsigned minNrBlocks = (this->nrBlocks < rhs.nrBlocks) ? this->nrBlocks : rhs.nrBlocks;
164,735,436✔
869
                for (unsigned i = 0; i < minNrBlocks; ++i) {
399,246,402✔
870
                        _block[i] = rhs.block(i);
234,510,966✔
871
                }
872
                if constexpr (nbits > srcbits && NumberType == BinaryNumberType::Signed) {
873
                        if (rhs.sign()) {
108,190,463✔
874
                                for (unsigned i = srcbits; i < nbits; ++i) { // TODO: replace bit-oriented sequence with block
141,423,812✔
875
                                        setbit(i);
79,911,209✔
876
                                }
877
                        }
878
                }
879
                // enforce precondition for fast comparison by properly nulling bits that are outside of nbits
880
                _block[MSU] &= MSU_MASK;
164,735,436✔
881
                return *this;
164,735,436✔
882
        }
883

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

900
        // return the position of the most significant bit, -1 if v == 0
901
        constexpr int msb() const noexcept {
1,342,560✔
902
                for (int i = int(MSU); i >= 0; --i) {
2,152,834✔
903
                        if (_block[i] != 0) {
2,152,834✔
904
                                bt mask = (bt(1u) << (bitsInBlock-1));
1,342,560✔
905
                                for (int j = bitsInBlock - 1; j >= 0; --j) {
5,988,339✔
906
                                        if (_block[i] & mask) {
5,988,339✔
907
                                                return i * static_cast<int>(bitsInBlock) + j;
1,342,560✔
908
                                        }
909
                                        mask >>= 1;
4,645,779✔
910
                                }
911
                        }
912
                }
UNCOV
913
                return -1; // no significant bit found, all bits are zero
×
914
        }
915
        // conversion to native types
916
        constexpr int64_t to_sll() const {
138,757,790✔
917
                constexpr unsigned sizeoflonglong = 8 * sizeof(long long);
138,757,790✔
918
                int64_t ll{ 0 };
138,757,790✔
919
                int64_t mask{ 1 };
138,757,790✔
920
                unsigned upper = (nbits < sizeoflonglong ? nbits : sizeoflonglong);
138,757,790✔
921
                for (unsigned i = 0; i < upper; ++i) {
1,775,209,142✔
922
                        ll |= at(i) ? mask : 0;
1,636,451,352✔
923
                        mask <<= 1;
1,636,451,352✔
924
                }
925
                if (sign() && upper < sizeoflonglong) { // sign extend
138,757,790✔
926
                        for (unsigned i = upper; i < sizeoflonglong; ++i) {
2,147,483,647✔
927
                                ll |= mask;
2,147,483,647✔
928
                                mask <<= 1;
2,147,483,647✔
929
                        }
930
                }
931
                return ll;
138,757,790✔
932
        }
933
        constexpr uint64_t to_ull() const {
132,337,337✔
934
                uint64_t ull{ 0 };
132,337,337✔
935
                uint64_t mask{ 1 };
132,337,337✔
936
                uint32_t msb = nbits < 64 ? nbits : 64;
132,337,337✔
937
                for (uint32_t i = 0; i < msb; ++i) {
745,178,368✔
938
                        ull |= at(i) ? mask : 0;
612,841,031✔
939
                        mask <<= 1;
612,841,031✔
940
                }
941
                return ull;
132,337,337✔
942
        }
943
        template<typename Real,
944
                typename = typename std::enable_if< std::is_floating_point<Real>::value, Real >::type>
945
        Real to_native() const {
1,186,262✔
946
                blockbinary tmp(*this);
1,186,262✔
947
                if (isneg()) tmp.twosComplement();
1,186,262✔
948
                Real v{ 0.0 }, base{ 1.0 };
1,186,262✔
949
                for (unsigned i = 0; i < nbits; ++i) {
20,154,726✔
950
                        if (tmp.test(i)) v += base;
18,968,464✔
951
                        base *= 2.0;
18,968,464✔
952
                }
953
                return (isneg() ? -v : v);
1,186,262✔
954
        }
955
        // determine the rounding mode: result needs to be rounded up if true
956
        constexpr bool roundingMode(unsigned targetLsb) const {
1,348,786✔
957
                bool lsb = at(targetLsb);
1,348,786✔
958
                bool guard = (targetLsb == 0 ? false : at(targetLsb - 1));
1,348,786✔
959
                bool round = (targetLsb > 1 ? at(targetLsb - 2) : false);
1,348,786✔
960
                bool sticky =(targetLsb < 3 ? false : any(targetLsb - 3));
1,348,786✔
961
                bool tie = guard && !round && !sticky;
1,348,786✔
962
                return (lsb && tie) || (guard && !tie);
1,348,786✔
963
        }
964
        constexpr bool any(unsigned msb) const {
979,933✔
965
                msb = (msb > nbits - 1 ? nbits - 1 : msb);
979,933✔
966
                unsigned topBlock = msb / bitsInBlock;
979,933✔
967
                bt mask = bt(ALL_ONES >> (bitsInBlock - 1 - (msb % bitsInBlock)));
979,933✔
968
                for (unsigned i = 0; i < topBlock; ++i) {
980,310✔
969
                        if (_block[i] > 0) return true;
16,806✔
970
                }
971
                // process the partial block
972
                if (_block[topBlock] & mask) return true;
963,504✔
973
                return false;
453,622✔
974
        }
975

976
protected:
977
        // HELPER methods
978
        // none
979

980
private:
981
        bt _block[nrBlocks];
982

983
        //////////////////////////////////////////////////////////////////////////////
984
        // friend functions
985

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

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

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

1008
//////////////////////////////////////////////////////////////////////////////////
1009
// logic operators
1010

1011
template<unsigned N, typename B, BinaryNumberType T>
1012
constexpr bool operator==(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
228,892,033✔
1013
        for (unsigned i = 0; i < lhs.nrBlocks; ++i) {
483,586,164✔
1014
                if (lhs._block[i] != rhs._block[i]) {
302,052,511✔
1015
                        return false;
47,358,380✔
1016
                }
1017
        }
1018
        return true;
181,533,653✔
1019
}
1020
template<unsigned N, typename B, BinaryNumberType T>
1021
constexpr bool operator!=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
174,680,112✔
1022
        return !operator==(lhs, rhs);
174,680,112✔
1023
}
1024
template<unsigned N, typename B, BinaryNumberType T>
1025
constexpr bool operator<(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
29,292,007✔
1026
        if (lhs.ispos() && rhs.isneg()) return false; // need to filter out possible overflow conditions
29,292,007✔
1027
        if (lhs.isneg() && rhs.ispos()) return true;  // need to filter out possible underflow conditions
22,977,800✔
1028
        if (lhs == rhs) return false; // so the maxneg logic works
16,351,151✔
1029
        blockbinary<N, B, T> mneg; maxneg<N, B>(mneg);
16,104,352✔
1030
        if (rhs == mneg) return false; // special case: nothing is smaller than maximum negative
16,104,352✔
1031
        blockbinary<N, B, T> diff = lhs - rhs;
16,096,150✔
1032
        return diff.isneg();
16,096,150✔
1033
}
1034
template<unsigned N, typename B, BinaryNumberType T>
1035
constexpr bool operator<=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
12,896,368✔
1036
        return (lhs < rhs || lhs == rhs);
12,896,368✔
1037
}
1038
template<unsigned N, typename B, BinaryNumberType T>
1039
constexpr bool operator>(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
227,790✔
1040
        return !(lhs <= rhs);
227,790✔
1041
}
1042
template<unsigned N, typename B, BinaryNumberType T>
1043
constexpr bool operator>=(const blockbinary<N, B, T>& lhs, const blockbinary<N, B, T>& rhs) {
11,694,334✔
1044
        return !(lhs < rhs);
11,694,334✔
1045
}
1046
///////////////////////////////////////////////////////////////////////////////
1047
// binary operators
1048

1049
template<unsigned N, typename B, BinaryNumberType T>
1050
constexpr blockbinary<N, B, T> operator+(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
100,800,503✔
1051
        blockbinary<N, B, T> c(a);
100,800,503✔
1052
        return c += b;
100,800,503✔
1053
}
1054
template<unsigned N, typename B, BinaryNumberType T >
1055
constexpr blockbinary<N, B, T> operator-(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
16,296,492✔
1056
        blockbinary<N, B, T> c(a);
16,296,492✔
1057
        return c -= b;
32,592,437✔
1058
}
1059
template<unsigned N, typename B, BinaryNumberType T>
1060
constexpr inline blockbinary<N, B, T> operator*(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
72,424,744✔
1061
        blockbinary<N, B, T> c(a);
72,424,744✔
1062
        return c *= b;
97,398,072✔
1063
}
1064
template<unsigned N, typename B, BinaryNumberType T>
1065
constexpr inline blockbinary<N, B, T> operator/(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
67,157✔
1066
        blockbinary<N, B, T> c(a);
67,157✔
1067
        return c /= b;
68,613✔
1068
}
1069
template<unsigned N, typename B, BinaryNumberType T>
1070
constexpr blockbinary<N, B, T> operator%(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
1,599,401✔
1071
        blockbinary<N, B, T> c(a);
1,599,401✔
1072
        return c %= b;
2,934,545✔
1073
}
1074

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

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

1107
        if (a < b) { // optimization for integer numbers
1,340,861✔
1108
                result.rem = dividend; // a % b = a when a / b = 0
669,581✔
1109
                return result;         // a / b = 0 when b > a
669,581✔
1110
        }
1111
        // initialize the long division
1112
        BlockBinary accumulator = a;
671,280✔
1113
        // prepare the subtractand
1114
        BlockBinary subtractand = b;
671,280✔
1115
        int msb_b = b.msb();
671,280✔
1116
        int msb_a = a.msb();
671,280✔
1117
        int shift = msb_a - msb_b;
671,280✔
1118
        subtractand <<= shift;
671,280✔
1119
        // long division
1120
        for (int i = shift; i >= 0; --i) {
2,293,969✔
1121
                if (subtractand <= accumulator) {
1,622,689✔
1122
                        accumulator -= subtractand;
966,479✔
1123
                        result.quo.setbit(static_cast<unsigned>(i));
966,479✔
1124
                }
1125
                else {
1126
                        result.quo.setbit(static_cast<unsigned>(i), false);
656,210✔
1127
                }
1128
                subtractand >>= 1;
1,622,689✔
1129
        }
1130
        if (result_negative) {  // take 2's complement
671,280✔
1131
                result.quo.flip();
333,196✔
1132
                result.quo += 1;
333,196✔
1133
        }
1134
        if (a_sign) {
671,280✔
1135
                result.rem = -accumulator;
334,077✔
1136
        }
1137
        else {
1138
                result.rem = accumulator;
337,203✔
1139
        }
1140
        return result;
671,280✔
1141
}
1142

1143
///////////////////////////////////////////////////////////////////////////////
1144
// specialty binary operators
1145

1146
// unrounded addition, returns a blockbinary that is of size nbits+1
1147
template<unsigned N, typename B, BinaryNumberType T>
1148
constexpr blockbinary<N + 1, B, T> uradd(const blockbinary<N, B, T>& a, const blockbinary<N, B, T>& b) {
2,457,349✔
1149
        blockbinary<N + 1, B, T> result(a);
2,457,349✔
1150
        return result += blockbinary<N + 1, B, T>(b);
2,457,349✔
1151
}
1152

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

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

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

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

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

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

1211
#if TRACE_URMUL
1212
        std::cout << "    " << a_new << " * " << b_new << std::endl;
1213
        std::cout << std::setw(3) << 0 << ' ' << multiplicant << ' ' << result << std::endl;
1214
#endif
1215
        for (unsigned i = 0; i < (N+1); ++i) {
13,869,705✔
1216
                if (a_new.at(i)) {
12,524,378✔
1217
                        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
4,853,881✔
1218
                }
1219
                multiplicant <<= 1;
12,524,378✔
1220
#if TRACE_URMUL
1221
                std::cout << std::setw(3) << i << ' ' << multiplicant << ' ' << result << std::endl;
1222
#endif
1223
        }
1224
        if (result_sign) result.twosComplement();
1,345,327✔
1225
#if TRACE_URMUL
1226
        std::cout << "fnl " << result << std::endl;
1227
#endif
1228
        return result;
1,345,327✔
1229
}
1230

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

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

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

1264
        constexpr unsigned msp = nbits + roundingBits; // msp = most significant position
1265
        decimator <<= msp; // scale the decimator to the largest possible positive value
1266

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

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

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

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

1303
//////////////////////////////////////////////////////////////////////////////
1304
// conversions to string representations
1305

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

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

1339
// decimal string conversion
1340
template<unsigned nbits, typename BlockType, BinaryNumberType NumberType>
1341
std::string to_decimal(const blockbinary<nbits, BlockType, NumberType>& number) {
1,897✔
1342
        if (number.iszero()) return "0";
2,185✔
1343

1344
        std::string result;
1,753✔
1345
        blockbinary<nbits, BlockType, NumberType> dividend(number);
1,753✔
1346
        bool isNegative = false;
1,753✔
1347

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

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

1374
        if (isNegative) {
1,753✔
1375
                result = "-" + result;
475✔
1376
        }
1377

1378
        return result;
1,753✔
1379
}
1,753✔
1380

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

1388

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

© 2026 Coveralls, Inc