ATLAS Offline Software
Loading...
Searching...
No Matches
FloatPacker.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
10
12#include "CxxUtils/ones.h"
14#include <limits>
15#include <sstream>
16#include <iomanip>
17#include <stdexcept>
18#include <bit>
19#include <cstdint>
20
21namespace {
23 typedef CxxUtils::FloatPacker::Packdest Packdest;
24 //
25 static_assert(std::numeric_limits<double>::is_iec559);
26 static_assert(std::numeric_limits<double>::digits == 53);
27 static_assert(std::numeric_limits<Packdest>::digits == 32);
28
29 constexpr std::uint64_t double_sign_mask = 0x8000000000000000ULL;
30 constexpr std::uint64_t double_exponent_mask = 0x7ff0000000000000ULL;
31 constexpr std::uint64_t double_mantissa_mask = 0x000fffffffffffffULL;
32
33 constexpr int ieee754_double_bias = 0x3ff;
34 constexpr int ieee754_double_exponent_bits = 11;
35 constexpr int ieee754_double_mantissa_bits = 52;
36 constexpr int ieee754_double_exponent_shift = 52;
37
38 constexpr std::uint64_t
39 doubleToBits(double value) noexcept
40 {
41 return std::bit_cast<std::uint64_t>(value);
42 }
43
44 constexpr double
45 bitsToDouble(std::uint64_t bits) noexcept
46 {
47 return std::bit_cast<double>(bits);
48 }
49
50 constexpr bool
51 isZeroBits(std::uint64_t bits) noexcept
52 {
53 return (bits & ~double_sign_mask) == 0;
54 }
55
56 constexpr bool
57 isNegativeBits(std::uint64_t bits) noexcept
58 {
59 return (bits & double_sign_mask) != 0;
60 }
61
62 constexpr int
63 biasedExponent(std::uint64_t bits) noexcept
64 {
65 return static_cast<int>(
66 (bits & double_exponent_mask) >> ieee754_double_exponent_shift);
67 }
68
69 constexpr std::uint64_t
70 doubleMantissaBits(std::uint64_t bits) noexcept
71 {
72 return bits & double_mantissa_mask;
73 }
74
75
76
78 const int packdest_bits = std::numeric_limits<Packdest>::digits;
79
80
81 // Handy constant: A Packdest with 1 in the high bit.
82 const Packdest high_packdest_bit = (1U << (packdest_bits - 1));
83
84 const Packdest ieee754_double_exponent_all_ones =
85 (Packdest{1} << ieee754_double_exponent_bits) - 1;
86
87
94 inline
95 int max_int (int nbits)
96 {
97 return ((1U << nbits) >> 1) - 1;
98 }
99
100
107 inline
108 int min_int (int nbits)
109 {
110 return static_cast<int>(~0U << nbits) >> 1;
111 }
112
113
122 void renormalize_denormal (int& exponent,
123 Packdest& mantissa)
124 {
125 if (mantissa == 0)
126 exponent -= packdest_bits; // Lost precision here.
127 else {
128 while ((mantissa & high_packdest_bit) == 0) {
129 --exponent;
130 mantissa <<= 1;
131 }
132 mantissa <<= 1;
133 }
134 }
135
149 void underflow_to_denormal (int min_exp,
150 int round_bits,
151 int& exponent,
152 Packdest& mantissa)
153 {
154 if (exponent <= min_exp) {
155 const Packdest mantissa_in = mantissa;
156
157 // Denormalize the mantissa.
158 mantissa = (mantissa >> 1) | high_packdest_bit;
159
160 // Now shift it right.
161 int shift = min_exp - exponent;
162 if (shift < packdest_bits)
163 mantissa >>= shift;
164 else
165 mantissa = 0; // underflow to 0.
166
167 // Flag it as denormal.
168 exponent = min_exp;
169
170 // Handle rounding, if desired.
171 if (round_bits) {
172 //
173 // +- packdest_bits - round_bits - 1
174 // |- round_bits -|v
175 // mantissa: .................X....
176 // v- shift+1 -^
177 // mantissa_in: .....X................
178 // ^
179 // +- packdest_bits - round_bits + shift
180 //
181 int orig_pos = packdest_bits - round_bits + shift;
182 if (orig_pos < packdest_bits &&
183 ((static_cast<Packdest> (1) << orig_pos) & mantissa_in) != 0)
184 {
185 Packdest lsb = (static_cast<Packdest> (1) <<
186 (packdest_bits - round_bits));
187 Packdest lsbmask = ~ (lsb - 1);
188
189 // ??? If we overflow here, it means we have to go back
190 // to a normalized representation. Just punt for now.
191 if ((mantissa & lsbmask) != lsbmask)
192 mantissa += lsb;
193 }
194 }
195 }
196 }
197
198
199} // unnamed namespace
200
201
202namespace CxxUtils {
203
204
216 int nmantissa,
217 double scale /*= 1*/,
218 bool is_signed /*= true*/,
219 bool round /*= false*/)
220 : m_nmantissa (nmantissa),
221 m_scale (scale),
222 m_is_signed (is_signed),
223 m_round (round)
224{
225 // scale==0 means not to scale.
226 // Use that instead of 1 since it's faster to test for 0.
227 if (scale == 1 || scale == 0)
228 m_invscale = 0;
229 else {
230 // Avoid spurious div-zero FPEs with clang.
232 m_invscale = 1. / m_scale;
233 }
234
235 // Set up other cached values.
237 if (m_is_signed)
238 --m_npack;
239
241
242 // Sign bit mask.
243 if (m_is_signed)
244 m_signmask = 1U << (nbits - 1);
245 else
246 m_signmask = 0;
247
248 // Number of exponent bits.
249 m_nexp = nbits - m_nmantissa;
251
252 // Minimum exponent value.
253 m_min_exp = min_int (m_nexp);
254
255 // Maximum exponent value.
256 m_max_exp = max_int (m_nexp);
257
258 if (m_npack < 1 || m_npack > nbits)
259 throw std::runtime_error ("Bad number of mantissa bits.");
260}
261
262
273FloatPacker::pack (double src, std::string* err /*= nullptr*/) const
274{
275 double d = src;
276 std::uint64_t bits = doubleToBits(d);
277
278 // Fast-path for zero. (Purely an optimization.)
279 // Note: can't use a double compare here. On some architectures (eg, MIPS)
280 // a denormal will compare equal to zero.
281 if (bits == 0) {
282 return 0;
283 }
284
285 // Check for NaN and infinity.
286 if (biasedExponent(bits) == ieee754_double_exponent_all_ones) {
287 if (err) {
288 std::ostringstream os;
289 os << "Bad float number: " << src << " ("
290 << std::setbase(16)
291 << static_cast<std::uint32_t>(bits)
292 << " "
293 << static_cast<std::uint32_t>(bits >> 32)
294 << ")";
295 *err = os.str();
296 }
297 d = 0;
298 bits = doubleToBits(d);
299 }
300
301 if (m_invscale){
302 d *= m_invscale;
303 bits = doubleToBits(d);
304 }
305 bool was_negative = false;
306 if (isNegativeBits(bits)) {
307 if (m_is_signed) {
308 was_negative = true;
309 d = -d;
310 bits = doubleToBits(d);
311 }
312 else {
313 // Don't complain on -0.
314 if (d < 0 && err) {
315 std::ostringstream os;
316 os << "Float overflow during packing: " << src;
317 *err = os.str();
318 }
319 d = 0;
320 bits = doubleToBits(d);
321 }
322 }
323
324 // Check for zero again.
325 // (Also need to preserve the sign; the scale division may
326 // have underflowed.)
327 if (isZeroBits(bits)) {
328 return was_negative ? m_signmask : 0;
329 }
330
331 // Get packdest_bits bits of mantissa.
332 const std::uint64_t fullMantissa = doubleMantissaBits(bits);
333 Packdest mantissa = static_cast<Packdest>(
334 fullMantissa >> (ieee754_double_mantissa_bits - packdest_bits));
335 int exponent = biasedExponent(bits) - ieee754_double_bias;
336
337 // Do rounding, if requested.
338 if (m_round) {
339 const Packdest lsbmask = Packdest{1} << (packdest_bits - m_npack);
340 Packdest roundmask = ~Packdest{0};
341 bool roundbit = false;
342
343 if (lsbmask > 1) {
344 roundbit = (mantissa & (lsbmask >> 1)) != 0;
345 roundmask = ~static_cast<Packdest>((lsbmask >> 1) - 1);
346 }
347 else {
348 // We are keeping all packdest_bits bits of the extracted mantissa.
349 // The rounding bit is therefore the next bit below those bits in the
350 // original 52-bit double mantissa.
351 constexpr int shift = ieee754_double_mantissa_bits - packdest_bits;
352 roundbit = (fullMantissa & (std::uint64_t{1} << (shift - 1))) != 0;
353 }
354
355 if (roundbit) {
356 // Handle the case where it would overflow.
357 if ((mantissa & roundmask) == roundmask) {
358 mantissa >>= 1;
359 mantissa |= roundmask;
360 exponent += 1;
361 }
362
363 mantissa += lsbmask;
364 }
365 }
366
367 // If the number is too large, bitch, and reset to the largest number.
368 if (exponent > m_max_exp) {
369 if (err) {
370 std::ostringstream os;
371 os << "Float overflow during packing: " << src;
372 *err = os.str();
373 }
374 exponent = m_max_exp;
375 mantissa = static_cast<Packdest> (~0);
376 }
377
378 // Handle denormals. (We've already handled the zero case.)
379 if (exponent == - ieee754_double_bias)
380 renormalize_denormal (exponent, mantissa);
381
382 // If the number is too small, denormalize, or underflow to 0.
383 underflow_to_denormal (m_min_exp, m_round ? m_npack: 0, exponent, mantissa);
384
385 // Pack in the mantissa bits.
386 Packdest dest = mantissa >> (packdest_bits - m_npack);
387
388 // The exponent, if desired.
389 if (m_nexp > 0)
390 dest |= ((exponent - m_min_exp) << m_npack);
391
392 // And the optional sign bit.
393 if (was_negative)
394 dest |= m_signmask;
395
396 return dest;
397}
398
399
407double
408FloatPacker::unpack(Packdest val, std::string* err /*= nullptr*/) const
409{
410 // Fast-path for 0.
411 if (val == 0) {
412 return 0;
413 }
414
415 // Break apart the packed value.
416 const bool was_negative = (val & m_signmask) != 0;
417
418 double d = 0;
419
420 // Fast path for fixed-point representations.
421 if (m_nexp == 0) {
422 const Packdest mantissa = val & m_npack_ones;
423 d = mantissa / (static_cast<double>(m_npack_ones) + 1);
424
425 if (was_negative) {
426 d *= -1;
427 }
428 }
429 else {
430 // Get the mantissa.
431 Packdest mantissa = (val & m_npack_ones) << (packdest_bits - m_npack);
432 // General case.
433 // Get the exponent.
434 int exponent = static_cast<int>((val >> m_npack) & m_nexp_ones);
435 exponent += m_min_exp; // unbias.
436
437 // Handle denormals.
438 if (exponent == m_min_exp) {
439 // Maybe it was -0?
440 if (mantissa == 0) {
441 std::uint64_t bits = 0;
442 if (was_negative) {
443 bits |= double_sign_mask;
444 }
445 return bitsToDouble(bits);
446 }
447 renormalize_denormal(exponent, mantissa);
448 }
449 // Complain about overflow.
450 if (exponent >= max_int(ieee754_double_exponent_bits)) {
451 if (err) {
452 std::ostringstream os;
453 os << "Overflow while unpacking float; exponent: " << exponent;
454 *err = os.str();
455 }
456 exponent = max_int(ieee754_double_exponent_bits) + 1;
457 mantissa = 0; // Infinity.
458 }
459 // Underflow into denormal.
460 underflow_to_denormal(-ieee754_double_bias, 0, exponent, mantissa);
461 // Pack into a double.
462 std::uint64_t bits = 0;
463 if (was_negative) {
464 bits |= double_sign_mask;
465 }
466 bits |= static_cast<std::uint64_t>(exponent + ieee754_double_bias)
467 << ieee754_double_exponent_shift;
468 bits |= static_cast<std::uint64_t>(mantissa)
469 << (ieee754_double_mantissa_bits - packdest_bits);
470 d = bitsToDouble(bits);
471 }
472 // Set the result.
473 if (m_scale) {
474 d *= m_scale;
475 }
476 return d;
477}
478
479} // namespace CxxUtils
Pack/unpack floating-point data from/to a given number of bits.
int m_nmantissa
Number of bits in the mantissa + sign bit.
double unpack(Packdest val, std::string *err=nullptr) const
Unpack the value VAL.
std::uint32_t Packdest
Type into which we pack.
Definition FloatPacker.h:61
Packdest pack(double src, std::string *err=nullptr) const
Pack a value.
Packdest m_npack_ones
Mask with that many low bits set.
int m_min_exp
Minimum exponent value.
int m_nexp
Number of exponent bits.
FloatPacker(int nbits, int nmantissa, double scale=1, bool is_signed=true, bool round=false)
Constructor.
bool m_is_signed
Should we use a sign bit?
double m_invscale
Inverse of scale.
int m_npack
Number of bits in mantissa (exclusive of any sign bit).
int m_max_exp
Maximum exponent value.
Packdest m_nexp_ones
Mask with that many low bits set.
Packdest m_signmask
Mask containing the sign bit (or 0 if there's no sign bit).
bool m_round
Should we round instead of truncating?
double m_scale
Scale factor for stored numbers.
constexpr T ones(unsigned int n)
Return a bit mask with the lower n bits set.
Definition ones.h:25
virtual void shift(size_t pos, ptrdiff_t offs) override
Shift the elements of the container.
Construct a bit mask.
Tell the compiler to optimize assuming that FP may trap.
#define CXXUTILS_TRAPPING_FP
Definition trapping_fp.h:24