Integer multiplication truncation

M

Mamluk Caliph

The following code executes as I would expect on gcc:

#include <inttypes.h>
#include <stdio.h>

int main(int argc, char **argv){
uint16_t apa = 10000;
uint16_t kaka = 7000;
uint32_t mazarin;

mazarin = apa*kaka;
printf("%lu \n",mazarin);
return 0;
}

I.e. it will print out the value 70000000. However, when I compile
this another compiler the result is truncated and I have to modify the
code to the following to make it run:

#include <inttypes.h>
#include <stdio.h>

int main(int argc, char **argv){
uint16_t apa = 10000;
uint16_t kaka = 7000;
uint32_t mazarin;

mazarin = (uint32_t)apa*kaka;
printf("%lu \n",mazarin);
return 0;
}


Could anybody explain this? Is this due to C99 improvements regarding
integer overflow? Is there any predefined macro one could use to
determine this special case?

(Note: The extended types in inttypes.h were added for the second
compiler.)


Regards
/Michael
 
H

Harald van Dijk

The following code executes as I would expect on gcc:

#include <inttypes.h>
#include <stdio.h>

int main(int argc, char **argv){
uint16_t apa = 10000;
uint16_t kaka = 7000;
uint32_t mazarin;

mazarin = apa*kaka;
printf("%lu \n",mazarin);

%lu is used to print a value of type unsigned long. What is the type of
mazarin?
return 0;
}

I.e. it will print out the value 70000000. However, when I compile this
another compiler the result is truncated and I have to modify the code
to the following to make it run:

mazarin = (uint32_t)apa*kaka;

Could anybody explain this?

If uint16_t is a typedef for unsigned int (meaning unsigned int has 16
bits), multiplying one uint16_t by another uint16_t produces a value of
type uint16_t. It doesn't matter that you're going to store it in an
uint32_t. You need to force the multiplication to be done in a way that
it produces a result of type uint32_t (or wider), which is what the cast
does.

It happens to work on your GCC setup, because on that system (I'm going
to make assumptions), int happens to have 32 bits, and multiplying an
uint16_t (unsigned short) by another uint16_t produces a result of type
int, or unsigned int, depending on the system. Since your int has enough
bits to store the result, you get to actually see the result.
Is this due to C99 improvements regarding
integer overflow? Is there any predefined macro one could use to
determine this special case?

There is nothing special happening here, and either result (or others) is
possible in both C90 and C99.
 
M

Mamluk Caliph

I think I'm starting to understand and I can verify that an int type
on my GNU system is indeed 32 bits. I added the following line:

printf("%d %d %d %d
\n",sizeof(apa),sizeof(kaka),sizeof(mazarin),sizeof(int));

which will output: 2 2 4 4

So if I understand this correctly, the result of an integer
multiplication is always an int no matter of the type of the operands?
Or does it mean that both operands are always up-casted to an int
before the operation?

What would happen if one of the operands would be 64 bits and the
values such that the result exceeds MAX_INT?

%lu is used to print a value of type unsigned long. What is the type of
mazarin?

I see your point. I assumed that unsigned long would be larger than an
int, i.e. 32 bit's. On my GNU system a long actually is 32 bits - but
so on the other hand is int.

I added yet the following line:
printf("%d %d %d %d
\n",sizeof(char),sizeof(int),sizeof(long),sizeof(long long));
which prints out: 1 4 4 8

I.e. mazarin (uint32_t) could be either unsigned int or unsigned long.
I don't know if it matters which since both seems to be of the same
size on my system.

By the way, I tried defining my own types and thought I could do
something as follows:

#if sizeof(int)==4
typedef int myInt32_t;
#endif

This results in a compilation error `missing binary operator before
token "("`, why is that? I thought sizof was just a macro.
 
T

Thad Smith

Mamluk said:
I think I'm starting to understand and I can verify that an int type
on my GNU system is indeed 32 bits. I added the following line:

printf("%d %d %d %d
\n",sizeof(apa),sizeof(kaka),sizeof(mazarin),sizeof(int));

which will output: 2 2 4 4

So if I understand this correctly, the result of an integer
multiplication is always an int no matter of the type of the operands?
Or does it mean that both operands are always up-casted to an int
before the operation?

The type of the product is the type of the promoted operands. First, the
individual operands undergo default integer promotion, which would make
each at least the size of an int. If either operand has greater rank than
int, the other operand is promoted to that type and the result is also that
type.
What would happen if one of the operands would be 64 bits and the
values such that the result exceeds MAX_INT?

If the product of integer types is greater than can be expressed in the
resulting type, the value of the result depends on whether the resulting
type is signed or unsigned. If unsigned, the lower bits of the product are
kept. If the resulting type is signed, the result is undefined by the C
Standard.
 
K

Keith Thompson

Mamluk Caliph said:
The following code executes as I would expect on gcc:

#include <inttypes.h>
#include <stdio.h>

int main(int argc, char **argv){
uint16_t apa = 10000;
uint16_t kaka = 7000;
uint32_t mazarin;

mazarin = apa*kaka;
printf("%lu \n",mazarin);
return 0;
}

I.e. it will print out the value 70000000. However, when I compile
this another compiler the result is truncated and I have to modify the
code to the following to make it run:
[snip]

The types uint16_t and uint32_t are declared in <stdint.h>, not in
<inttypes.h>. It happens that <inttypes.h> includes <stdint.h>, which
is why your program compiles, but the purpose of <inttypes.h> is to
define format conversions for use with the *printf and *scanf
functions.

If you just want the types, you should include <stdint.h> rather than
<inttypes.h>.

But the macros in <inttypes.h> are one possible solution to your
problem. The trouble, IMHO, is that they're ugly. For example, your
printf call would be:

printf("%" PRIu32 "\n", mazarin);

Another solution is to convert the value to a type for which you know
the format. Since unsigned long is guaranteed to be a least 32 bits,
you can safely use "%lu" *if* you explicitly convert the value:

printf("%lu\n", (unsigned long)mazarin);

For wider types you might need to use "%zd" or "%zu" with intmax_t or
uintmax_t, defined in <stdint.h> -- but only if your printf
implementation supports them (not all do).
 
K

Keith Thompson

Mamluk Caliph said:
By the way, I tried defining my own types and thought I could do
something as follows:

#if sizeof(int)==4
typedef int myInt32_t;
#endif

This results in a compilation error `missing binary operator before
token "("`, why is that? I thought sizof was just a macro.
[...]

No, sizeof is a built-in unary operator. (Its symbol happens to be a
keyword rather than a punctuation symbol.) The preprocessor doesn't
the "sizeof" or "int" keywords, so you can't use "sizeof(int)" in a
"#if" directive.

You can use the macros defined in <limits.h> in preprocessor
directives, though, and since these specify the ranges that the types
can represent rather than the sizes of their representations, they
might be more appropriate.
 
H

Harald van Dijk

The types uint16_t and uint32_t are declared in <stdint.h>, not in
<inttypes.h>. It happens that <inttypes.h> includes <stdint.h>, which
is why your program compiles, but the purpose of <inttypes.h> is to
define format conversions for use with the *printf and *scanf functions.

No, the purpose of <inttypes.h> is to define format specifiers for the
*printf and *scanf functions, as well as to provide the typedefs that
If you just want the types, you should include <stdint.h> rather than
<inttypes.h>.

<stdint.h> is a new invention in C99 intended to provide a subset of the
features of <inttypes.h> that can be required by freestanding
implementations (possibly lacking standard I/O). It incidentally happens
to have a possible benefit even for hosted implementations, but that
doesn't mean there's anything wrong with using <inttypes.h>.
 
K

Keith Thompson

Harald van Dijk said:
No, the purpose of <inttypes.h> is to define format specifiers for the
*printf and *scanf functions, as well as to provide the typedefs that


<stdint.h> is a new invention in C99 intended to provide a subset of the
features of <inttypes.h> that can be required by freestanding
implementations (possibly lacking standard I/O). It incidentally happens
to have a possible benefit even for hosted implementations, but that
doesn't mean there's anything wrong with using <inttypes.h>.

Interesting, I didn't know the historical context. I had assumed that
both <inttypes.h> and <stdint.h> were invented for C99 (certainly
neither existed in C89, C90, or C95). I suppose that explains the
seemingly counterintuitive name for <inttypes.h>. I had never paid
much attention to that header before; I just noticed that, in addition
to the macros for format specifiers, it also defines several functions
that operate on intmax_t and uintmax_t, corresponding to some
previously existing functions for the [un]signed [long] int types.

But regardless of the origins of the two headers, they were introduced
to standard C simultaneously, and <inttypes.h>'s history as a
non-standard header doesn't influence my decision whether to use it or
not. If I'm just going to use the typedefs declared in <stdint.h>,
I'd still rather just use <stdint.h> and avoid bringing in all the
other stuff. The formatting macros are nice, I suppose, but I
personally find them unwieldy, and for most purposes I'd rather just
convert to some type for which there's a built-in format string.
YMMV, of course.
 
M

Mamluk Caliph

No, the purpose of <inttypes.h> is to define format specifiers for the
*printf and *scanf functions, as well as to provide the typedefs that
these format specifiers are meant to be used with. <inttypes.h> is the
historical header that C99 adopted and extended.
<stdint.h> is a new invention in C99 intended to provide a subset of the
features of <inttypes.h> that can be required by freestanding
implementations (possibly lacking standard I/O). It incidentally happens
to have a possible benefit even for hosted implementations, but that
doesn't mean there's anything wrong with using <inttypes.h>.

Interesting, I didn't know the historical context. I had assumed that
both <inttypes.h> and <stdint.h> were invented for C99 (certainly
neither existed in C89, C90, or C95). I suppose that explains the
seemingly counterintuitive name for <inttypes.h>. I had never paid
much attention to that header before; I just noticed that, in addition
to the macros for format specifiers, it also defines several functions
that operate on intmax_t and uintmax_t, corresponding to some
previously existing functions for the [un]signed [long] int types.

But regardless of the origins of the two headers, they were introduced
to standard C simultaneously, and <inttypes.h>'s history as a
non-standard header doesn't influence my decision whether to use it or
not. If I'm just going to use the typedefs declared in <stdint.h>,
I'd still rather just use <stdint.h> and avoid bringing in all the
other stuff. The formatting macros are nice, I suppose, but I
personally find them unwieldy, and for most purposes I'd rather just
convert to some type for which there's a built-in format string.
YMMV, of course.

--
Keith Thompson (The_Other_Keith) <[email protected]>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Thanks Keith, Harald and Thad. Your inputs have been most helpful in
helping me understanding this problem (and also possibly avoid a few
future bugs).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top