Float overflowed but not when a printf argument in C? - c

Why is it that when my overflow calculation is an argument of the printf() function,the float does not overflow, but when the coded calculation is assigned to a separate variable ,float_overflowed, and is not an argument of the printf function I get the expected result of 'inf'?
Why does this happen? What is causing this difference?
The code and results that led me to this question are below.
Here is my code that didn't execute as expected when the calculation is an argument:
float float_overflow;
float_overflow=3.4e38;
printf("This demonstrates floating data type overflow. We should get an \'inf\' value.\n%e*10=%e.\n\n",float_overflow, float_overflow*10); //No overflow?
The result:
This demonstrates floating data type overflow. We should get an 'inf' value.
3.400000e+38*10=3.400000e+39.
And, when the calculation is not an argument:
float float_upperlimit;
float float_overflowed;
float_upperlimit=3.4e38;
float_overflowed=float_upperlimit*10;
printf("This demonstrates floating data type overflow. We should get an \'inf\' value.\n%e*10=%e.\n\n",float_upperlimit, float_overflowed); //for float overflow
and its result:
This demonstrates floating data type overflow. We should get an 'inf' value.
3.400000e+38*10=inf.

Actually the compiler is not constrained to do the arithmetic in float but it might well use double. 5.2.4.2.1 of the current C standard has:
Except for assignment and cast (which remove all extra range and
precision), the values yielded by operators with floating operands and
values subject to the usual arithmetic conversions and of floating
constants are evaluated to a format whose range and precision may be
greater than required by the type. The use of evaluation formats is
characterized by the implementation-defined value of FLT_EVAL_METHOD
So you only know to force the value to be float when you assign it. Since in the context of the printf call (it is a va_arg function) any such argument is needed as double anyhow, there is no conversion taking place in case that FLT_EVAL_METHOD is of value 1, that is all float arithmetic is done in double.

Remember that for the "%e" format (and all other floating-point formating codes), the argument is actually a double. See e.g. the table in this reference.
That means that when you do the calculation "in-line" as the argument you do now actually overflow. But when you do it for the variable, then it's indeed overflowed and that will carry when used in the printf call.

Related

long double in fabs, range and overflow errors

At wiki.sei.cmu.edu, they claim the following code is error-free for out-of-range floating-point errors during assignment; I've narrowed it down to the long double case:
Compliant Solution (Narrowing Conversion)
This compliant solution checks whether the values to be stored can be represented in the new type:
#include <float.h>
#include <math.h>
void func(double d_a, long double big_d) {
double d_b;
// ...
if (big_d != 0.0 &&
(isnan(big_d) ||
isgreater(fabs(big_d), DBL_MAX) ||
isless(fabs(big_d), DBL_MIN))) {
/* Handle error */
} else {
d_b = (double)big_d;
}
}
Unless I'm missing something, the declaration of fabs according to the C99 and C11 standards is double fabs(double x), which means it takes a double, so this code isn't compliant, and instead long double fabsl(long double x) should be used.
Further, I believe isgreater and isless should be declared as taking a long double as their first parameters (since that's what fabsl returns).
#include <stdio.h>
#include <math.h>
int main(void)
{
long double ld = 1.12345e506L;
printf("%lg\n", fabs(ld)); // UB: ld is outside the range of double (~ 1e308)
printf("%Lg\n", fabsl(ld)); // OK
return 0;
}
On my machine, this produces the following output:
inf
1.12345e+506
along with a warning (GCC):
warning: conversion from 'long double' to 'double' may change value [-Wfloat-conversion]
printf("%lg\n", fabs(ld));
^~
Am I therefore correct in saying their code results in undefined behavior?
On p. 211 of the C99 standard there's a footnote that reads:
Particularly on systems with wide expression evaluation, a <math.h> function might pass arguments
and return values in wider format than the synopsis prototype indicates.
and on some systems long double has the exact same value range, representation, etc. as double, but this doesn't mean the code above is portable.
Now I have a related question here, and I'd just like to ask for confirmation (I've read through dozens of questions and answers here, but I'm still a little confused because they often deal with specific examples and specific types, not all of them are sourced, or they're about C++, and I think it'd be a waste of time to ask each of these questions as a separate, "formal" question on Stack Overflow): according to the C99 and C11 standards, there's a difference between overflow, which occurs during an arithmetic operation, and a range error, which occurs when a value is too large to be represented in a given type. I've provided excerpts from the C99 standard that talk about this, and I'd appreciate it if someone could confirm that my interpretation is correct. (I'm aware of the fact that certain implementations define what happens when undefined behavior occurs, e.g. as explained here, but that's not what I'm interested in right now.)
for floating-point types, overflow results in some representation of a "large value" (i.e. as defined by the HUGE_VAL* macro definition as per 7.12.1):
A floating result overflows if the magnitude of the mathematical result is finite but so
large that the mathematical result cannot be represented without extraordinary roundoff
error in an object of the specified type. If a floating result overflows and default rounding
is in effect, or if the mathematical result is an exact infinity (for example log(0.0)),
then the function returns the value of the macro HUGE_VAL, HUGE_VALF, or HUGE_VALL according to the return type, with the same sign as the correct value of the
function;
On my system, HUGE_VAL* is defined as INFINITY cast to the appropriate floating-point type.
So this is completely legal, the value of HUGE_VAL* being implementation-defined or something like that notwithstanding.
for floating-point types, a range error results in undefined behavior (6.3.1.5):
When a double is demoted to float, a long double is demoted to double or
float, or a value being represented in greater precision and range than required by its
semantic type (see 6.3.1.8) is explicitly converted to its semantic type [...]. If the value being converted is outside the range of values that can be represented, the behavior is undefined.

why float-pointing numbers don't overflow to infinity

Let's say we have the following code:
float f = 999999999999*9999999999999999*999999999999; //a large number to make it overflow
so according to the float-pointing rule, the result should be infinity as:
But I checked the result's bit representation , it is not the infinity, it is some else, how come?
None of the values in 999999999999*9999999999999999*999999999999 are floating points, so you aren't doing floating point arithmetic. In fact, even without any extra warning options set, gcc gives me this warning for your code (clang gives a similar warning):
warning: integer overflow in expression of type 'long int' results in '4467987020393345025' [-Woverflow]
Do this instead
float f = 999999999999.0f*9999999999999999*999999999999;
printf("%f\n", f);
output:
inf
Making the first literal a floating point number forces floating point arithmetic, so you get the infinite value you want.

Why does (int) float == float.truncate instead of garbage (How does casting actually work?)

Going on understanding of these datatypes as primitives
(int) char, and (char) int are intepretations of data. (int) c gives the integer value of that character, and (char) 14 gives you back the character encoded by 14.
I've always understood this as being a "memory parse", such that it just takes the value at that position and then applies a type filter to it.
Given that floating points are stored as some version of scientific notation, what is stored in memory should be garbage as an integer. Looking into this utility http://www.h-schmidt.net/FloatConverter/IEEE754.html it appears that the whole number portion is separated.
However, since this is in the higher portion of memory, how does the int cast know to "reformat"? Does the compiler identify that it was a float and apply special handling, or what's going on?
Your understanding of casts is completely wrong. Casts are nothing but explicit requests for a value conversion from one type to another. They do not reinterpret the representation of one type as if it had a different type. The source code:
float f = 42.5;
int x;
x = (int)f;
simply instructs the compiler to produce code that truncates the floating point value of the expression f to an integer and store the result in the object x.
I've always understood this as being a "memory parse", such that it just takes the value at that position and then applies a type filter to it.
That is an incorrect understanding.
The language specifies conversions between the fundamental arithmetic types. Lookup "Usual Arithmetic Conversions" on the web. You will find a lot of links that describe that. For converting a floating point type to an integral type, this is what the C99 Standard has to say:
6.3.1.4 Real floating and integer
1 When a finite value of real floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.
float f = 4.5;
int i = (int); // i is 4
f = -6.3;
i = (int)f; // i is -6

Puzzled about printf output

While using printf with %d as format specifier and giving a float as an argument e.g, 2.345, it prints 1546188227. So, I understand that it may be due to the conversion of single point float precision format to simple decimal format. But when we print 2.000 with the %d as format specifier, then why it prints 0 only ?
Please help.
Format specifier %d can only be used with values of type int (and compatible types). Trying to use %d with float or any other types produces undefined behavior. That's the only explanation that truly applies here. From the language point of view the output you see is essentially random. And you are not guaranteed to get any output at all.
If you are still interested in investigating the specific reason for the output you see (however little sense it makes), you'll have to perform a platform-specific investigation, because the actual behavior depends critically on various implementation details. And you are not even mentioning your platform in your post.
In any case, as a side note, note that it is impossible to pass float values as variadic arguments to variadic functions. float values in such cases are always converted to double and passed as double. So in your case it is double values you are attempting to print. Behavior is still undefined though.
Go here, enter 2.345, click "rounded". Observe 64-bit hex value: 4002C28F5C28F5C3. Observe that 1546188227 is 0x5c28f5c3.
Now repeat for 2.00. Observe that 64-bit hex value is 4000000000000000
P.S. When you say that you give a float argument, what you apparently mean is that you give a double argument.
Here what ISO/IEC 9899:1999 standard $7.19.6 states:
If a conversion specification is invalid, the behavior is undefined.239)
If any argument is not the correct type for the corresponding conversion
specification, the behavior is undefined.
If you're trying to make it print integer values, cast the floats to ints in the call:
printf("ints: %d %d", (int) 2.345, (int) 2.000);
Otherwise, use the floating point format identifier:
printf("floats: %f %f", 2.345, 2.000);
When you use printf with wrong format specifier for the corresponding argument, the result is undefined behavior. It could be anything and may differ from one implementation to another. Only correct use of format specified has defined behavior.
First a small nitpick: The literal 2.345 is actually of the type double, not float, and besides, even a float, such as the literal 2.345f, would be converted to double when used as an argument to a function that takes a variable number of arguments, such as printf.
But what happens here is that the (typically) 64 bits of the double value is sent to printf, and then it interprets (typically) 32 of those bits as an integer value. So it just happens that those bits were zero.
According to the standard, this is what is called undefined behavior: The compiler is allowed to do anything at all.

Problems casting NAN floats to int

Ignoring why I would want to do this, the 754 IEEE fp standard doesn't define the behavior for the following:
float h = NAN;
printf("%x %d\n", (int)h, (int)h);
Gives: 80000000 -2147483648
Basically, regardless of what value of NAN I give, it outputs 80000000 (hex) or -2147483648 (dec). Is there a reason for this and/or is this correct behavior? If so, how come?
The way I'm giving it different values of NaN are here:
How can I manually set the bit value of a float that equates to NaN?
So basically, are there cases where the payload of the NaN affects the output of the cast?
Thanks!
The result of a cast of a floating point number to an integer is undefined/unspecified for values not in the range of the integer variable (±1 for truncation).
Clause 6.3.1.4:
When a finite value of real floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.
If the implementation defines __STDC_IEC_559__, then for conversions from a floating-point type to an integer type other than _BOOL:
if the floating value is infinite or NaN or if the integral part of the floating value exceeds the range of the integer type, then the "invalid" floating-
point exception is raised and the resulting value is unspecified.
(Annex F [normative], point 4.)
If the implementation doesn't define __STDC_IEC_559__, then all bets are off.
There is a reason for this behavior, but it is not something you should usually rely on.
As you note, IEEE-754 does not specify what happens when you convert a floating-point NaN to an integer, except that it should raise an invalid operation exception, which your compiler probably ignores. The C standard says the behavior is undefined, which means not only do you not know what integer result you will get, you do not know what your program will do at all; the standard allows the program to abort or get crazy results or do anything. You probably executed this program on an Intel processor, and your compiler probably did the conversion using one of the built-in instructions. Intel specifies instruction behavior very carefully, and the behavior for converting a floating-point NaN to a 32-bit integer is to return 0x80000000, regardless of the payload of the NaN, which is what you observed.
Because Intel specifies the instruction behavior, you can rely on it if you know the instruction used. However, since the compiler does not provide such guarantees to you, you cannot rely on this instruction being used.
First, a NAN is everything not considered a float number according to the IEEE standard.
So it can be several things. In the compiler I work with there is NAN and -NAN, so it's not about only one value.
Second, every compiler has its isnan set of functions to test for this case, so the programmer doesn't have to deal with the bits himself. To summarize, I don't think peeking at the value makes any difference. You might peek the value to see its IEEE construction, like sign, mantissa and exponent, but, again, each compiler gives its own functions (or better say, library) to deal with it.
I do have more to say about your testing, however.
float h = NAN;
printf("%x %d\n", (int)h, (int)h);
The casting you did trucates the float for converting it to an int. If you want to get the
integer represented by the float, do the following
printf("%x %d\n", *(int *)&h, *(int *)&h);
That is, you take the address of the float, then refer to it as a pointer to int, and eventually take the int value. This way the bit representation is preserved.

Resources