1

How to check if a float is positive denormalized/negative denormalized or not denormalized.

I tried to do:

int is_denorm(float f)
{
  unsigned int x = *(int*)&f; 
  unsigned expMask = (1 << 8) - 1;
  expMask = expMask << 23;
  //now needs to check if the exp is all zero how can I do it
}
philipxy
  • 14,867
  • 6
  • 39
  • 83
Nico
  • 11
  • 4

2 Answers2

3

check if a float is positive denormalized/negative denormalized or not denormalized

Note that both C and IEEE-754 use subnormal and not denormal.

#include <math.h>

//  1 +subnormal
// -1 -subnormal
//  0 not subnormal
int subnormalness(float x) {
  if (fpclassify(x) == FP_SUBNORMAL) {
    return signbit(x) ? -1 : 1;
  }
  return 0;
}

Avoid code like *(int*)&f; and expMask << 23, .... That runs into aliasing concerns, float encoding issues and size of unsigned.


Sometimes 0.0 is desired to be classified like sub-normals

int subnormalzeroness(float x) {
  switch (fpclassify(x))
    case FP_SUBNORMAL: // fall through
    case FP_ZERO:
      return signbit(x) ? -1 : 1;
    }
  }
  return 0;
}

Code such as below works well too when NANs behave per IEEE-754 and fails < comparisons, otherwise append a && !isnan(x) to the return.

int subnormalzeroness_alt(float x) {
  return fabsf(x) < FLT_MIN;  
} 
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Perhaps not as efficient, but more fun: `return copysign(fpclassify(x) == FP_SUBNORMAL, x);`. – Eric Postpischil May 02 '22 at 17:29
  • Or `return x < 0 ? -(-FP_MIN < x) : x == 0 ? 0 : x < FP_MIN;`. – Eric Postpischil May 02 '22 at 17:31
  • `copysign()` --> Nice. Of course we now get 4 potential results including -0.0. – chux - Reinstate Monica May 02 '22 at 17:32
  • Naw, the `copysign` result is converted to the `int` return type. So the return values are just −1, 0, and +1. Unless you are in a sign-and-magnitude C implementation. – Eric Postpischil May 02 '22 at 17:33
  • @EricPostpischil OK, so the return type brings it back to 3. – chux - Reinstate Monica May 02 '22 at 17:39
  • @EricPostpischil Aside, IIRC, IEEE-754 classifies 0.0f as a _normal_ and C has `isnormal(0.0f)` as false. Is that your understanding too? – chux - Reinstate Monica May 02 '22 at 17:39
  • 1
    Yes, IEEE 754-2019 2.1 says “In this standard, zero is neither normal nor subnormal.” The bits in its representation are consistent with the subnormal format (its significand interpreted like any subnormal number has value zero), so the only reason to not consider it subnormal may be for easier writing in various places where zero is important or some sort of underflow effect does not apply or such. – Eric Postpischil May 02 '22 at 17:54
  • Although the question refers to “denormalized,” not subnormal. Subnormal numbers are those (non-zero) numbers lower in magnitude than the normal numbers. “Denormalized” is not used in the IEEE 754 standard but could refer to representations where the significand is less than a normal or canonical significand. In IEEE-754 binary format, the only such representations are for zero and the subnormals. In decimal formats, numbers may have multiple representations, so some of them could be called denormalized. And, since it is not standard, you could call zero denormalized. OP would have to clarify. – Eric Postpischil May 02 '22 at 17:58
0

Instead of making assumptions about the representation of float and unsigned int, including size and endianness and encoding, you should use the fpclassify macro defined in <math.h> specifically designed for this purpose:

int is_denorm(float f) {
    return fpclassify(f) == FP_SUBNORMAL;
}

Depending on its argument, fpclassify(x) evaluates to one of the number classification macros:

FP_INFINITE
FP_NAN
FP_NORMAL
FP_SUBNORMAL
FP_ZERO

They represent the mutually exclusive kinds of floating-point values. They expand to integer constant expressions with distinct values. Additional implementation-defined floating-point classifications, with macro definitions beginning with FP_ and an uppercase letter, may also be specified by the implementation.

The signbit macro can be used to extract the sign of a floating point value (of type float, double or long double). Note that signbit(x) evaluates to non zero for negative values and non-values, including -0.0 for which x < 0 would evaluate to false.

Note that your approach has some problems even on architectures using IEEE 754 single precision floats and 32-bit integers with the same endianness:

  • to avoid aliasing issues, instead of unsigned int x = *(int *)&f; you should write uint32_t x; memcpy(&x, &f, sizeof x);
  • testing the exponent bits is not sufficient to detect subnormal values as the values 0.0 and -0.0 also have all exponent bits set to 0.

Also note that denormalized is not always the same as subnormal: In the IEEE 754 standard, subnormal refers to non zero numbers smaller in magnitude than normal numbers with an implicit 1 mantissa (denormal is not used anymore in the IEEE 754 standard nor in the C Standard). Other floating point standards with multiple representations may have denormalized numbers for other sets of values.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    @chqrlie: FYI, `fpclassify` does not “return.” It cannot be a function in C because any `float`, `double` or `long double` argument would have to be converted to the parameter type, and then the function would not know the original type and would not be able to distinguish subnormal `float` arguments from some normal `double` arguments. The C standard refers to it as a macro. So it will just evaluate to a value, not return a value. It could be implemented with `_Generic`. – Eric Postpischil May 02 '22 at 18:01
  • @chux-ReinstateMonica: sharp eyes as always :) – chqrlie May 02 '22 at 18:03
  • @EricPostpischil: correct! answer updated. – chqrlie May 02 '22 at 18:04