0

How to determine w/o conversions that a given floating constant can be represented?

Sample code:

#define FLOATING_CONSTANT1  2147483647.0f
#define FLOATING_CONSTANT2  2147483520.0f

bool b1 = FLOATING_CONSTANT_CAN_BE_REPRESENTED( FLOATING_CONSTANT1 );  // false
bool b2 = FLOATING_CONSTANT_CAN_BE_REPRESENTED( FLOATING_CONSTANT2 );  // true
pmor
  • 5,392
  • 4
  • 17
  • 36
  • Represented in what way? – Ted Lyngmo Aug 18 '21 at 15:54
  • @TedLyngmo I assume he means without the loss inherent to floating point representations. – Eric J. Aug 18 '21 at 15:55
  • 3
    Following the earlier [question](https://stackoverflow.com/questions/68832098/if-float-int-max-is-true-then-why-intfloat-may-trigger-undefined-behavi) if you use `double` then all 32-bit `int` values can be exactly stored. I am getting the feeling this is an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Weather Vane Aug 18 '21 at 15:56
  • Does this answer your question? [Determine if a number can be precisely represented in float/double format](https://stackoverflow.com/questions/28634461/determine-if-a-number-can-be-precisely-represented-in-float-double-format) – Eric J. Aug 18 '21 at 15:57
  • 1
    Hmm. Not sure how safe or portable it is but, using `#define FLOATING_CONSTANT_CAN_BE_REPRESENTED(x) (x == x)` and compiling with clang-cl, I get a warning for your `FLOATING_CONSTANT1` but ***not*** for `FLOATING_CONSTANT2`. This warning: *warning : comparing floating point with == or != is unsafe [-Wfloat-equal]* – Adrian Mole Aug 18 '21 at 16:08
  • 1
    ... clang also gives that warning for `3.3f` (which I know cannot be accurately represented) but not for `2.0f` (which I know can be). – Adrian Mole Aug 18 '21 at 16:13
  • I don't know if, but I doubt that, there's a good way of doing this — but if there is, it's almost certainly going to involve a "conversion" of some sort. The math required, to determine whether a given string of decimal digits can or can't be represented exactly in a given binary floating-point format, is just about guaranteed to be equivalent to the work required to convert it. – Steve Summit Aug 18 '21 at 16:17
  • Please let us know why you're asking. Are you just trying to understand floating-point representations better, or are you writing some code that sees to care? – Steve Summit Aug 18 '21 at 16:19
  • *How to determine w/o conversions that a given floating constant can be represented?* In general, you ***CAN'T***. You can either duplicate the conversion to your platform's floating-point format and see if it's possible without loss of data, or you can just use your platform's native conversions and see if you lost data. Either way, you have to either explicitly or implicitly convert the input string to your platform's floating-point format. – Andrew Henle Aug 18 '21 at 17:12
  • 1
    @WeatherVane "XY problem": you're right, the core question was "how to automatically find a max. representable floating constant, which being converted to integer does not trigger undefined behavior?". And the answer is seems to be `nextafterf(((float)INT_MAX)+1.0f, -INFINITY)`. – pmor Aug 18 '21 at 17:17
  • @AdrianMole To my experience, static analysis tools are capable of issuing warnings "floating constant X cannot be represented exactly". Frama-C is one example. – pmor Aug 18 '21 at 17:21
  • @pmor I see from your comments and some other questions you've asked that you're probably working on Boost or some similar preprocessing toolkit. So although I seem to have been saying (in my comments and answer) that you can't do this and you shouldn't even want to, I have to concede that Boost successfully implements large numbers of things that I wouldn't have thought possible. So, in the spirit of "Those who believe a thing to be impossible should not stand in the way of those who are doing it", I'm going to retract most of my previous comments, and refrain from making any more. :-) – Steve Summit Aug 18 '21 at 17:28
  • But as an example of just how hard and/or complicated and/or subtle the problem can be, here's an example provided to me by Eric Postpischil just the other day: the rather improbable-looking number 1.40129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125e-45 *can* be represented exactly as a `float`. (Note that it has a whopping 105 significant digits, even thought `float` isn't usually good for more than about 7.) – Steve Summit Aug 18 '21 at 17:34
  • @SteveSummit Or 751 significant decimal digits for a small [`double`](https://codereview.stackexchange.com/q/212490/29485). – chux - Reinstate Monica Aug 18 '21 at 20:52
  • "how to automatically find a [max. representable floating constant](https://stackoverflow.com/questions/68835588/how-to-determine-w-o-conversions-that-a-given-floating-constant-can-be-represent/68838973#comment121654607_68835588), which being converted to integer does not trigger undefined behavior?" --> That's easy as a `float` constant: `some_float_constant < (INT_MAX/2 + 1)*2.0f`. Can put that in a `_Static_assert`. – chux - Reinstate Monica Aug 18 '21 at 20:55

3 Answers3

1

This probably isn't the answer you're looking for, and it may seem facetious, but in my own work, I pretty much imagine that I have this macro available to me. It has the handy advantage of working equally well on float, double, and long double operands:

#define fp_is_exact(f) 0

But I confess I very rarely actually write things like

if(fp_is_exact(x))
    do something the easy way;
else
    do something the hard way;

I'm going to have to write the code to do it the hard way, anyway, and the "easy" code gets used so rarely that I might as well get out of the habit of imagining that it might ever be useful for anything.

In all seriousness: assume that floating-point numbers are never exact. Exactness is not what they're for. Just about anything you might ever need to represent as a floating-point variable is not an exactly-known quantity anyway, especially if it's something you measured in the real world like a distance or weight or velocity or something. (My favorite example: How far is it from Los Angeles to New York? In inches?)


Addendum: Climbing down off of my high horse, I have to admit, sometimes it's appropriate to assume that floating-point numbers are exact. Comparisons like if(f == 0) or if(f == 1) or if(f == 3) are perfectly fine in practice. I'd have no hesitation comparing a floating-point variable for exact equality to an integer less than 10 -- or less than 100, if it came to that, and maybe also 0.5. But for just about any other number I might have in mind, or that I might be manipulating in a floating-point variable in a C program, the chance that it's exactly representable as a float or double is just about vanishingly small.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • Does it mean that it is impossible* to determine Inexact at compile time? * As, for example, it is impossible to write in C preprocessor the general purpose emptiness detector like `#define EMPTY(x) ??`. – pmor Aug 18 '21 at 16:18
  • @pmor Wait, you want to determine this *at compile time*? I already knew this was a hard problem, but that's even harder! Yes, I believe it's impossible to determine at compile time, although I could be wrong, and for what it's worth I have no idea what a "general purpose emptiness detector" is. – Steve Summit Aug 18 '21 at 16:21
  • When exactly a compiler converts irrepresentable floating constant (hereafter "FP constant") to representable FP constant? I guess that internally compiler stores FP constants in higher precision and converts them to the target precision when need. If it does it immediately after parsing of the FP constant, then I believe that it is impossible to determine the Inexact at run time. Otherwise, should `float f = FP_CONSTANT` trigger Inexact? Note that some static analysis tools (such as Frama-C) generate Inexact warnings for such scenarios. – pmor Aug 18 '21 at 16:57
  • @pmor Sorry, when I said "impossible at compile time", I meant, "Impossible for a portable C program". Clearly a compiler or other static analysis tool *could* determine this — and indeed Adrian Mole mentioned an example in a comment. – Steve Summit Aug 18 '21 at 17:10
  • About the "general purpose emptiness detector": see [this](https://stackoverflow.com/q/65044384/1778275), [this](https://p99.gforge.inria.fr/p99-html/group__basic__list__operations_ga27a53661a1c739a0f9d1e70f8958e4bc.html) (see _Warning_), and [this](https://lists.boost.org/Archives/boost/2006/02/101287.php) (_BOOST_PP_IS_EMPTY macro is not a general purpose emptiness detector (there is no such thing)_). – pmor Aug 18 '21 at 17:12
1

How to determine w/o conversions that a given floating constant can be represented?

A floating constant accepted by the compiler has type float, double, long double or some extension floating type as far as C semantics are concerned. It is represented.

But I suppose the question is meant to be whether the lexical decimal expression of the constant appearing in the source code corresponds to an exactly representable floating-point value of some chosen floating type. I feel pretty confident in saying that such a determination cannot be made by the preprocessor, because the preprocessor deals in units of (preprocessor) tokens. Although under some circumstances it operates by converting text to numbers, the preprocessor has no accessible mechanism for breaking tokens into smaller pieces.

It is possible, however, to stringify the token of interest at compile time and analyze the resulting string representation at run time. It is also possible in principle to write a standalone tool or adapt an existing standalone preprocessor to perform the kind of test you want.

It is even conceivable that a compiler might offer an option for warning about lexical floating point numbers that cannot be represented exactly, which alternative was considered in another question. (Spoiler: this is not an alternative that compiler writers have historically considered worthwhile to implement.)

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

"w/o conversions" seems to be an unnecessary restrictive requirement, but something to get OP started and a test harness for others.


#define str(s) #s
#define xstr(s) str(s)
#define FLOATING_CONSTANT_CAN_BE_REPRESENTED(fc1) float_const_test(xstr(fc1), fc1)

bool float_const_test(const char *s, float f1) {
  printf("<%s> %g\n", s, f1);
  char *endptr;
  errno = 0;
  double d2 = strtod(s, &endptr);
  if (s == endptr) return false;
  if (strcmp(endptr, "f") && strcmp(endptr, "F")) return false;
  if (f1 != d2) return false;
  // Note a 100% here, the string may have rounded to a double.
  return true;
}

int main(void) {
  puts("Good");
  printf("%d\n", FLOATING_CONSTANT_CAN_BE_REPRESENTED(1.5f));
  printf("%d\n", FLOATING_CONSTANT_CAN_BE_REPRESENTED(0x1.5p0f));
  puts("\nBad");
  printf("%d\n", FLOATING_CONSTANT_CAN_BE_REPRESENTED(1.23f));
  printf("%d\n", FLOATING_CONSTANT_CAN_BE_REPRESENTED(1.23e40f));
}

Output

Good
<1.5f> 1.5
1
<0x1.5p0f> 1.3125
1

Bad
<1.23f> 1.23
0
<1.23e40f> inf
0

Notes:

If the fractional portion of a FP constant does not end in 5 or 0, it cannot be exactly converted to a float/double.

When exact FP constants are needed, the first step is to consider hexadecimal-floating-constant such a 0x1.23CDp12f

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    *If the fractional portion of a FP constant does not end in 5 or 0, it cannot be exactly converted to a `float`/`double`.* And of course, even if it *does* end in 5 or 0, there's a good chance it can't be exactly converted: 0.35, 0.10, ... – Steve Summit Aug 18 '21 at 20:45
  • 1
    @SteveSummit True. I think some tests can be had to include/exclude some code constants, just not all. – chux - Reinstate Monica Aug 18 '21 at 20:45