On Sat, Apr 22, 2023 at 4:34 PM Ben Pfaff <b...@cs.stanford.edu> wrote: > Before this afternoon, I thought that a check like this for a double 'd': > d == floor (d) && d >= LONG_MIN && d <= LONG_MAX > was sufficient to determine whether converting 'd' to 'long' would > yield a 'long' with the same value as 'd'. > > Now I realize that this is wrong. In particular, take a look at the > following program: > > #include <limits.h> > #include <stdio.h> > > int main (void) > { > long i = LONG_MAX; > double d = i; > long j = d; > printf ("%ld, %f, %ld\n", i, d, j); > return 0; > } > > On my system, this prints: > > 9223372036854775807, 9223372036854775808.000000, -9223372036854775808 > > In other words, LONG_MAX gets rounded up to 2**63 when it's converted to > 'double', which makes sense because 'double' only has 53 bits of precision, > but this also means that 'd <= LONG_MAX' and still doesn't fit in 'long', as > one > can see from it getting converted to a wrong answer (-2**63 instead of > 2**63) when converted back to 'long'. And of course any answer is OK there, > since this out-of-range conversion yields undefined behavior. > > Can anyone suggest a correct way to check whether a 'double' is in the > range of 'long'?
I figured out a solution to the problem I wanted to solve. After thinking about it for a while longer, I realized that what I really wanted was the range of integers that 'double' represents without loss of precision, that is, most commonly,-2**53...2**53. And then I wanted the intersection of this range with the range of 'long'. Typically that's going to be -2**53...2**53 also, of course. I came up with the following. Feedback welcomed! #include <limits.h> #include <stdio.h> #include <float.h> #include <math.h> /* Maximum positive integer 'double' represented with no loss of precision (that is, with unit precision). The maximum negative integer with this property is -DBL_UNIT_MAX. */ #if DBL_MANT_DIG == 53 /* 64-bit double */ #define DBL_UNIT_MAX 9007199254740992.0 #elif DBL_MANT_DIG == 64 /* 80-bit double */ #define DBL_UNIT_MAX 18446744073709551616.0 #elif DBL_MANT_DIG == 113 /* 128-bit double */ #define DBL_UNIT_MAX 10384593717069655257060992658440192.0 #else #error "Please define DBL_UNIT_MAX for your system (as 2**DBL_MANT_DIG)." #endif /* Intersection of ranges [LONG_MIN,LONG_MAX] and [-DBL_UNIT_MAX,DBL_UNIT_MAX], as a range of 'long's. This range is the (largest contiguous) set of integer values that can be safely converted between 'long' and 'double' without loss of precision. */ #if DBL_MANT_DIG < LONG_WIDTH - 1 #define DBL_UNIT_LONG_MIN ((long) -DBL_UNIT_MAX) #define DBL_UNIT_LONG_MAX ((long) DBL_UNIT_MAX) #else #define DBL_UNIT_LONG_MIN LONG_MIN #define DBL_UNIT_LONG_MAX LONG_MAX #endif int main (void) { long i = DBL_UNIT_LONG_MAX; double d = i; long j = d; printf ("%ld, %f, %ld\n", i, d, j); printf ("%f\n", DBL_UNIT_MAX); printf ("%d, %d\n", DBL_MANT_DIG, LONG_WIDTH); printf ("%ld, %ld\n", DBL_UNIT_LONG_MIN, DBL_UNIT_LONG_MAX); printf ("%ld, %ld\n", LONG_MIN, LONG_MAX); return 0; }