Problems casting a double into an unsigned char - c

Why does casting a double 728.3 to an unsigned char produce zero? 728 is 0x2D8, so shouldn't w be 0xD8 (216)?
int w = (unsigned char)728.3;
int x = (int)728.3;
int y = (int)(unsigned char)728.3;
int z = (unsigned char)(int)728.3;
printf( "%i %i %i %i", w, x, y, z );
// prints 0 728 0 216

From the C standard 6.3.1.4p1:
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.
So, unless you have >=10 bit unsigned char, your code invokes undefined behaviour.
Note that the cast explicitly tells the compiler you know what you are doing, thus suppresses a warning.

Supposing that unsigned char has 8 value bits, as is nearly (but not completely) certain for your implementation, the behavior of converting the double value 728.3 to type unsigned char is undefined, as specified by paragraph 6.3.1.4/1 of the standard:
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.
This applies to both your w and your y. It does not apply to your x, and the rules covering conversions between integer values (i.e. your z) are different.
Basically, then, there is no answer at the C level for why you see the specific results you do, nor for why I see different ones when I run your code. The behavior is undefined; I can be thankful that it did not turn out to be an outpouring of nasal demons.

Related

Negative float values casting to unsigned int on Arm and intel

I am facing the below which is when I run below code
int main()
{
float test = -6.25f;
unsigned int result_1;
result_1= (unsigned int)test ;
printf("test result_1: %x \n", result_1);
return 0;
}
The output on arm is equal 0 and
output on intel is equal 4294967291 .
Do you know how can force intel compiler to make output 0 as arm compiler?
From https://en.cppreference.com/w/c/language/conversion :
Real floating-integer conversions
A finite value of any real floating type can be implicitly converted to any integer type. Except where covered by boolean conversion above, the rules are:
The fractional part is discarded (truncated towards zero).
If the resulting value can be represented by the target type, that value is used
otherwise, the behavior is undefined
Your code does:
float test = -6.25f;
(unsigned int)test;
The type unsigned int is not able to represent the value -6. You can't convert a float with a negative value to unsigned type. Your code has undefined behavior.
Do you know how can force intel compiler to make output 0 as arm compiler?
Check if the value is less than 0.
int result_1 = test < 0 ? 0 : <something else here>;
If your compiler if following ANNEX F addition to the C language, then according to https://port70.net/~nsz/c/c11/n1570.html#F.4
[...] 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 [...]
In which case anyway, the resulting value is unspecified, so it may differ between compilers as you experience.
When converting an float to unsigned, code is subject to:
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.) C17dr § 6.3.1.4 1
To avoid undefined behavior (UB) for that conversion for out-of-range values, code needs to test the float value first. The typical valid range for conversion is -0.999... to 4,294,967,295.999...
#define UINT_MAX_PLUS1_FLT ((UINT_MAX/2 + 1)*2.0f)
unsigned float_to_unsigned(float f) {
// Exclude values that are too small, too big or NAN
if (f > -1.0f && f < UINT_MAX_PLUS1_FLT) {
return (unsigned) f;
}
return 0; // Adjust as desired
}

Why doesn't integer promotion happen in my case?

#include <stdio.h>
void main()
{
unsigned char a = 0xfb;
char b = 0xfb;
printf("case1 : %d, %d", a, b); // case1
char x=90, y=60, z=100, h;
h = x*y/z;
printf("\ncase2 : %d", h); // case2
int m = 32768, n = 65536, o = 2, i;
i = m*n/o;
printf("\ncase3 : %d", i); // case3
}
result
case1 : 251, -5
case2 : 54
case3 : -107341824
In case1, identifier b is compiled as -5, which is not syntax error thought char only accept -128~127 value. So, first question is identifier b is firstly saved as int data type by end of its translation?(When translation is end, b will be saved in char.)
In case2, x, y is promoted as int. So h has right result value. But in case3, m, n aren't promoted as unsigned int(maybe). Identifier I doesn't have ordinary value(2^30).
C99 says that
If an int can represent all values of the original type, the value is converted to an int;
otherwise, it is converted to an unsigned int.
Being based on C99, value of h is natural, but dealing with m*n/o is overflowed. It's not natural because it's opposed to C99. This is my second query.
In case1, identifier b is compiled as -5, which is not syntax error
thought char only accept -128~127 value. So, first question is
identifier b is firstly saved as int data type by end of its
translation?(When translation is end, b will be saved in char.)
In case 1, the initializer for variable b, the constant 0xfb, represents a value of type int with value 251 (decimal). In the C abstract machine model, this value is converted to b's type (char) when the initializer runs, which for a block-scope variable is when execution reaches that declaration (not during translation). If indeed the range of char in your implementation is -128 - 127 then you have signed chars that cannot represent the initializer value, resulting in implementation-defined behavior.
So, again referencing the abstract machine model, nothing is stored in b at the end of translation. It is a block-scope variable, so it does not exist until execution reaches its declaration. The translated program does need somehow to store b's initial value, but C does not specify in what form it should do so. At no time during translation or execution does b contain a value of type int, however.
When the arguments to the printf call are evaluated, b's value is read and then converted to int (because it is a variadic argument). Arguably, then, it is ok to use a %d field to print it, but if you want to be certain to print the pre-conversion value then you should really use %hhd instead (though in your case, that will almost certainly print the same result).
In case2, x, y is promoted as int. So h has right result value.
More specifically, in case 2, the values of x, y, and z are promoted to int during evaluation of the expression x*y/z, and each operation produces an int result. The multiplication does not overflow the chosen type, int, and the overall result is in the range of type char, so the conversion to char applied upon assignment to h is unremarkable.
But in
case3, m, n aren't promoted as unsigned int(maybe). Identifier I
doesn't have ordinary value(2^30).
In case 3, m, n, and o already have type int, so they are not promoted, and the arithmetic expressions compute results of the same type (int). The result of sub-expression m*n is not in the range of type int, so undefined behavior ensues, pursuant to paragraph 6.5/5 of the Standard:
If an exceptional condition occurs during the evaluation of an
expression (that is, if the result is not mathematically defined or
not in the range of representable values for its type), the behavior
is undefined.
It is true that
C99 [and C11] says that
If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int.
, but that is irrelevant here. It is part of the description of the of the "integer promotions", which apply to the operands of an expression, based on their types.
Being based on C99, value of h is natural, but dealing with m*n/o is
overflowed. It's not natural because it's opposed to C99. This is my
second query.
You seem to expect that the intermediate expression m*n will be evaluated to produce a result of type unsigned int, so that it does not overflow, but this is not supported by the standard. The usual arithmetic conversions, including the integer promotions, are based only on the characteristics of operand types, including their signedness and value ranges. Operators that are subject to the usual arithmetic conversions, including *, use them to determine a common type for all operands and the result.
Your m and n already being the same type, and that type being int, no conversions / promotions apply. The result of the multiplication would also be an int if m and n's values were not such that their product (as an int) is undefined. In fact, however, the operation overflows type int, yielding undefined behavior.
In case 1, initializing a char, which is signed on your platform, with the value 0xfb, greater than CHAR_MAX, is not a syntax error. The effect is implementation defined and unsurprisingly 0xfb is converted to the value -5 which has the same 8-bit pattern. a and b are promoted to int when passed as variable arguments to printf because this is the specified behavior in such a case.
In case 2, x, y and z are promoted to int, because on your platform type int can represent all values of type char. The computation is performed with int arithmetic, producing a result of 54, which is in the range of type char, so can be stored into h without a problem. h is promoted to int when passed to printf as above.
In case 3, m and n are not promoted because they have type int already. The promotion rules apply to each operand, not to the result of the operation as if it was performed with arbitrary precision: m*n is performed with int arithmetic and overflows on your platform where int is presumably 32 bits wide. The behavior is undefined. It so happens that the result is -2147483648 so dividing it by 2 yields -107341824 but this is not guaranteed by the C Standard.
For the computation to be performed accurately, at least m or n must have a larger type or be cast as such: i = (unsigned)m * n / o; would produce 107341824 on your platform, but beware that type int might have fewer than 32 bits. For a portable expression, you would need to use type unsigned long, which is specified as having at least 32 value bits, for both the cast and the type of i.
Finally, main should be defined as int main() or int main(void) or int main(int argc, char *argv[]) or a compatible prototype. void main() is incorrect.
#include <stdio.h>
int main()
{
unsigned char a = 0xfb;
char b = 0xfb;
printf("case1 : %d, %d", a, b); // case1
char x=90, y=60, z=100, h;
h = x*y/z;
printf("\ncase2 : %d", h); // case2
unsigned long long int m = 32768, n = 65536, o = 2,i;
i = m*n/o;
printf("\ncase3 : %llu", i); // case3
}
in this type you can get answer [0, +18,446,744,073,709,551,615] range

How does printf knows if variable passed signed or unsigned

Given the following code snippet:
signed char x = 150;
unsigned char y = 150;
printf("%d %d\n", x, y);
The output is:
-106 150
However, I'm using the same format specifier, for variables that are represented in memory in the same way. How does printf knows whether it's signed or unsigned.
Memory representation in both cases is:
10010110
signed char x = 150; incurs implementation defines behavior as 150 is not in the range of OP's signed char.
The 150 is an int and not fitting in the signed char range undergoes:
the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised. C17dr § 6.3.1.3 3
In this case, x took on the value of 150 - 256.
Good code would not assume this result of -106 and instead not assign to a signed char values outside it range.
Then ...
Commonly, both signed char x and unsigned char y are promoted to int before being passed as arguments to a ... function due to the usual arithmetic conversions. (types in the range of int are promoted to int).
Thus printf("%d %d\n", x, y); is not a problem. printf() receive 2 ints and that matches the "%d" specifiers.
Let's first recognize this issue:
char x = 150;
x never had the value 150 to begin with. That 150 is going to get auto casted to signed char. Hence x, is immediately going to assume the value of -106, since 150 can't be represented within a signed 8-bit value. You might as well have said:
char x = (signed char)150; // same as -106, which is 0x96 in hex
Second, char and short values when passed as variable arguments get auto promoted int. as part of being put on the stack. This includes getting sign-extended.
So when you invoke printf("%d %d\n", x, y);, the compiler will massage it to really be this:
printf("%d %d\n", (int)x, (unsigned int)y);
the following gets put onto the stack:
"%d %d\n"
0xffffff96 (int)x
0x00000096 (unsigned int)y
When printf runs, it parses the formatting string on the stack (%d %d\n) and sees it needs to interpret the next two items on the stack as signed integers. It references 0xffffff96 and 00000096 as value on the stack respectively and renders both to the console in decimal form.
How does printf knows if variable passed signed or unsigned?
The printf function doesn't "know".
You effectively tell it by using either a signed conversion specifier (d or i) or an unsigned conversion specifier (o, u, x or X).
And if you print a signed integer as unsigned or vice versa, printf just does what you told it to do.
I used the same specifier "%d", and it printed different values (the positive one and the negative one"
In your example, you are printing signed and unsigned char values.
signed char x = 150;
The value in x is -106 (8 bits signed) because 150 is greater than the largest value for char. (The char type's range is -128 to +127 with any hardware / C compiler that you are likely to encounter.)
unsigned char y = 150;
The value in y is 150 (8 bits unsigned) as expected.
At the call site. The char value -108 is sign extended to a larger integer type. The unsigned char value 150 is converted without sign extension.
By the time printf is called, the values that are have been passed to it have a different representation.

cast float to unsigned int in C with gcc

I am using gcc to test some simple casts between float to unsigned int.
The following piece of code gives the result 0.
const float maxFloat = 4294967295.0;
unsigned int a = (unsigned int) maxFloat;
printf("%u\n", a);
0 is printed (which I belive is very strange).
On the other hand the following piece of code:
const float maxFloat = 4294967295.0;
unsigned int a = (unsigned int) (signed int) maxFloat;
printf("%u\n", a);
prints 2147483648 which I belive is the correct results.
What happens that I get 2 different results?
If you first do this:
printf("%f\n", maxFloat);
The output you'll get is this:
4294967296.000000
Assuming a float is implemented as an IEEE754 single precision floating point type, the value 4294967295.0 cannot be represented exactly by this type because there's aren't enough bits of precision. The closest value it can store is 4294967296.0.
Assuming an int (and likewise unsigned int) is 32 bits, the value 4294967296.0 is outside the range of both of these types. Converting a floating point type to an integer type when the value cannot be represented in the given integer type invokes undefined behavior.
This is detailed in section 6.3.1.4 of the C standard which dictates conversion from floating point types to integer types:
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.61)
...
61) The remaindering operation performed when a value of integer type
is converted to unsigned type need not be performed when a value of
real floating type is converted to unsigned type. Thus, the range of
portable real floating values is (−1, Utype_MAX+1).
The footnote in the above passage is referencing section 6.3.1.3, which details integer to integer conversions:
1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new
type, it is unchanged.
2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that
can be represented in the new type until the value is in the range of
the new type.
3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an
implementation-defined signal is raised.
The behavior you see in the first code snippet is consistent with an out-of-range conversion to an unsigned type when the value in question is an integer, however because the value being converted has a floating point type it is undefined behavior.
Just because one implementation does this doesn't mean that all will. In fact, gcc gives a different result if you change the optimization settings.
For example, on my machine using gcc 5.4.0, given this code:
float n = 4294967296;
printf("n=%f\n", n);
unsigned int a = (unsigned int) n;
int b = (signed int) n;
unsigned int c = (unsigned int) (signed int) n;
printf("a=%u\n", a);
printf("b=%d\n", b);
printf("c=%u\n", c);
I get the following results with -O0:
n=4294967296.000000
a=0
b=-2147483648
c=2147483648
And this with -O1:
n=4294967296.000000
a=4294967295
b=2147483647
c=2147483647
If on the other hand n is defined as long or long long, you would always get this output:
n=4294967296
a=0
b=0
c=0
The conversion to unsigned is well defined by the C standard as sited above, and the conversion to signed is implementation defined, which gcc defines as follows:
The result of, or the signal raised by, converting an integer to a signed integer type when the value cannot be represented in an object
of that type (C90 6.2.1.2, C99 and C11 6.3.1.3).
For conversion to a type of width N, the value is reduced modulo 2^N
to be within range of the type; no signal is raised.
Assuming IEEE 754 floating point numbers, the number 4294967295.0 can't be stored exactly in a float. It will be stored as 4294967296.0 instead (which is 232).
Further assuming your unsigned int has 32 value bits, this is just by one too large to fit in an unsigned int, so the result of the conversion is undefined according to the C standard -- 0 is a "reasonable" outcome.
In your second case, you have undefined behavior as well, and I have no theory what's happening here on the representation level. Fact is, the number is much too large for a 32 bit signed int (still assuming this is what your machine uses).
From this remark in your question:
prints 2147483648 which I belive is the correct results.
I assume you wanted to see the representation of your float in memory. Casting will convert the value, so that's not the way to see the representation. The following code would do:
int main(void) {
const float maxFloat = 4294967295.0;
unsigned char *floatBytes = &maxFloat;
for (int i=0; i < sizeof maxFloat; ++i)
{
printf("0x%02x ", floatBytes[i]);
}
puts("");
}
online example

storing a big float into an integer (cast and no cast)

What does the standard (are there differences in the standards?) say about assigning a float number out of the range of an integer to this integer?
So what should happen here,
assuming 16 bit short, to keep the number small (USHRT_MAX == 65535)
float f = 100000.0f;
short s = f;
s = (short) f;
unsigned short us = f;
us = (unsigned short) f;
This is undefined behaviour (with no diagnostic required). See C11 6.3.1.4 (earlier standards had similar text):
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.
So, assuming your system has USHRT_MAX as 65535, short s = f; and all subsequent lines cause undefined behaviour.

Resources