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

stillwater-sc / universal / 23689705135

28 Mar 2026 04:46PM UTC coverage: 84.358% (-0.03%) from 84.388%
23689705135

push

github

web-flow
feat(ucalc): mixed-precision dot product command (#650)

* feat(ucalc): mixed-precision dot product command (#629)

Add `dot [v1] [v2] [accum=<type>]` command for computing dot products
with explicit control over element type and accumulation type.

Elements are multiplied in the active type, products are accumulated
in the accumulation type (defaults to active type). Result is compared
against a quad-double reference.

Key use case: demonstrating how accumulation precision affects results.
Example: float elements with [1e10, 1, -1e10].[1, 1, 1]:
- float accum:  0 (catastrophic cancellation, rel_error = 1.0)
- dd accum:     1.0 (exact)

All output formats: plain, --json, --csv, --quiet.
21/21 CTests pass on gcc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(dfloat): implement proper operator++/-- for next representable value

dfloat's operator++ was adding 1 (integer increment) instead of
advancing to the next representable value. This caused
numeric_limits::epsilon() to return 1.0 instead of 10^(1-ndigits).

The fix normalizes the significand to exactly ndigits decimal digits
before incrementing, so that +1 on the significand gives the true
successor. For example, dfloat<7>(1.0) stored as (sig=1, exp=0) is
first normalized to (sig=1000000, exp=-6), then incremented to
(sig=1000001, exp=-6) = 1.000001.

Handles boundary cases:
- Significand overflow (10^ndigits): roll to 10^(ndigits-1), exp++
- Significand underflow (below 10^(ndigits-1)): roll to 10^ndigits-1, exp--
- Zero: advance to minpos/minneg
- Exponent overflow/underflow: saturate to inf/zero

epsilon() now returns correct values:
- decimal32 (ndigits=7):  1e-6
- decimal64 (ndigits=16): 1e-15

Also reverts the ucalc numberline workaround (ulp_unreliable) since
the underlying type bug is fixed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(universal): add to_native() API across all number systems

Add a to_native() free function to ... (continued)

28 of 42 new or added lines in 1 file covered. (66.67%)

10 existing lines in 2 files now uncovered.

44590 of 52858 relevant lines covered (84.36%)

6101828.35 hits per line

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

92.94
/include/sw/universal/number/dfloat/dfloat_impl.hpp
1
#pragma once
2
// dfloat_impl.hpp: implementation of an IEEE 754-2008 decimal floating-point number system
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 <cstring>
10
#include <cmath>
11
#include <string>
12
#include <sstream>
13
#include <iostream>
14
#include <iomanip>
15
#include <algorithm>
16

17
// supporting types and functions
18
#include <universal/native/ieee754.hpp>
19
#include <universal/number/shared/nan_encoding.hpp>
20
#include <universal/number/shared/infinite_encoding.hpp>
21
#include <universal/number/shared/specific_value_encoding.hpp>
22
// dfloat exception structure
23
#include <universal/number/dfloat/exceptions.hpp>
24
// DPD (Densely Packed Decimal) codec
25
#include <universal/number/dfloat/dpd_codec.hpp>
26
// blockbinary for encoding storage and significand arithmetic
27
#include <universal/internal/blockbinary/blockbinary.hpp>
28

29
namespace sw { namespace universal {
30

31
///////////////////////////////////////////////////////////////////////////////
32
// Internal helpers for BID encoding
33
//
34
// IEEE 754-2008 decimal format layout:
35
//   [sign(1)] [combination(5)] [exponent_continuation(w)] [trailing_significand(t)]
36
//
37
// Total bits: nbits = 1 + 5 + w + t
38
// where w = es (exponent continuation bits)
39
//       t = nbits - 1 - 5 - w
40
//
41
// Combination field (5 bits: abcde):
42
//   ab != 11: exponent MSBs = ab, MSD (most significant digit) = 0cde (0-7)
43
//   ab == 11 && c != 1: exponent MSBs = cd, MSD = 100e (8 or 9)
44
//   11110: +/- infinity
45
//   11111: NaN (quiet or signaling based on trailing significand MSB)
46
//
47
// BID encoding: trailing significand stored as binary integer
48
// DPD encoding: trailing significand stored as densely packed decimal (10-bit declets)
49

50
// Compute number of bits needed for a decimal32/64/128 configuration
51
// decimal32:  ndigits=7,  es=6   -> nbits = 1 + 5 + 6  + 20  = 32
52
// decimal64:  ndigits=16, es=8   -> nbits = 1 + 5 + 8  + 50  = 64
53
// decimal128: ndigits=34, es=12  -> nbits = 1 + 5 + 12 + 110 = 128
54

55
///////////////////////////////////////////////////////////////////////////////
56
// power_of_10: constexpr power-of-10 helpers
57
static constexpr uint64_t _pow10_table[20] = {
58
        1ull,
59
        10ull,
60
        100ull,
61
        1000ull,
62
        10000ull,
63
        100000ull,
64
        1000000ull,
65
        10000000ull,
66
        100000000ull,
67
        1000000000ull,
68
        10000000000ull,
69
        100000000000ull,
70
        1000000000000ull,
71
        10000000000000ull,
72
        100000000000000ull,
73
        1000000000000000ull,
74
        10000000000000000ull,
75
        100000000000000000ull,
76
        1000000000000000000ull,
77
        10000000000000000000ull
78
};
79

80
static constexpr uint64_t pow10_64(unsigned n) {
398✔
81
        return _pow10_table[n]; // n >= 20 is undefined: array bounds enforced by compiler in constexpr
398✔
82
}
83

84
// count decimal digits of a uint64_t
85
static constexpr unsigned count_decimal_digits(uint64_t v) {
86
        if (v == 0) return 1;
87
        unsigned d = 0;
88
        while (v > 0) { v /= 10; ++d; }
89
        return d;
90
}
91

92

93
// constexpr ceil(log2(10^n)) - bits needed to represent 10^n in binary
94
// This is the number of trailing significand bits for BID encoding
95
static constexpr unsigned bid_trailing_bits(unsigned n) {
96
        // 10^n values and their bit widths
97
        // We compute ceil(log2(10^n)) = floor(log2(10^n - 1)) + 1
98
        // Using the identity: ceil(n * log2(10)) where log2(10) ~= 3.321928
99
        // Approximate with integer arithmetic: ceil(n * 3322 / 1000)
100
        if (n == 0) return 0;
101
        return static_cast<unsigned>((static_cast<uint64_t>(n) * 3322u + 999u) / 1000u);
102
}
103

104
// DPD trailing bits: (ndigits-1)/3 declets of 10 bits + remainder
105
static constexpr unsigned dpd_trailing_bits(unsigned ndigits_minus_1) {
106
        unsigned full_declets = ndigits_minus_1 / 3;
107
        unsigned remainder = ndigits_minus_1 % 3;
108
        unsigned bits = full_declets * 10;
109
        if (remainder == 1) bits += 4;
110
        else if (remainder == 2) bits += 7;
111
        return bits;
112
}
113

114
///////////////////////////////////////////////////////////////////////////////
115
// dfloat: IEEE 754-2008 decimal floating-point number
116
//
117
// Template parameters:
118
//   ndigits  - number of decimal precision digits (p)
119
//   es       - exponent continuation bits (w)
120
//   Encoding - BID or DPD
121
//   bt       - block type for storage
122
//
123
template<unsigned _ndigits, unsigned _es, DecimalEncoding _Encoding = DecimalEncoding::BID, typename bt = std::uint32_t>
124
class dfloat {
125
public:
126
        static constexpr unsigned ndigits  = _ndigits;             // precision in decimal digits (p)
127
        static constexpr unsigned es       = _es;                  // exponent continuation bits (w)
128
        static constexpr DecimalEncoding encoding = _Encoding;
129
        static constexpr unsigned combBits = 5u;                   // combination field bits
130
        static constexpr unsigned t        = (encoding == DecimalEncoding::BID)
131
                ? bid_trailing_bits(ndigits - 1)
132
                : dpd_trailing_bits(ndigits - 1);
133
        static constexpr unsigned nbits    = 1u + combBits + es + t;
134
        static constexpr int      bias     = (3 << (es - 1)) + static_cast<int>(ndigits) - 2;
135
        static constexpr int      emax     = (3 << es) - 1 - bias;   // max biased exponent
136
        static constexpr int      emin     = -bias;                    // min biased exponent
137

138
        // Significand arithmetic type: blockbinary with enough bits for any ndigits
139
        // Signed is required because blockbinary::longdivision() requires it.
140
        // The sign bit is unused headroom since significands are always >= 0.
141
        static constexpr unsigned sig_bits = 4 * ndigits + 8;
142
        using significand_t = blockbinary<sig_bits, bt, BinaryNumberType::Signed>;
143

144
        // Wide significand for overflow-free multiplication
145
        using wide_significand_t = blockbinary<2 * sig_bits, bt, BinaryNumberType::Signed>;
146

147
        // Helper: power of 10 returning significand_t
148
        static significand_t pow10_s(unsigned n) {
2,806✔
149
                significand_t result(1);
2,806✔
150
                significand_t ten(10);
2,806✔
151
                for (unsigned i = 0; i < n; ++i) result *= ten;
29,223✔
152
                return result;
3,093✔
153
        }
154

155
        // Helper: count decimal digits of a significand_t
156
        static unsigned count_digits_s(const significand_t& v) {
819✔
157
                if (v.iszero()) return 1;
819✔
158
                unsigned count = 0;
819✔
159
                significand_t tmp(v);
819✔
160
                significand_t ten(10);
819✔
161
                while (!tmp.iszero()) { tmp /= ten; ++count; }
2,492✔
162
                return count;
819✔
163
        }
164

165
        // Helper: significand_t to string
166
        static std::string sig_to_string(const significand_t& v) {
86✔
167
                return to_decimal(v);
86✔
168
        }
169

170
        typedef bt BlockType;
171

172
        // Encoding storage type: blockbinary with Unsigned encoding
173
        using encoding_t = blockbinary<nbits, bt, BinaryNumberType::Unsigned>;
174

175
        /// trivial constructor
176
        dfloat() = default;
177

178
        dfloat(const dfloat&) = default;
179
        dfloat(dfloat&&) = default;
180

181
        dfloat& operator=(const dfloat&) = default;
182
        dfloat& operator=(dfloat&&) = default;
183

184
        // converting constructors
185
        constexpr dfloat(const std::string& stringRep) { clear(); assign(stringRep); }
1✔
186

187
        // specific value constructor
188
        constexpr dfloat(const SpecificValue code) noexcept {
73✔
189
                clear();
73✔
190
                switch (code) {
73✔
191
                case SpecificValue::maxpos:
13✔
192
                        maxpos();
13✔
193
                        break;
13✔
194
                case SpecificValue::minpos:
12✔
195
                        minpos();
12✔
196
                        break;
12✔
197
                case SpecificValue::zero:
1✔
198
                default:
199
                        zero();
1✔
200
                        break;
1✔
201
                case SpecificValue::minneg:
6✔
202
                        minneg();
6✔
203
                        break;
6✔
204
                case SpecificValue::maxneg:
6✔
205
                        maxneg();
6✔
206
                        break;
6✔
207
                case SpecificValue::infpos:
15✔
208
                        setinf(false);
15✔
209
                        break;
15✔
210
                case SpecificValue::infneg:
11✔
211
                        setinf(true);
11✔
212
                        break;
11✔
213
                case SpecificValue::nar:
8✔
214
                case SpecificValue::qnan:
215
                        setnan(NAN_TYPE_QUIET);
8✔
216
                        break;
8✔
217
                case SpecificValue::snan:
1✔
218
                        setnan(NAN_TYPE_SIGNALLING);
1✔
219
                        break;
1✔
220
                }
221
        }
73✔
222

223
        // initializers for native types
224
        explicit dfloat(signed char iv)           noexcept { clear(); *this = iv; }
225
        explicit dfloat(short iv)                 noexcept { clear(); *this = iv; }
226
        explicit dfloat(int iv)                   noexcept { clear(); *this = iv; }
85✔
227
        explicit dfloat(long iv)                  noexcept { clear(); *this = iv; }
228
        explicit dfloat(long long iv)             noexcept { clear(); *this = iv; }
229
        explicit dfloat(char iv)                  noexcept { clear(); *this = iv; }
230
        explicit dfloat(unsigned short iv)        noexcept { clear(); *this = iv; }
231
        explicit dfloat(unsigned int iv)          noexcept { clear(); *this = iv; }
6✔
232
        explicit dfloat(unsigned long iv)         noexcept { clear(); *this = iv; }
233
        explicit dfloat(unsigned long long iv)    noexcept { clear(); *this = iv; }
234
        explicit dfloat(float iv)                 noexcept { clear(); *this = iv; }
2✔
235
        explicit dfloat(double iv)                noexcept { clear(); *this = iv; }
416✔
236

237
        // assignment operators for native types
238
        dfloat& operator=(signed char rhs)        noexcept { return convert_signed(rhs); }
239
        dfloat& operator=(short rhs)              noexcept { return convert_signed(rhs); }
240
        dfloat& operator=(int rhs)                noexcept { return convert_signed(rhs); }
91✔
241
        dfloat& operator=(long rhs)               noexcept { return convert_signed(rhs); }
242
        dfloat& operator=(long long rhs)          noexcept { return convert_signed(rhs); }
243
        dfloat& operator=(char rhs)               noexcept { return convert_unsigned(rhs); }
244
        dfloat& operator=(unsigned short rhs)     noexcept { return convert_unsigned(rhs); }
245
        dfloat& operator=(unsigned int rhs)       noexcept { return convert_unsigned(rhs); }
6✔
246
        dfloat& operator=(unsigned long rhs)      noexcept { return convert_unsigned(rhs); }
247
        dfloat& operator=(unsigned long long rhs) noexcept { return convert_unsigned(rhs); }
248
        dfloat& operator=(float rhs)              noexcept { return convert_ieee754(rhs); }
2✔
249
        dfloat& operator=(double rhs)             noexcept { return convert_ieee754(rhs); }
416✔
250

251
        // conversion operators
252
        explicit operator float()           const noexcept { return float(convert_to_double()); }
253
        explicit operator double()          const noexcept { return convert_to_double(); }
389✔
254

255
#if LONG_DOUBLE_SUPPORT
256
        explicit dfloat(long double iv)           noexcept { clear(); *this = iv; }
257
        dfloat& operator=(long double rhs)        noexcept { return convert_ieee754(double(rhs)); }
258
        explicit operator long double()     const noexcept { return (long double)convert_to_double(); }
259
#endif
260

261
        // prefix operators
262
        dfloat operator-() const {
29✔
263
                dfloat negated(*this);
29✔
264
                if (!negated.iszero()) {
29✔
265
                        negated.setsign(!negated.sign());
24✔
266
                }
267
                return negated;
29✔
268
        }
269

270
        // arithmetic operators
271
        dfloat& operator+=(const dfloat& rhs) {
208✔
272
                // unpack both operands
273
                bool lhs_sign, rhs_sign;
274
                int lhs_exp, rhs_exp;
275
                significand_t lhs_sig, rhs_sig;
276
                unpack(lhs_sign, lhs_exp, lhs_sig);
208✔
277
                rhs.unpack(rhs_sign, rhs_exp, rhs_sig);
208✔
278

279
                // handle special values
280
                if (isnan() || rhs.isnan()) { setnan(NAN_TYPE_QUIET); return *this; }
208✔
281
                if (isinf() && rhs.isinf()) {
207✔
282
                        if (lhs_sign != rhs_sign) { setnan(NAN_TYPE_QUIET); return *this; } // inf + (-inf) = NaN
1✔
283
                        return *this; // same sign inf
×
284
                }
285
                if (isinf()) return *this;
206✔
286
                if (rhs.isinf()) { *this = rhs; return *this; }
205✔
287
                if (rhs.iszero()) return *this;
205✔
288
                if (iszero()) { *this = rhs; return *this; }
201✔
289

290
                // align exponents by scaling the higher-exponent significand UP
291
                // result exponent = min(lhs_exp, rhs_exp)
292
                int shift = lhs_exp - rhs_exp;
197✔
293
                int abs_shift = (shift >= 0) ? shift : -shift;
197✔
294

295
                // When the magnitude difference exceeds the precision, the smaller
296
                // operand cannot contribute any digits to the result -- short-circuit.
297
                if (abs_shift >= static_cast<int>(ndigits)) {
197✔
298
                        if (shift > 0) return *this;       // lhs dominates
×
299
                        *this = rhs; return *this;         // rhs dominates
×
300
                }
301

302
                int result_exp;
303
                bool result_sign;
304
                significand_t abs_sig;
305

306
                // Unified path using blockbinary significand_t
307
                significand_t aligned_lhs(lhs_sig);
197✔
308
                significand_t aligned_rhs(rhs_sig);
197✔
309
                significand_t ten(10);
197✔
310

311
                if (shift >= 0) {
197✔
312
                        result_exp = rhs_exp;
158✔
313
                        for (int i = 0; i < shift; ++i) aligned_lhs *= ten;
298✔
314
                }
315
                else {
316
                        result_exp = lhs_exp;
39✔
317
                        for (int i = 0; i < -shift; ++i) aligned_rhs *= ten;
119✔
318
                }
319

320
                if (lhs_sign == rhs_sign) {
197✔
321
                        abs_sig = aligned_lhs + aligned_rhs;
110✔
322
                        result_sign = lhs_sign;
110✔
323
                }
324
                else {
325
                        if (aligned_lhs >= aligned_rhs) {
87✔
326
                                abs_sig = aligned_lhs - aligned_rhs;
59✔
327
                                result_sign = lhs_sign;
59✔
328
                        }
329
                        else {
330
                                abs_sig = aligned_rhs - aligned_lhs;
28✔
331
                                result_sign = rhs_sign;
28✔
332
                        }
333
                }
334

335
                // normalize to ndigits precision
336
                normalize_and_pack(result_sign, result_exp, abs_sig);
197✔
337
                return *this;
197✔
338
        }
339
        dfloat& operator-=(const dfloat& rhs) {
63✔
340
                dfloat neg(rhs);
63✔
341
                if (!neg.iszero()) neg.setsign(!neg.sign());
63✔
342
                return operator+=(neg);
126✔
343
        }
344
        dfloat& operator*=(const dfloat& rhs) {
99✔
345
                bool lhs_sign, rhs_sign;
346
                int lhs_exp, rhs_exp;
347
                significand_t lhs_sig, rhs_sig;
348
                unpack(lhs_sign, lhs_exp, lhs_sig);
99✔
349
                rhs.unpack(rhs_sign, rhs_exp, rhs_sig);
99✔
350

351
                // handle special values
352
                if (isnan() || rhs.isnan()) { setnan(NAN_TYPE_QUIET); return *this; }
99✔
353
                if (isinf() || rhs.isinf()) {
98✔
354
                        if (iszero() || rhs.iszero()) { setnan(NAN_TYPE_QUIET); return *this; } // 0 * inf = NaN
2✔
355
                        setinf(lhs_sign != rhs_sign);
1✔
356
                        return *this;
1✔
357
                }
358
                if (iszero() || rhs.iszero()) { setzero(); return *this; }
96✔
359

360
                bool result_sign = (lhs_sign != rhs_sign);
93✔
361
                int result_exp = lhs_exp + rhs_exp;
93✔
362

363
                // Wide multiplication: urmul returns blockbinary<2*sig_bits>
364
                wide_significand_t wide = urmul(lhs_sig, rhs_sig);
93✔
365
                wide_significand_t ten_w(10);
93✔
366

367
                // Count digits in wide result and trim to ndigits
368
                // Use a helper to count digits of the wide result
369
                unsigned wd = 0;
93✔
370
                {
371
                        wide_significand_t tmp(wide);
93✔
372
                        if (tmp.iszero()) { wd = 1; }
93✔
373
                        else { while (!tmp.iszero()) { tmp /= ten_w; ++wd; } }
273✔
374
                }
375
                while (wd > ndigits) {
97✔
376
                        wide /= ten_w;
4✔
377
                        result_exp++;
4✔
378
                        wd--;
4✔
379
                }
380

381
                // Truncate wide result to significand_t
382
                significand_t result_sig;
383
                result_sig.assign(wide);
93✔
384

385
                normalize_and_pack(result_sign, result_exp, result_sig);
93✔
386
                return *this;
93✔
387
        }
388
        dfloat& operator/=(const dfloat& rhs) {
25✔
389
                bool lhs_sign, rhs_sign;
390
                int lhs_exp, rhs_exp;
391
                significand_t lhs_sig, rhs_sig;
392
                unpack(lhs_sign, lhs_exp, lhs_sig);
25✔
393
                rhs.unpack(rhs_sign, rhs_exp, rhs_sig);
25✔
394

395
                // handle special values
396
                if (isnan() || rhs.isnan()) { setnan(NAN_TYPE_QUIET); return *this; }
25✔
397
                if (isinf() && rhs.isinf()) { setnan(NAN_TYPE_QUIET); return *this; }
24✔
398
                if (rhs.iszero()) {
23✔
399
#if DFLOAT_THROW_ARITHMETIC_EXCEPTION
400
                        throw dfloat_divide_by_zero();
401
#else
402
                        if (iszero()) { setnan(NAN_TYPE_QUIET); return *this; } // 0/0
2✔
403
                        setinf(lhs_sign != rhs_sign);
1✔
404
                        return *this;
1✔
405
#endif
406
                }
407
                if (iszero()) { setzero(); return *this; }
21✔
408
                if (isinf()) { setsign(lhs_sign != rhs_sign); return *this; }
20✔
409

410
                bool result_sign = (lhs_sign != rhs_sign);
19✔
411
                int result_exp = lhs_exp - rhs_exp;
19✔
412

413
                // Unified iterative long division using blockbinary
414
                significand_t remainder(lhs_sig);
19✔
415
                significand_t quotient(0);
19✔
416
                significand_t ten(10);
19✔
417
                for (unsigned i = 0; i < ndigits; ++i) {
179✔
418
                        remainder *= ten;
160✔
419
                        quotient = quotient * ten + remainder / rhs_sig;
160✔
420
                        remainder = remainder % rhs_sig;
160✔
421
                }
422
                result_exp -= static_cast<int>(ndigits);
19✔
423

424
                normalize_and_pack(result_sign, result_exp, quotient);
19✔
425
                return *this;
19✔
426
        }
427

428
        // unary operators: advance to next/previous representable value
429
        dfloat& operator++() {
2✔
430
                if (isnan() || isinf()) return *this;
2✔
431
                if (iszero()) { *this = dfloat(SpecificValue::minpos); return *this; }
2✔
432
                bool s; int exp; significand_t sig;
433
                unpack(s, exp, sig);
2✔
434
                // Normalize significand to exactly ndigits decimal digits
435
                // so that incrementing by 1 gives the true next representable value.
436
                significand_t lo_bound = pow10_s(ndigits - 1);
2✔
437
                significand_t hi_bound = pow10_s(ndigits);
2✔
438
                while (sig < lo_bound && exp > emin) {
14✔
439
                        sig *= significand_t(10);
12✔
440
                        --exp;
12✔
441
                }
442
                if (s) {
2✔
443
                        // Negative: next = closer to zero = decrement magnitude
NEW
444
                        sig -= significand_t(1);
×
NEW
445
                        if (sig.iszero()) { setzero(); return *this; }
×
NEW
446
                        if (sig < lo_bound) {
×
NEW
447
                                sig = hi_bound - significand_t(1);
×
NEW
448
                                --exp;
×
NEW
449
                                if (exp < emin) { setzero(); return *this; }
×
450
                        }
451
                } else {
452
                        // Positive: next = increment significand
453
                        sig += significand_t(1);
2✔
454
                        if (sig >= hi_bound) {
2✔
NEW
455
                                sig = lo_bound;
×
NEW
456
                                ++exp;
×
NEW
457
                                if (exp > emax) { setinf(false); return *this; }
×
458
                        }
459
                }
460
                pack(s, exp, sig);
2✔
461
                return *this;
2✔
462
        }
463
        dfloat operator++(int) {
464
                dfloat tmp(*this);
465
                operator++();
466
                return tmp;
467
        }
468
        dfloat& operator--() {
2✔
469
                if (isnan() || isinf()) return *this;
2✔
470
                if (iszero()) { *this = dfloat(SpecificValue::minneg); return *this; }
2✔
471
                bool s; int exp; significand_t sig;
472
                unpack(s, exp, sig);
2✔
473
                // Normalize significand to exactly ndigits decimal digits
474
                significand_t lo_bound = pow10_s(ndigits - 1);
2✔
475
                significand_t hi_bound = pow10_s(ndigits);
2✔
476
                while (sig < lo_bound && exp > emin) {
14✔
477
                        sig *= significand_t(10);
12✔
478
                        --exp;
12✔
479
                }
480
                if (s) {
2✔
481
                        // Negative: previous = farther from zero = increment magnitude
NEW
482
                        sig += significand_t(1);
×
NEW
483
                        if (sig >= hi_bound) {
×
NEW
484
                                sig = lo_bound;
×
NEW
485
                                ++exp;
×
NEW
486
                                if (exp > emax) { setinf(true); return *this; }
×
487
                        }
488
                } else {
489
                        // Positive: previous = decrement significand
490
                        sig -= significand_t(1);
2✔
491
                        if (sig.iszero()) { setzero(); return *this; }
2✔
492
                        if (sig < lo_bound) {
2✔
493
                                sig = hi_bound - significand_t(1);
2✔
494
                                --exp;
2✔
495
                                if (exp < emin) { setzero(); return *this; }
2✔
496
                        }
497
                }
498
                pack(s, exp, sig);
2✔
499
                return *this;
2✔
500
        }
501
        dfloat operator--(int) {
1✔
502
                dfloat tmp(*this);
1✔
503
                operator--();
1✔
504
                return tmp;
1✔
505
        }
506

507
        // modifiers
508
        void clear() noexcept {
1,538✔
509
                _encoding.clear();
1,538✔
510
        }
1,538✔
511
        void setzero() noexcept { clear(); }
53✔
512

513
        void setinf(bool negative = true) noexcept {
28✔
514
                clear();
28✔
515
                // combination field = 11110 -> bits: sign | 11110 | 0...0
516
                // set sign
517
                setbit(nbits - 1, negative);
28✔
518
                // set combination field bits to 11110
519
                unsigned combStart = nbits - 2; // MSB of combination
28✔
520
                setbit(combStart,     true);   // a = 1
28✔
521
                setbit(combStart - 1, true);   // b = 1
28✔
522
                setbit(combStart - 2, true);   // c = 1
28✔
523
                setbit(combStart - 3, true);   // d = 1
28✔
524
                setbit(combStart - 4, false);  // e = 0
28✔
525
        }
28✔
526

527
        void setnan(int NaNType = NAN_TYPE_SIGNALLING) noexcept {
16✔
528
                clear();
16✔
529
                // combination field = 11111
530
                unsigned combStart = nbits - 2;
16✔
531
                setbit(combStart,     true);
16✔
532
                setbit(combStart - 1, true);
16✔
533
                setbit(combStart - 2, true);
16✔
534
                setbit(combStart - 3, true);
16✔
535
                setbit(combStart - 4, true);
16✔
536
                if (NaNType == NAN_TYPE_QUIET) {
16✔
537
                        // set MSB of trailing significand for quiet NaN
538
                        if (t > 0) setbit(t - 1, true);
15✔
539
                }
540
        }
16✔
541

542
        void setsign(bool negative = true) noexcept {
91✔
543
                setbit(nbits - 1, negative);
91✔
544
        }
91✔
545

546
        // use un-interpreted raw bits to set the value of the dfloat
547
        inline void setbits(uint64_t value) noexcept {
548
                _encoding.setbits(value);
549
        }
550

551
        // create specific number system values of interest
552
        dfloat& maxpos() noexcept {
13✔
553
                clear();
13✔
554
                significand_t max_sig = pow10_s(ndigits) - significand_t(1);
13✔
555
                pack(false, emax, max_sig);
13✔
556
                return *this;
13✔
557
        }
558
        dfloat& minpos() noexcept {
12✔
559
                clear();
12✔
560
                pack(false, emin, significand_t(1));
12✔
561
                return *this;
12✔
562
        }
563
        dfloat& zero() noexcept {
1✔
564
                clear();
1✔
565
                return *this;
1✔
566
        }
567
        dfloat& minneg() noexcept {
6✔
568
                clear();
6✔
569
                pack(true, emin, significand_t(1));
6✔
570
                return *this;
6✔
571
        }
572
        dfloat& maxneg() noexcept {
6✔
573
                clear();
6✔
574
                significand_t max_sig = pow10_s(ndigits) - significand_t(1);
6✔
575
                pack(true, emax, max_sig);
6✔
576
                return *this;
6✔
577
        }
578

579
        dfloat& assign(const std::string& txt) {
2✔
580
                clear();
2✔
581
                if (txt.empty()) return *this;
2✔
582

583
                // Skip leading whitespace
584
                size_t pos = 0;
2✔
585
                while (pos < txt.size() && std::isspace(static_cast<unsigned char>(txt[pos]))) ++pos;
2✔
586
                if (pos >= txt.size()) return *this;
2✔
587

588
                // Check for sign
589
                bool negative = false;
2✔
590
                if (txt[pos] == '-') { negative = true; ++pos; }
2✔
591
                else if (txt[pos] == '+') { ++pos; }
1✔
592

593
                // Check for special values (case-insensitive)
594
                std::string rest = txt.substr(pos);
2✔
595
                if (rest.size() >= 3) {
2✔
596
                        char c0 = static_cast<char>(std::tolower(static_cast<unsigned char>(rest[0])));
2✔
597
                        char c1 = static_cast<char>(std::tolower(static_cast<unsigned char>(rest[1])));
2✔
598
                        char c2 = static_cast<char>(std::tolower(static_cast<unsigned char>(rest[2])));
2✔
599
                        if (c0 == 'i' && c1 == 'n' && c2 == 'f') { setinf(negative); return *this; }
2✔
600
                        if (c0 == 'n' && c1 == 'a' && c2 == 'n') { setnan(NAN_TYPE_QUIET); return *this; }
2✔
601
                }
602

603
                // Parse decimal digits, collecting significand and tracking decimal point
604
                // Input forms: "123", "123.456", ".456", "123.", "123.456e-78", "123e5"
605
                significand_t sig(0);
2✔
606
                significand_t ten(10);
2✔
607
                unsigned digit_count = 0;
2✔
608
                int decimal_exponent = 0;
2✔
609
                bool seen_dot = false;
2✔
610
                int frac_digits = 0;
2✔
611

612
                // Parse integer and fractional parts
613
                while (pos < txt.size()) {
17✔
614
                        char ch = txt[pos];
16✔
615
                        if (ch == '.') {
16✔
616
                                if (seen_dot) break; // second dot ends parsing
2✔
617
                                seen_dot = true;
2✔
618
                                ++pos;
2✔
619
                                continue;
2✔
620
                        }
621
                        if (ch >= '0' && ch <= '9') {
14✔
622
                                if (digit_count < ndigits) {
13✔
623
                                        sig = sig * ten + significand_t(static_cast<long long>(ch - '0'));
13✔
624
                                        digit_count++;
13✔
625
                                }
626
                                else {
627
                                        // Beyond precision: count but don't store
628
                                        if (!seen_dot) decimal_exponent++;
×
629
                                }
630
                                if (seen_dot) frac_digits++;
13✔
631
                                ++pos;
13✔
632
                                continue;
13✔
633
                        }
634
                        break; // non-digit, non-dot ends the mantissa
1✔
635
                }
636

637
                // The significand represents: sig * 10^(-frac_digits)
638
                // So the base exponent before any explicit exponent is -frac_digits
639
                decimal_exponent -= frac_digits;
2✔
640

641
                // Parse optional exponent: e/E followed by optional sign and digits
642
                if (pos < txt.size() && (txt[pos] == 'e' || txt[pos] == 'E')) {
2✔
643
                        ++pos;
1✔
644
                        bool exp_neg = false;
1✔
645
                        if (pos < txt.size() && txt[pos] == '-') { exp_neg = true; ++pos; }
1✔
646
                        else if (pos < txt.size() && txt[pos] == '+') { ++pos; }
×
647

648
                        int exp_val = 0;
1✔
649
                        while (pos < txt.size() && txt[pos] >= '0' && txt[pos] <= '9') {
3✔
650
                                exp_val = exp_val * 10 + (txt[pos] - '0');
2✔
651
                                ++pos;
2✔
652
                        }
653
                        decimal_exponent += exp_neg ? -exp_val : exp_val;
1✔
654
                }
655

656
                // Remove trailing zeros from significand (normalize)
657
                while (!sig.iszero() && digit_count > 1) {
2✔
658
                        significand_t remainder = sig % ten;
2✔
659
                        if (!remainder.iszero()) break;
2✔
660
                        sig /= ten;
×
661
                        decimal_exponent++;
×
662
                        digit_count--;
×
663
                }
664

665
                if (sig.iszero()) {
2✔
666
                        setzero();
×
667
                        if (negative) setsign(true);
×
668
                        return *this;
×
669
                }
670

671
                normalize_and_pack(negative, decimal_exponent, sig);
2✔
672
                return *this;
2✔
673
        }
2✔
674

675
        // selectors
676
        bool sign() const noexcept {
1,437✔
677
                return getbit(nbits - 1);
1,437✔
678
        }
679

680
        bool iszero() const noexcept {
2,476✔
681
                // zero when all bits except sign are 0
682
                // Check all bits except the sign bit (nbits-1)
683
                for (unsigned i = 0; i < nbits - 1; ++i) {
7,173✔
684
                        if (_encoding.at(i)) return false;
7,089✔
685
                }
686
                return true;
84✔
687
        }
688

689
        bool isone() const noexcept {
2✔
690
                bool s; int e; significand_t sig;
691
                unpack(s, e, sig);
2✔
692
                return !s && (sig == significand_t(1)) && (e == 0);
2✔
693
        }
694

695
        bool ispos() const noexcept { return !sign(); }
696
        bool isneg() const noexcept { return sign(); }
697

698
        bool isinf() const noexcept {
2,593✔
699
                // combination field == 11110
700
                unsigned combStart = nbits - 2;
2,593✔
701
                return getbit(combStart) && getbit(combStart - 1) &&
2,821✔
702
                       getbit(combStart - 2) && getbit(combStart - 3) &&
2,870✔
703
                       !getbit(combStart - 4);
2,642✔
704
        }
705

706
        bool isnan() const noexcept {
2,390✔
707
                // combination field == 11111
708
                unsigned combStart = nbits - 2;
2,390✔
709
                return getbit(combStart) && getbit(combStart - 1) &&
2,610✔
710
                       getbit(combStart - 2) && getbit(combStart - 3) &&
2,655✔
711
                       getbit(combStart - 4);
2,435✔
712
        }
713

714
        bool isnan(int NaNType) const noexcept {
715
                if (!isnan()) return false;
716
                if (NaNType == NAN_TYPE_QUIET) {
717
                        return (t > 0) ? getbit(t - 1) : true;
718
                }
719
                else {
720
                        return (t > 0) ? !getbit(t - 1) : true;
721
                }
722
        }
723

724
        int scale() const noexcept {
12✔
725
                if (iszero() || isinf() || isnan()) return 0;
12✔
726
                bool s; int e; significand_t sig;
727
                unpack(s, e, sig);
12✔
728
                // scale in powers of 10
729
                return e + static_cast<int>(count_digits_s(sig)) - 1;
12✔
730
        }
731

732
        // Format modes for str()
733
        enum class FmtMode { automatic, fixed, scientific };
734

735
        // convert to string
736
        // precision: number of significant digits (0 = ndigits)
737
        // mode: automatic (default), fixed, or scientific
738
        std::string str(size_t precision = 0, FmtMode mode = FmtMode::automatic) const {
99✔
739
                if (isnan()) return std::string("nan");
103✔
740
                if (isinf()) return sign() ? std::string("-inf") : std::string("inf");
127✔
741
                if (iszero()) return sign() ? std::string("-0") : std::string("0");
84✔
742

743
                bool s; int e; significand_t sig;
744
                unpack(s, e, sig);
81✔
745

746
                // value = (-1)^s * sig * 10^e
747
                std::string digits = sig_to_string(sig);
81✔
748
                int num_digits = static_cast<int>(digits.size());
81✔
749
                int decimal_pos = num_digits + e; // position of decimal point from left
81✔
750

751
                // Determine effective precision (number of significant digits to show)
752
                size_t prec = (precision > 0) ? precision : static_cast<size_t>(ndigits);
81✔
753
                // Trim digits to requested precision
754
                if (digits.size() > prec) {
81✔
755
                        digits.resize(prec);
×
756
                }
757
                num_digits = static_cast<int>(digits.size());
81✔
758

759
                // Determine format mode
760
                // automatic: use scientific when the exponent would produce more than
761
                //            ndigits leading/trailing zeros, otherwise use fixed
762
                if (mode == FmtMode::automatic) {
81✔
763
                        if (decimal_pos > static_cast<int>(ndigits) || decimal_pos < -static_cast<int>(ndigits / 2)) {
81✔
764
                                mode = FmtMode::scientific;
35✔
765
                        }
766
                        else {
767
                                mode = FmtMode::fixed;
46✔
768
                        }
769
                }
770

771
                std::string result;
81✔
772
                if (s) result = "-";
81✔
773

774
                if (mode == FmtMode::scientific) {
81✔
775
                        // Scientific notation: d.ddd...e+/-NNN
776
                        result += digits[0];
35✔
777
                        if (num_digits > 1) {
35✔
778
                                result += '.';
18✔
779
                                result += digits.substr(1);
18✔
780
                        }
781
                        // exponent = decimal_pos - 1 (since we placed decimal after first digit)
782
                        int sci_exp = decimal_pos - 1;
35✔
783
                        result += 'e';
35✔
784
                        if (sci_exp >= 0) {
35✔
785
                                result += '+';
17✔
786
                        }
787
                        result += std::to_string(sci_exp);
35✔
788
                }
789
                else {
790
                        // Fixed notation
791
                        if (decimal_pos <= 0) {
46✔
792
                                // value < 1: 0.000...digits
793
                                result += "0.";
11✔
794
                                for (int i = 0; i < -decimal_pos; ++i) result += '0';
11✔
795
                                result += digits;
11✔
796
                        }
797
                        else if (decimal_pos >= num_digits) {
35✔
798
                                // integer value
799
                                result += digits;
25✔
800
                                for (int i = 0; i < decimal_pos - num_digits; ++i) result += '0';
42✔
801
                                result += ".0";
25✔
802
                        }
803
                        else {
804
                                // mixed: some digits before and after decimal
805
                                result += digits.substr(0, static_cast<size_t>(decimal_pos));
10✔
806
                                result += '.';
10✔
807
                                result += digits.substr(static_cast<size_t>(decimal_pos));
10✔
808
                        }
809
                }
810

811
                return result;
81✔
812
        }
81✔
813

814
        ///////////////////////////////////////////////////////////////////
815
        // Bit access (public for free functions like to_binary, color_print)
816
        bool getbit(unsigned pos) const noexcept {
60,168✔
817
                if (pos >= nbits) return false;
60,168✔
818
                return _encoding.at(pos);
60,168✔
819
        }
820

821
        ///////////////////////////////////////////////////////////////////
822
        // Unpacking / Packing helpers (public for testing)
823

824
        // Unpack the dfloat into sign, unbiased exponent, and significand integer
825
        void unpack(bool& s, int& exponent, significand_t& significand) const noexcept {
1,172✔
826
                s = sign();
1,172✔
827
                if (iszero()) { exponent = 0; significand = 0; return; }
1,172✔
828
                if (isinf() || isnan()) { exponent = 0; significand = 0; return; }
1,154✔
829

830
                // Extract combination field (5 bits)
831
                unsigned combStart = nbits - 2;
1,143✔
832
                bool a = getbit(combStart);
1,143✔
833
                bool b = getbit(combStart - 1);
1,143✔
834
                bool c = getbit(combStart - 2);
1,143✔
835
                bool d = getbit(combStart - 3);
1,143✔
836
                bool e_bit = getbit(combStart - 4);
1,143✔
837

838
                unsigned exp_msbs;
839
                unsigned msd; // most significant digit
840

841
                if (!(a && b)) {
1,143✔
842
                        // ab != 11: exp MSBs = ab, MSD = 0cde
843
                        exp_msbs = (a ? 2u : 0u) + (b ? 1u : 0u);
1,111✔
844
                        msd = (c ? 4u : 0u) + (d ? 2u : 0u) + (e_bit ? 1u : 0u);
1,111✔
845
                }
846
                else {
847
                        // ab == 11, c determines large digit vs special
848
                        // cd are exp MSBs, MSD = 100e (digit 8 or 9)
849
                        exp_msbs = (c ? 2u : 0u) + (d ? 1u : 0u);
32✔
850
                        msd = 8u + (e_bit ? 1u : 0u);
32✔
851
                }
852

853
                // Extract exponent continuation (es bits after combination field)
854
                unsigned exp_cont = 0;
1,143✔
855
                unsigned bitpos = nbits - 1 - 1 - combBits; // first bit of exponent continuation
1,143✔
856
                for (unsigned i = 0; i < es; ++i) {
8,835✔
857
                        if (getbit(bitpos - i)) {
7,692✔
858
                                exp_cont |= (1u << (es - 1 - i));
3,476✔
859
                        }
860
                }
861

862
                unsigned biased_exp = (exp_msbs << es) | exp_cont;
1,143✔
863
                exponent = static_cast<int>(biased_exp) - bias;
1,143✔
864

865
                // Extract trailing significand (t bits) using blockbinary
866
                if constexpr (encoding == DecimalEncoding::BID) {
867
                        // Read trailing bits directly from encoding into a significand_t
868
                        significand_t trailing(0);
1,046✔
869
                        for (unsigned i = 0; i < t; ++i) {
30,726✔
870
                                if (getbit(i)) trailing.setbit(i, true);
29,680✔
871
                        }
872
                        significand = significand_t(static_cast<long long>(msd)) * pow10_s(ndigits - 1) + trailing;
1,046✔
873
                }
874
                else {
875
                        // DPD: decode declets from trailing bits
876
                        significand = dpd_decode_trailing_wide(msd);
97✔
877
                }
878
        }
879

880
protected:
881
        encoding_t _encoding;
882

883
        ///////////////////////////////////////////////////////////////////
884
        // Bit manipulation helpers
885
        void setbit(unsigned pos, bool value) noexcept {
36,482✔
886
                if (pos >= nbits) return;
36,482✔
887
                _encoding.setbit(pos, value);
36,482✔
888
        }
889

890
        ///////////////////////////////////////////////////////////////////
891
        // Pack sign, unbiased exponent, and significand into the dfloat encoding
892
        void pack(bool s, int exponent, const significand_t& significand) noexcept {
818✔
893
                clear();
818✔
894
                if (significand.iszero()) return; // zero
818✔
895

896
                // Determine MSD and trailing
897
                significand_t msd_val = significand / pow10_s(ndigits - 1);
818✔
898
                unsigned msd = static_cast<unsigned>(static_cast<long long>(msd_val));
818✔
899

900
                unsigned biased_exp = static_cast<unsigned>(exponent + bias);
818✔
901

902
                // Encode sign
903
                setbit(nbits - 1, s);
818✔
904

905
                // Encode combination field
906
                unsigned exp_msbs = (biased_exp >> es) & 0x3u;
818✔
907
                unsigned combStart = nbits - 2;
818✔
908

909
                if (msd < 8) {
818✔
910
                        setbit(combStart,     (exp_msbs >> 1) & 1);
791✔
911
                        setbit(combStart - 1, exp_msbs & 1);
791✔
912
                        setbit(combStart - 2, (msd >> 2) & 1);
791✔
913
                        setbit(combStart - 3, (msd >> 1) & 1);
791✔
914
                        setbit(combStart - 4, msd & 1);
791✔
915
                }
916
                else {
917
                        setbit(combStart,     true);
27✔
918
                        setbit(combStart - 1, true);
27✔
919
                        setbit(combStart - 2, (exp_msbs >> 1) & 1);
27✔
920
                        setbit(combStart - 3, exp_msbs & 1);
27✔
921
                        setbit(combStart - 4, msd & 1);
27✔
922
                }
923

924
                // Encode exponent continuation (es bits)
925
                unsigned exp_cont = biased_exp & ((1u << es) - 1u);
818✔
926
                unsigned bitpos = nbits - 1 - 1 - combBits;
818✔
927
                for (unsigned i = 0; i < es; ++i) {
6,348✔
928
                        setbit(bitpos - i, (exp_cont >> (es - 1 - i)) & 1);
5,530✔
929
                }
930

931
                // Encode trailing significand (t bits)
932
                if constexpr (encoding == DecimalEncoding::BID) {
933
                        significand_t trailing = significand % pow10_s(ndigits - 1);
745✔
934
                        // Extract bits from blockbinary significand_t and write into encoding
935
                        for (unsigned i = 0; i < t; ++i) {
22,185✔
936
                                setbit(i, trailing.at(i));
21,440✔
937
                        }
938
                }
939
                else {
940
                        // DPD encoding: encode and write declets directly into bits
941
                        dpd_encode_trailing_wide(significand);
73✔
942
                }
943
        }
944

945
        ///////////////////////////////////////////////////////////////////
946
        // Normalize significand to ndigits and pack
947
        void normalize_and_pack(bool s, int exponent, significand_t significand) noexcept {
794✔
948
                if (significand.iszero()) { setzero(); if (s) setsign(true); return; }
794✔
949

950
                // Normalize: ensure significand has exactly ndigits digits
951
                significand_t ten(10);
777✔
952
                unsigned digits = count_digits_s(significand);
777✔
953
                while (digits > ndigits) {
793✔
954
                        significand /= ten;
16✔
955
                        exponent++;
16✔
956
                        digits--;
16✔
957
                }
958
                // No need to scale up - smaller significands are valid
959

960
                // Check for overflow/underflow
961
                if (exponent > emax) {
777✔
962
                        setinf(s);
×
963
                        return;
×
964
                }
965
                if (exponent < emin) {
777✔
966
                        // underflow to zero
967
                        setzero();
×
968
                        if (s) setsign(true);
×
969
                        return;
×
970
                }
971

972
                pack(s, exponent, significand);
777✔
973
        }
974

975
        ///////////////////////////////////////////////////////////////////
976
        // DPD encode/decode helpers (unified for all widths)
977

978
        // DPD decode: read declets directly from encoding bits
979
        significand_t dpd_decode_trailing_wide(unsigned msd) const noexcept {
97✔
980
                significand_t result(0);
97✔
981
                significand_t multiplier(1);
97✔
982
                significand_t thousand(1000);
97✔
983
                unsigned remaining = ndigits - 1;
97✔
984
                unsigned bit_offset = 0;
97✔
985

986
                while (remaining >= 3) {
666✔
987
                        // Read 10-bit declet from bit_offset
988
                        uint16_t declet = 0;
569✔
989
                        for (unsigned b = 0; b < 10; ++b) {
6,259✔
990
                                if (getbit(bit_offset + b)) declet |= static_cast<uint16_t>(1u << b);
5,690✔
991
                        }
992
                        unsigned value = dpd_decode(declet);
569✔
993
                        result += significand_t(static_cast<long long>(value)) * multiplier;
569✔
994
                        multiplier *= thousand;
569✔
995
                        bit_offset += 10;
569✔
996
                        remaining -= 3;
569✔
997
                }
998

999
                return significand_t(static_cast<long long>(msd)) * pow10_s(ndigits - 1) + result;
97✔
1000
        }
1001

1002
        // DPD encode: write declets directly into encoding bits
1003
        void dpd_encode_trailing_wide(const significand_t& significand) noexcept {
73✔
1004
                significand_t msd_factor = pow10_s(ndigits - 1);
73✔
1005
                significand_t trailing_val = significand % msd_factor;
73✔
1006
                significand_t thousand(1000);
73✔
1007
                unsigned remaining = ndigits - 1;
73✔
1008
                unsigned bit_offset = 0;
73✔
1009

1010
                while (remaining >= 3) {
498✔
1011
                        significand_t group_bb = trailing_val % thousand;
425✔
1012
                        unsigned group = static_cast<unsigned>(static_cast<long long>(group_bb));
425✔
1013
                        trailing_val /= thousand;
425✔
1014
                        uint16_t declet = dpd_encode(group);
425✔
1015
                        for (unsigned b = 0; b < 10; ++b) {
4,675✔
1016
                                setbit(bit_offset + b, (declet >> b) & 1);
4,250✔
1017
                        }
1018
                        bit_offset += 10;
425✔
1019
                        remaining -= 3;
425✔
1020
                }
1021
        }
73✔
1022

1023
        ///////////////////////////////////////////////////////////////////
1024
        // Conversion helpers
1025

1026
        // Convert native IEEE-754 double to dfloat
1027
        dfloat& convert_ieee754(double rhs) noexcept {
418✔
1028
                if (std::isnan(rhs)) {
418✔
1029
                        setnan(NAN_TYPE_QUIET);
×
1030
                        return *this;
×
1031
                }
1032
                if (std::isinf(rhs)) {
418✔
1033
                        setinf(rhs < 0);
×
1034
                        return *this;
×
1035
                }
1036
                if (rhs == 0.0) {
418✔
1037
                        setzero();
20✔
1038
                        if (std::signbit(rhs)) setsign(true);
20✔
1039
                        return *this;
20✔
1040
                }
1041

1042
                bool negative = (rhs < 0);
398✔
1043
                double abs_val = std::fabs(rhs);
398✔
1044

1045
                // Convert to decimal significand and exponent
1046
                // Double has ~15-17 significant digits, so the significand from double
1047
                // always fits in uint64_t regardless of ndigits.
1048
                int dec_exp = 0;
398✔
1049
                if (abs_val != 0.0) {
398✔
1050
                        dec_exp = static_cast<int>(std::floor(std::log10(abs_val)));
398✔
1051
                }
1052

1053
                // Scale to get min(ndigits, 17) significant digits (double precision limit)
1054
                unsigned effective_digits = (ndigits < 17) ? ndigits : 17;
398✔
1055
                int target_exp = dec_exp - static_cast<int>(effective_digits) + 1;
398✔
1056
                double scaled = abs_val / std::pow(10.0, static_cast<double>(target_exp));
398✔
1057
                uint64_t sig_narrow = static_cast<uint64_t>(std::round(scaled));
398✔
1058

1059
                // Adjust if rounding pushed us over
1060
                uint64_t limit = pow10_64(effective_digits);
398✔
1061
                if (sig_narrow >= limit) {
398✔
1062
                        sig_narrow /= 10;
×
1063
                        target_exp++;
×
1064
                }
1065
                // Remove trailing zeros
1066
                while (sig_narrow > 0 && (sig_narrow % 10) == 0) {
3,195✔
1067
                        sig_narrow /= 10;
2,797✔
1068
                        target_exp++;
2,797✔
1069
                }
1070

1071
                normalize_and_pack(negative, target_exp, significand_t(static_cast<long long>(sig_narrow)));
398✔
1072
                return *this;
398✔
1073
        }
1074

1075
        // Convert dfloat to native IEEE-754 double
1076
        double convert_to_double() const noexcept {
389✔
1077
                if (isnan()) return std::numeric_limits<double>::quiet_NaN();
389✔
1078
                if (isinf()) return sign() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
389✔
1079
                if (iszero()) return sign() ? -0.0 : 0.0;
389✔
1080

1081
                bool s; int e; significand_t sig;
1082
                unpack(s, e, sig);
356✔
1083

1084
                // value = (-1)^s * sig * 10^e
1085
                // For ndigits <= 19, sig fits in uint64_t; for wider, use string conversion
1086
                double sig_d;
1087
                if constexpr (sig_bits <= 64) {
1088
                        sig_d = static_cast<double>(static_cast<unsigned long long>(sig));
282✔
1089
                }
1090
                else {
1091
                        // Use the string representation for wide significands
1092
                        std::string sig_str = to_decimal(sig);
74✔
1093
                        sig_d = std::strtod(sig_str.c_str(), nullptr);
74✔
1094
                }
74✔
1095
                double result = sig_d * std::pow(10.0, static_cast<double>(e));
356✔
1096
                return s ? -result : result;
356✔
1097
        }
1098

1099
        dfloat& convert_signed(int64_t v) noexcept {
91✔
1100
                if (0 == v) {
91✔
1101
                        setzero();
11✔
1102
                        return *this;
11✔
1103
                }
1104
                bool negative = (v < 0);
80✔
1105
                uint64_t abs_v = static_cast<uint64_t>(negative ? -v : v);
80✔
1106

1107
                // Remove trailing zeros
1108
                int exponent = 0;
80✔
1109
                while (abs_v > 0 && (abs_v % 10) == 0) {
108✔
1110
                        abs_v /= 10;
28✔
1111
                        exponent++;
28✔
1112
                }
1113

1114
                normalize_and_pack(negative, exponent, significand_t(static_cast<long long>(abs_v)));
80✔
1115
                return *this;
80✔
1116
        }
1117

1118
        dfloat& convert_unsigned(uint64_t v) noexcept {
6✔
1119
                if (0 == v) {
6✔
1120
                        setzero();
1✔
1121
                        return *this;
1✔
1122
                }
1123

1124
                int exponent = 0;
5✔
1125
                while (v > 0 && (v % 10) == 0) {
9✔
1126
                        v /= 10;
4✔
1127
                        exponent++;
4✔
1128
                }
1129

1130
                normalize_and_pack(false, exponent, significand_t(static_cast<long long>(v)));
5✔
1131
                return *this;
5✔
1132
        }
1133

1134
private:
1135

1136
        // dfloat - dfloat logic comparisons
1137
        template<unsigned N, unsigned E, DecimalEncoding Enc, typename B>
1138
        friend bool operator==(const dfloat<N, E, Enc, B>& lhs, const dfloat<N, E, Enc, B>& rhs);
1139

1140
        // dfloat - literal logic comparisons
1141
        template<unsigned N, unsigned E, DecimalEncoding Enc, typename B>
1142
        friend bool operator==(const dfloat<N, E, Enc, B>& lhs, const double rhs);
1143

1144
        // literal - dfloat logic comparisons
1145
        template<unsigned N, unsigned E, DecimalEncoding Enc, typename B>
1146
        friend bool operator==(const double lhs, const dfloat<N, E, Enc, B>& rhs);
1147
};
1148

1149

1150
////////////////////////    helper functions   /////////////////////////////////
1151

1152
// divide dfloat a and b and return result argument
1153
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1154
void divide(const dfloat<ndigits, es, Encoding, BlockType>& a, const dfloat<ndigits, es, Encoding, BlockType>& b, dfloat<ndigits, es, Encoding, BlockType>& quotient) {
1155
        quotient = a;
1156
        quotient /= b;
1157
}
1158

1159
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1160
inline std::string to_binary(const dfloat<ndigits, es, Encoding, BlockType>& number, bool nibbleMarker = false) {
83✔
1161
        using Dfloat = dfloat<ndigits, es, Encoding, BlockType>;
1162
        std::stringstream s;
83✔
1163

1164
        // sign bit
1165
        s << "0b" << (number.sign() ? '1' : '0') << '.';
83✔
1166

1167
        // combination field (5 bits)
1168
        unsigned combStart = Dfloat::nbits - 2;
83✔
1169
        for (unsigned i = 0; i < Dfloat::combBits; ++i) {
498✔
1170
                s << (number.getbit(combStart - i) ? '1' : '0');
415✔
1171
        }
1172
        s << '.';
83✔
1173

1174
        // exponent continuation (es bits)
1175
        unsigned expStart = Dfloat::nbits - 1 - 1 - Dfloat::combBits;
83✔
1176
        for (unsigned i = 0; i < es; ++i) {
685✔
1177
                s << (number.getbit(expStart - i) ? '1' : '0');
602✔
1178
        }
1179
        s << '.';
83✔
1180

1181
        // trailing significand (t bits, MSB first)
1182
        for (int i = static_cast<int>(Dfloat::t) - 1; i >= 0; --i) {
3,303✔
1183
                s << (number.getbit(static_cast<unsigned>(i)) ? '1' : '0');
3,220✔
1184
                if (nibbleMarker && i > 0 && (i % 4 == 0)) s << '\'';
3,220✔
1185
        }
1186

1187
        return s.str();
166✔
1188
}
83✔
1189

1190
// native semantic representation: radix-10, shows decimal coefficient and exponent
1191
// Format: +DDDDDDDDDDDDDDDDe+EEE (fixed-width for visual alignment)
1192
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1193
inline std::string to_native(const dfloat<ndigits, es, Encoding, BlockType>& number, bool = false) {
1194
        using Dfloat = dfloat<ndigits, es, Encoding, BlockType>;
1195
        std::stringstream s;
1196

1197
        if (number.isnan()) { s << "NaN"; return s.str(); }
1198
        if (number.isinf()) { s << (number.sign() ? "-inf" : "+inf"); return s.str(); }
1199
        if (number.iszero()) {
1200
                s << (number.sign() ? '-' : '+');
1201
                s << std::string(ndigits, '0') << "e+0";
1202
                return s.str();
1203
        }
1204

1205
        bool sign; int exp; typename Dfloat::significand_t sig;
1206
        number.unpack(sign, exp, sig);
1207

1208
        s << (sign ? '-' : '+');
1209

1210
        // Convert significand to decimal string, left-pad to ndigits
1211
        std::string digits = Dfloat::sig_to_string(sig);
1212
        while (digits.size() < ndigits) digits = "0" + digits;
1213

1214
        s << digits << 'e' << std::showpos << exp;
1215
        return s.str();
1216
}
1217

1218
////////////////////////    DFLOAT functions   /////////////////////////////////
1219

1220
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1221
inline dfloat<ndigits, es, Encoding, BlockType> abs(const dfloat<ndigits, es, Encoding, BlockType>& a) {
1222
        dfloat<ndigits, es, Encoding, BlockType> result(a);
1223
        result.setsign(false);
1224
        return result;
1225
}
1226

1227
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1228
inline dfloat<ndigits, es, Encoding, BlockType> fabs(dfloat<ndigits, es, Encoding, BlockType> a) {
1229
        a.setsign(false);
1230
        return a;
1231
}
1232

1233

1234
////////////////////////  stream operators   /////////////////////////////////
1235

1236
// generate a dfloat format ASCII format
1237
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1238
inline std::ostream& operator<<(std::ostream& ostr, const dfloat<ndigits, es, Encoding, BlockType>& i) {
99✔
1239
        using Dfloat = dfloat<ndigits, es, Encoding, BlockType>;
1240
        using FmtMode = typename Dfloat::FmtMode;
1241

1242
        std::streamsize prec = ostr.precision();
99✔
1243
        std::streamsize width = ostr.width();
99✔
1244
        std::ios_base::fmtflags ff = ostr.flags();
99✔
1245

1246
        // Map iostream format flags to dfloat FmtMode
1247
        FmtMode mode = FmtMode::automatic;
99✔
1248
        bool scientific = (ff & std::ios_base::scientific) == std::ios_base::scientific;
99✔
1249
        bool fixed      = (ff & std::ios_base::fixed) == std::ios_base::fixed;
99✔
1250
        if (scientific && !fixed) mode = FmtMode::scientific;
99✔
1251
        else if (fixed && !scientific) mode = FmtMode::fixed;
99✔
1252

1253
        // Default to ndigits precision so all stored digits are shown.
1254
        // The iostream default precision is 6, which would silently truncate
1255
        // exact decimal digits. Only use the stream precision when the user
1256
        // has explicitly set scientific or fixed mode.
1257
        size_t effective_prec = (scientific || fixed)
99✔
1258
                ? static_cast<size_t>(prec)
198✔
1259
                : 0;  // 0 tells str() to use ndigits
1260

1261
        std::string representation = i.str(effective_prec, mode);
99✔
1262

1263
        // Handle setw and alignment
1264
        std::streamsize repWidth = static_cast<std::streamsize>(representation.size());
99✔
1265
        if (width > repWidth) {
99✔
1266
                std::streamsize diff = width - repWidth;
16✔
1267
                char fill = ostr.fill();
16✔
1268
                if ((ff & std::ios_base::left) == std::ios_base::left) {
16✔
1269
                        representation.append(static_cast<size_t>(diff), fill);
×
1270
                }
1271
                else {
1272
                        representation.insert(0, static_cast<size_t>(diff), fill);
16✔
1273
                }
1274
        }
1275

1276
        return ostr << representation;
198✔
1277
}
99✔
1278

1279
// read an ASCII dfloat format
1280
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1281
inline std::istream& operator>>(std::istream& istr, dfloat<ndigits, es, Encoding, BlockType>& p) {
1282
        std::string txt;
1283
        istr >> txt;
1284
        if (!parse(txt, p)) {
1285
                std::cerr << "unable to parse -" << txt << "- into a dfloat value\n";
1286
        }
1287
        return istr;
1288
}
1289

1290
////////////////// string operators
1291

1292
// read a dfloat ASCII format and make a dfloat out of it
1293
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1294
bool parse(const std::string& number, dfloat<ndigits, es, Encoding, BlockType>& value) {
1295
        if (number.empty()) return false;
1296
        value.assign(number);
1297
        return true;
1298
}
1299

1300

1301
//////////////////////////////////////////////////////////////////////////////////////////////////////
1302
// dfloat - dfloat binary logic operators
1303

1304
// equal: precondition is that the storage is properly nulled in all arithmetic paths
1305
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1306
inline bool operator==(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
11✔
1307
        using Dfloat = dfloat<ndigits, es, Encoding, BlockType>;
1308
        // NaN != anything (including itself)
1309
        if (lhs.isnan() || rhs.isnan()) return false;
11✔
1310
        // both zero (ignoring sign)
1311
        if (lhs.iszero() && rhs.iszero()) return true;
10✔
1312
        // compare unpacked values
1313
        bool ls, rs; int le, re;
1314
        typename Dfloat::significand_t lsig, rsig;
1315
        lhs.unpack(ls, le, lsig);
9✔
1316
        rhs.unpack(rs, re, rsig);
9✔
1317
        return (ls == rs) && (le == re) && (lsig == rsig);
9✔
1318
}
1319

1320
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1321
inline bool operator!=(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
3✔
1322
        return !operator==(lhs, rhs);
3✔
1323
}
1324

1325
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1326
inline bool operator< (const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
19✔
1327
        using Dfloat = dfloat<ndigits, es, Encoding, BlockType>;
1328
        // NaN is unordered
1329
        if (lhs.isnan() || rhs.isnan()) return false;
19✔
1330
        // handle infinities
1331
        if (lhs.isinf() && rhs.isinf()) {
19✔
1332
                return lhs.sign() && !rhs.sign(); // -inf < +inf
×
1333
        }
1334
        if (lhs.isinf()) return lhs.sign();  // -inf < anything
19✔
1335
        if (rhs.isinf()) return !rhs.sign(); // anything < +inf
19✔
1336

1337
        // handle zeros
1338
        if (lhs.iszero() && rhs.iszero()) return false;
19✔
1339
        if (lhs.iszero()) return !rhs.sign(); // 0 < positive
19✔
1340
        if (rhs.iszero()) return lhs.sign();  // negative < 0
18✔
1341

1342
        // both nonzero, non-special
1343
        bool ls = lhs.sign(), rs = rhs.sign();
17✔
1344
        if (ls != rs) return ls; // negative < positive
17✔
1345

1346
        // same sign: compare magnitudes
1347
        bool ls_ign; int le; typename Dfloat::significand_t lsig;
1348
        bool rs_ign; int re; typename Dfloat::significand_t rsig;
1349
        lhs.unpack(ls_ign, le, lsig);
15✔
1350
        rhs.unpack(rs_ign, re, rsig);
15✔
1351

1352
        // normalize to same scale for comparison
1353
        int l_scale = le + static_cast<int>(Dfloat::count_digits_s(lsig)) - 1;
15✔
1354
        int r_scale = re + static_cast<int>(Dfloat::count_digits_s(rsig)) - 1;
15✔
1355

1356
        if (l_scale != r_scale) {
15✔
1357
                // higher scale means larger magnitude
1358
                return ls ? (l_scale > r_scale) : (l_scale < r_scale);
5✔
1359
        }
1360

1361
        // same overall scale: compare significands at same exponent
1362
        // Align to same exponent by adjusting significands
1363
        typename Dfloat::significand_t ten(10);
10✔
1364
        if (le < re) {
10✔
1365
                int diff = re - le;
×
1366
                if (diff < static_cast<int>(ndigits)) {
×
1367
                        for (int i = 0; i < diff; ++i) rsig *= ten;
×
1368
                }
1369
        }
1370
        else if (re < le) {
10✔
1371
                int diff = le - re;
×
1372
                if (diff < static_cast<int>(ndigits)) {
×
1373
                        for (int i = 0; i < diff; ++i) lsig *= ten;
×
1374
                }
1375
        }
1376

1377
        return ls ? (lsig > rsig) : (lsig < rsig);
10✔
1378
}
1379

1380
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1381
inline bool operator> (const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
3✔
1382
        return operator< (rhs, lhs);
3✔
1383
}
1384

1385
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1386
inline bool operator<=(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
3✔
1387
        return operator< (lhs, rhs) || operator==(lhs, rhs);
3✔
1388
}
1389

1390
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1391
inline bool operator>=(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
3✔
1392
        return !operator< (lhs, rhs);
3✔
1393
}
1394

1395
//////////////////////////////////////////////////////////////////////////////////////////////////////
1396
// dfloat - literal binary logic operators
1397
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1398
inline bool operator==(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1399
        return operator==(lhs, dfloat<ndigits, es, Encoding, BlockType>(rhs));
1400
}
1401

1402
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1403
inline bool operator!=(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1404
        return !operator==(lhs, rhs);
1405
}
1406

1407
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1408
inline bool operator< (const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1409
        return operator<(lhs, dfloat<ndigits, es, Encoding, BlockType>(rhs));
1410
}
1411

1412
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1413
inline bool operator> (const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1414
        return operator< (dfloat<ndigits, es, Encoding, BlockType>(rhs), lhs);
1415
}
1416

1417
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1418
inline bool operator<=(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1419
        return operator< (lhs, rhs) || operator==(lhs, rhs);
1420
}
1421

1422
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1423
inline bool operator>=(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1424
        return !operator< (lhs, rhs);
1425
}
1426

1427
//////////////////////////////////////////////////////////////////////////////////////////////////////
1428
// literal - dfloat binary logic operators
1429
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1430
inline bool operator==(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1431
        return operator==(dfloat<ndigits, es, Encoding, BlockType>(lhs), rhs);
1432
}
1433

1434
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1435
inline bool operator!=(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1436
        return !operator==(lhs, rhs);
1437
}
1438

1439
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1440
inline bool operator< (const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1441
        return operator<(dfloat<ndigits, es, Encoding, BlockType>(lhs), rhs);
1442
}
1443

1444
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1445
inline bool operator> (const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1446
        return operator< (rhs, lhs);
1447
}
1448

1449
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1450
inline bool operator<=(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1451
        return operator< (lhs, rhs) || operator==(lhs, rhs);
1452
}
1453

1454
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1455
inline bool operator>=(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1456
        return !operator< (lhs, rhs);
1457
}
1458

1459
//////////////////////////////////////////////////////////////////////////////////////////////////////
1460
// dfloat - dfloat binary arithmetic operators
1461
// BINARY ADDITION
1462
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1463
inline dfloat<ndigits, es, Encoding, BlockType> operator+(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
125✔
1464
        dfloat<ndigits, es, Encoding, BlockType> sum(lhs);
125✔
1465
        sum += rhs;
125✔
1466
        return sum;
125✔
1467
}
1468
// BINARY SUBTRACTION
1469
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1470
inline dfloat<ndigits, es, Encoding, BlockType> operator-(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
63✔
1471
        dfloat<ndigits, es, Encoding, BlockType> diff(lhs);
63✔
1472
        diff -= rhs;
63✔
1473
        return diff;
63✔
1474
}
1475
// BINARY MULTIPLICATION
1476
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1477
inline dfloat<ndigits, es, Encoding, BlockType> operator*(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
99✔
1478
        dfloat<ndigits, es, Encoding, BlockType> mul(lhs);
99✔
1479
        mul *= rhs;
99✔
1480
        return mul;
99✔
1481
}
1482
// BINARY DIVISION
1483
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1484
inline dfloat<ndigits, es, Encoding, BlockType> operator/(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
25✔
1485
        dfloat<ndigits, es, Encoding, BlockType> ratio(lhs);
25✔
1486
        ratio /= rhs;
25✔
1487
        return ratio;
25✔
1488
}
1489

1490
//////////////////////////////////////////////////////////////////////////////////////////////////////
1491
// dfloat - literal binary arithmetic operators
1492
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1493
inline dfloat<ndigits, es, Encoding, BlockType> operator+(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1494
        return operator+(lhs, dfloat<ndigits, es, Encoding, BlockType>(rhs));
1495
}
1496
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1497
inline dfloat<ndigits, es, Encoding, BlockType> operator-(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1498
        return operator-(lhs, dfloat<ndigits, es, Encoding, BlockType>(rhs));
1499
}
1500
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1501
inline dfloat<ndigits, es, Encoding, BlockType> operator*(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1502
        return operator*(lhs, dfloat<ndigits, es, Encoding, BlockType>(rhs));
1503
}
1504
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1505
inline dfloat<ndigits, es, Encoding, BlockType> operator/(const dfloat<ndigits, es, Encoding, BlockType>& lhs, const double rhs) {
1506
        return operator/(lhs, dfloat<ndigits, es, Encoding, BlockType>(rhs));
1507
}
1508

1509
//////////////////////////////////////////////////////////////////////////////////////////////////////
1510
// literal - dfloat binary arithmetic operators
1511
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1512
inline dfloat<ndigits, es, Encoding, BlockType> operator+(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1513
        return operator+(dfloat<ndigits, es, Encoding, BlockType>(lhs), rhs);
1514
}
1515
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1516
inline dfloat<ndigits, es, Encoding, BlockType> operator-(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1517
        return operator-(dfloat<ndigits, es, Encoding, BlockType>(lhs), rhs);
1518
}
1519
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1520
inline dfloat<ndigits, es, Encoding, BlockType> operator*(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1521
        return operator*(dfloat<ndigits, es, Encoding, BlockType>(lhs), rhs);
1522
}
1523
template<unsigned ndigits, unsigned es, DecimalEncoding Encoding, typename BlockType>
1524
inline dfloat<ndigits, es, Encoding, BlockType> operator/(const double lhs, const dfloat<ndigits, es, Encoding, BlockType>& rhs) {
1525
        return operator/(dfloat<ndigits, es, Encoding, BlockType>(lhs), rhs);
1526
}
1527

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