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