How people check nan and inf in C89
Asked Answered
H

4

6

isnan(), isinf() was not in the spec until C99, is there anyway to implement such a function in C89?

i could use if (d * 0 != 0) to check if d was NaN or Inf, but we always compile our project with option -Werror=float-equal which would like to shouting out :error: no == or != on float point value

How people check nan and inf in C89 then?

Hilbert answered 18/1, 2020 at 3:44 Comment(9)
Try x > DBL_MAX for infinity and x != x for NAN.Catlee
C99 has been out for 20+ years. Why the interest in C89?Catlee
@chux I think your first comment could become a nice answer to an interesting question, with a little explanation. I would not be able to provide a good explanation, so can I ask you to create an answer?Rumph
@chux-ReinstateMonica I was professionally confined to C89 until some 5 years ago. It was a question of purpose-oriented qualification and licenses with the used compilers in a slightly unusual environment. Also some worries about code behaviour people relied on. All with a touch of not entirely unreasonable paranoia.Rumph
@Rumph Answer posted. not entirely unreasonable paranoiaCatlee
@Rumph the first comment of @chux may not work because of the compile option -Werror=float-equal.Hilbert
@Hilbert Would that interesting comment not be more helpful with the answer by chux and addressed to chux?Rumph
hukeping, If code warns due to -Werror=float-equal, simple disable that warning for that section of code. How to disable GCC warnings for a few lines of code.Catlee
chux, i think #pragma directive would work.Hilbert
C
7

Since C99

isinf(x) and isnan(x) work on the real-floating type of x and is undefined for other types like int. isinf(x) and isnan(x) are macros.

isinf(x) and isnan(x) use the same name for float, double, long double and so act like an overloaded function.

isinf

For C89 we could use separate functions to test against _MAX. Note that C89 does not define long double.

A "roll your own" isinf() can use the below.

#include <float.h>
int isinf_f(float x) { return x < -FLT_MAX || x > FLT_MAX; }
int isinf_d(double x) { return x < -DBL_MAX || x > DBL_MAX; }

Note that C does not require that an implementation support infinity. If so, the above are never true.

isnan

A "roll your own" for C89 isnan() is trickier. Could be done below as functions or a simply macro. Functionality relies on modern Not-a-number behavior where a Nan is never equal to anything, not even itself. This is not specified by C89 but commonly the underlying floating point system follows this. Otherwise you need a more platform specific approach.

/* Note `x` used twice here - so use with caution */
#define my_is_nan(x) ( (x) != (x) )

int isnan_f(float x) { x != x; }
int isnan_d(double x) { x != x; }

Note that C does not require that an implementation support Not-a-number. If so, the above are never true.

Given the C89 wild-wild-west era, I would not assume IEEE 754 compliance. Infinity and NAN are exactly the fringes of any floating point implementations that lack formal compliance. Good luck.


C allows use of FP wider math depending on FLT_EVAL_METHOD so 1.0f / 7.0f might use double. This complicates things a bit, yet using a true function does coerce x expression into the desired type.

Catlee answered 18/1, 2020 at 15:16 Comment(0)
I
2

If your system uses the IEEE 754 Standard to represent floating point values (as most do), then you can explicitly check for the NaN and inf values. Single-precision (32-bit) floating-point values have 1 sign bit (bit 31), an 8 bit exponent (bits 30-23) and a 23 bit mantissa (bits 22-0), laid out like so (in binary format):

SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM

An infinity value is denoted with an exponent of all 1s and a mantissa of all 0s (the sign bit distinguishes between negative infinity and positive infinity).

A NaN value is denoted by an exponent of all 1s and a non-zero fraction (a quiet NaN has the most significant mantissa bit set, whilst a signalling Nan has this bit clear).

So, by 'casting' the float to an unsigned, 32-bit integer, we can explicitly check for these representations (assuming unsigned int is a 32-bit type):

int IsInfinite(float test) {
    unsigned int mask = *(unsigned int *)(&test);
    return ( (mask & 0x7F800000) == 0x7F800000 && (mask & 0x007FFFFF) == 0 );
}

int NotANumber(float test) {
    unsigned int mask = *(unsigned int *)(&test);
    return ( (mask & 0x7F800000) == 0x7F800000 && (mask & 0x007FFFFF) != 0 );
}

The representation for double-precision values is similar, but with 11 exponent bits (62-52) and 52 mantissa bits (51-0).

Issykkul answered 18/1, 2020 at 13:11 Comment(3)
unsigned int mask = *(unsigned int *)(&test); violates strict aliasing, ignores any potential endianness issues, and assumes all the bits of a float fit exactly into an unsigned int.Urinal
@AndrewHenle , do you mean if the floating-point numbers are represented in big-endian form in our hardware, these two functions will not work properly?Hilbert
@hukeping: The method fails when float has a different endian-ness as int. That does not happen on real hardware - it breaks basic ideas behind IEEE754, such as sorting numbers by their binary representation.Zweig
C
1

On review, perhaps another approach for C89.

If we assume comparing a NaN with a finite value is always false (which is the IEEE approach), we could guess that on such systems that do not follow that that the compare is always true (versus sometimes true). The trick would then be to handle both cases.

With x as a non-Nan, (x >= 0.0) == (x < 0.0) should always be false as mathematically they are the opposite tests.

With x as a NaN, if we are fortunate that such x vs. constant compares are always false or always true, the is_nan() result is true.

// Assuming -Werror=float-equal 
int is_nan(double x) {
  return (x >= 0.0) == (x < 0.0);
}

As possible, I would also input a static_assert() for a C89 compile. Given _Static_assert does not exist in C89, consider various C89 substitutes as found here.

Catlee answered 11/8, 2022 at 15:23 Comment(0)
W
-2

I would like to try to expose NaN by a logical consideration.

If a float is not infinite (neither greater than FLT_MAX nor less than -FLT_MAX), it should therefore be less than or equal to FLT_MAX. If not, it is NaN.

int is_inf(float x) { return x < -FLT_MAX || x > FLT_MAX; }
int is_nan(float x) { return !is_inf(x) && !(x <= FLT_MAX); }

My very first answer and already down voted. So I decided to add an example. Well, it works as expected.

#include <stdio.h>
#include <math.h>
#include <float.h>

static int is_inf(float x) { return x < -FLT_MAX || x > FLT_MAX; }
static int is_nan(float x) { return !is_inf(x) && !(x <= FLT_MAX); }

static void show(float f)
{
  float g = -f;
  printf("% f : %i %i %i %i\n", f, isinf(f), isnan(f), is_inf(f), is_nan(f));
  printf("% f : %i %i %i %i\n", g, isinf(g), isnan(g), is_inf(g), is_nan(g));
}

int main(void)
{
  float inf = FLT_MAX * 2.0f;
  float nan = inf / inf;

  show(inf);
  show(nan);
  show(FLT_MAX);
  show(FLT_MIN);
  show(0.0f);
  show(1234.5678f);

  return 0;
}

Compiled and started under Linux:

$ gcc infnan.c
$ ./a.out
 inf : 1 0 1 0
-inf : -1 0 1 0
-nan : 0 1 0 1
 nan : 0 1 0 1
 340282346638528859811704183484516925440.000000 : 0 0 0 0
-340282346638528859811704183484516925440.000000 : 0 0 0 0
 0.000000 : 0 0 0 0
-0.000000 : 0 0 0 0
 0.000000 : 0 0 0 0
-0.000000 : 0 0 0 0
 1234.567749 : 0 0 0 0
-1234.567749 : 0 0 0 0

Complementary solution for the data type double with improved return value.

static int is_inf(double x) {
  if (x >  DBL_MAX) return  1;
  if (x < -DBL_MAX) return -1;
  return 0;
}
static int is_nan(double x) { return !is_inf(x) && !(x <= DBL_MAX); }

Thinking about it again, is_nan could be calculated easier.

int is_nan(double x) { return !(x > DBL_MAX || x <= DBL_MAX); }

Or even without an included constant.

int is_nan(double x) { return !(x > 0.0 || x <= 0.0); }
Woolf answered 30/1, 2020 at 16:12 Comment(4)
nan does not compare equal, less than, or greater than to anything.Scouring
@S.S.Anne "nan does not compare equal, less than, or greater than to anything" is not specified by C89. That is an IEEE 754 spec. It's a good start, yet as OP wants a C89 solution, it is even more probable that nan does not follow expected practices of today (2020). OP may need a solution specific to the implementation.Catlee
Andreas, Minor: suggest "% f" --> "% -14g" for a cleaner informative table.Catlee
@S.S.Anne: In particular, for x is NaN (and only for NaN), (x<C)==(x>=C) (both are false). This suggestion happens to use C=FLT_MAX for the constant to compare with, which is fine. As for non-IEEE754 answers, that doesn't really make sense. NaN is very much an IEEE754 invention, and the non-signaling NaN in particular.Zweig

© 2022 - 2024 — McMap. All rights reserved.