Portable custom integer width definitions

J

James Harris

1. Is there a type for a double-width integer (or a simple way to derive one)?

I mean that if ints are N bytes then an integer of 2N bytes should be generated.

I am writing code that I want to compile to 16-, 32- and 64-bit architectures. On each of them the int will be the correct size but I was also looking for a way to define a double-width integer that would be twice as wide as an int.

If I use type "long" my 16-bit C compiler generates a 32-bit integer but gcc requires "long long" to generate the 64-bit integer that is required on a 32-bit target. The 16-bit compiler doesn't recognise "long long".

2. Even better than simply doubling the width in all cases is there a clean and portable way to do the following:

For a 16-bit target generate a 32-bit integer.
For a 32-bit or 64-bit target generate a 64-bit integer.

Finally, what names would you use for the resulting wide integer types?

James
 
N

Noob

James said:
1. Is there a type for a double-width integer (or a simple way to derive one)?

I mean that if ints are N bytes then an integer of 2N bytes should be generated.

I am writing code that I want to compile to 16-, 32- and 64-bit architectures. On each of them the int will be the correct size but I was also looking for a way to define a double-width integer that would be twice as wide as an int.

If I use type "long" my 16-bit C compiler generates a 32-bit integer but gcc requires "long long" to generate the 64-bit integer that is required on a 32-bit target. The 16-bit compiler doesn't recognise "long long".

2. Even better than simply doubling the width in all cases is there a clean and portable way to do the following:

For a 16-bit target generate a 32-bit integer.
For a 32-bit or 64-bit target generate a 64-bit integer.

Finally, what names would you use for the resulting wide integer types?

Does your compiler provide stdint.h?

http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdint.h.html
 
E

Eric Sosman

1. Is there a type for a double-width integer (or a simple way to derive one)?

I mean that if ints are N bytes then an integer of 2N bytes should be generated.

I am writing code that I want to compile to 16-, 32- and 64-bit architectures. On each of them the int will be the correct size but I was also looking for a way to define a double-width integer that would be twice as wide as an int.

If I use type "long" my 16-bit C compiler generates a 32-bit integer but gcc requires "long long" to generate the 64-bit integer that is required on a 32-bit target. The 16-bit compiler doesn't recognise "long long".

You could check the upper limits of likely candidates:

#include <limits.h>
#if LONG_MAX / INT_MAX / 2 == INT_MAX
typedef long WideInt;
#elif LLONG_MAX / INT_MAX / 2 == INT_MAX
typedef long long WideInt;
#else
#error "No viable candidate"
#endif

Unfortunately, this requires enumerating the candidates -- easy in
C90, but the set of integer types became open-ended in C99. (Also,
you should double-check my arithmetic: It'll behave correctly if
either long or long long is the right choice, but I'm not sure about
weird cases, like 18-bit int and 45-bit long. Note that a system
without long long won't define LLONG_MAX, so the second test is
harmless.)
2. Even better than simply doubling the width in all cases is there a clean and portable way to do the following:

For a 16-bit target generate a 32-bit integer.
For a 32-bit or 64-bit target generate a 64-bit integer.

With a C99 or C11 implementation you could use the <stdint.h>
header and specify the desired bit widths directly, without all
this fooling around. If you could live with the base type being
int16_t instead of int, you could just use int16_t and int32_t
and avoid all the running around. If the base type actually needs
to be good old unspecified int it gets messier, but you could march
through likely candidates as above:

#include <limits.h>
#include <stdint.h>
#if INT_MAX == INT16_MAX && defined(INT32_MAX)
typedef int32_t WideInt;
#elif INT_MAX == INT32_MAX && defined(INT64_MAX)
typedef int64_t WideInt;
#else
#error "No viable candidate"
#endif
Finally, what names would you use for the resulting wide integer types?

"Sabrina".
 
N

Noob

James said:
The 32-bit compiler does. It seems that the 16-bit one does not. I
did notice that both define types such as __u32. Not sure how
portable/standard such names are, though.

Were you thinking of referring to those in a #if in some way?

I was thinking of using exact-width types, such as
int8_t, int16_t, int32_t, int64_t
 
J

James Harris

You could check the upper limits of likely candidates:

#include <limits.h>
#if LONG_MAX / INT_MAX / 2 == INT_MAX
typedef long WideInt;
#elif LLONG_MAX / INT_MAX / 2 == INT_MAX
typedef long long WideInt;
#else
#error "No viable candidate"
#endif

Unfortunately, this requires enumerating the candidates -- easy in
C90, but the set of integer types became open-ended in C99. (Also,
you should double-check my arithmetic: It'll behave correctly if
either long or long long is the right choice, but I'm not sure about
weird cases, like 18-bit int and 45-bit long. Note that a system
without long long won't define LLONG_MAX, so the second test is
harmless.)

Thanks. Re. the arithmetic I presume it uses integer maths and the results have limited range so I did adapt the expressions slightly. I found it worked mathematically and in code if I used

#if LONG_MAX / INT_MAX / INT_MAX == 2

and

#elif LLONG_MAX / INT_MAX / INT_MAX == 2

I take it from your comment that if LLONG_MAX is undefined then the second test is defined to return false. It seems to evaluate that way on at least one of the compilers I am using.
With a C99 or C11 implementation you could use the <stdint.h>
header and specify the desired bit widths directly, without all
this fooling around. If you could live with the base type being
int16_t instead of int, you could just use int16_t and int32_t
and avoid all the running around.

In some instances the base type needs to be int16, int32 or int64.
If the base type actually needs
to be good old unspecified int it gets messier, but you could march
through likely candidates as above:

#include <limits.h>
#include <stdint.h>
#if INT_MAX == INT16_MAX && defined(INT32_MAX)
typedef int32_t WideInt;
#elif INT_MAX == INT32_MAX && defined(INT64_MAX)
typedef int64_t WideInt;
#else
#error "No viable candidate"
#endif

Thanks again. The older 16-bit compiler doesn't have those types but I'll try to adapt the preprocessor code to suit.
"Sabrina".

If you mean this one, good choice!

http://images4.fanpop.com/image/pho...Duncan-kate-jackson-fans-21742785-404-600.jpg

James
 
J

James Kuyper

....
Thanks. Re. the arithmetic I presume it uses integer maths and the results have limited range so I did adapt the expressions slightly. I found it worked mathematically and in code if I used

To be precise, the expression is evaluated as an intmax_t expression;
the relevant "limited" range is from INTMAX_MIN to INTMAX_MAX.
#if LONG_MAX / INT_MAX / INT_MAX == 2

and

#elif LLONG_MAX / INT_MAX / INT_MAX == 2

I take it from your comment that if LLONG_MAX is undefined then the second test is defined to return false. It seems to evaluate that way on at least one of the compilers I am using.

He's relying upon the fact that undefined identifiers are treated as
having a value of 0 when evaluated in the condition of a #if or #elif.

You'll have to define what you mean by "N-bit" target. One plausible
definition: the target is 16 bit if UINT_MAX == 0xFFFF, 32-bit if
UINT_MAX == 0xFFFFFFFF, and 64-bit UINT_MAX === 0xFFFFFFFFFFFFFFFF. Is
that definition acceptable, or did you have a different one in mind?

You should also think about the case where your code is ported to a
platform which doesn't qualify as any one of those three categories.
It's perfectly legitimate to decide "I don't care what my code does if
ported to such a platform" - but that should be a conscious decision,
not the result of failing to pay attention to that possibility. If you
do care what happens on other platforms, you need to specify what that is.
In some instances the base type needs to be int16, int32 or int64.

Those aren't C standard types. The corresponding <stdint.h> types are
int16_t, int32_t, and int64_t.
Are you certain you need exact width types? Would int_fast16_t or
int_least16_t be acceptable? If so, you should use them, since the exact
width types are optional.
 
E

Eric Sosman

Thanks. Re. the arithmetic I presume it uses integer maths and the results have limited range so I did adapt the expressions slightly. I found it worked mathematically and in code if I used

#if LONG_MAX / INT_MAX / INT_MAX == 2

and

#elif LLONG_MAX / INT_MAX / INT_MAX == 2

I take it from your comment that if LLONG_MAX is undefined then the second test is defined to return false. It seems to evaluate that way on at least one of the compilers I am using.

If you're striving for portability, it'd be a good idea to
acquaint yourself with the rules of the language you're using ...

In this instance, Section 6.10.1 of the Standard addresses all
three of your assumptions or presumptions:

- Paragraph 4: "[...] After all replacements due to macro
expansion and the *defined* unary operator have been
performed, all remaining identifiers [...] are replaced
with the pp-number 0 [...]" If LLONG_MAX is not defined
as a macro it survives macro replacement intact and is then
replaced by zero, leaving `#if 0 / (otherstuff) == 2'.

- Paragraph 1: "The expression that controls conditional
inclusion shall be an integer constant expression except
that [... stuff not altering the integer-ness ...]"

- Paragraph 4 again: "[...] all signed integer types and
all unsigned integer types act as if they have the same
representation as, respectively, the types *intmax_t* and
*uintmax_t* [...]"
In some instances the base type needs to be int16, int32 or int64.

Then just use int{16,32,64}_t. That's if you need exact widths;
if you can tolerate extra bits in any of these you'll get more
flexibility with int_least{16,32,64}_t. For example, if you need
an exact 16-bit type and a 32-or-more-bit type to hold products,
you could use int16_t (exactly 16) and int_least32_t (32 or more).
Thanks again. The older 16-bit compiler doesn't have those types but I'll try to adapt the preprocessor code to suit.

Various people have written <stdint.h> fill-ins for pre-C99
compilers, and you may be able to find one you can borrow. If not,
for what you need it's straightforward to cobble something together
using the values in <limits.h>, e.g.

#include <limits.h>
#if INT_MAX == 0x7fff
typedef int int16_t;
#elif (INT_MAX >> 16) == 0x7fff /* avoid large constants */
typedef int int32_t;
#...
 
J

James Harris

To be precise, the expression is evaluated as an intmax_t expression;
the relevant "limited" range is from INTMAX_MIN to INTMAX_MAX.
OK.


He's relying upon the fact that undefined identifiers are treated as
having a value of 0 when evaluated in the condition of a #if or #elif.
OK.


You'll have to define what you mean by "N-bit" target. One plausible
definition: the target is 16 bit if UINT_MAX == 0xFFFF, 32-bit if
UINT_MAX == 0xFFFFFFFF, and 64-bit UINT_MAX === 0xFFFFFFFFFFFFFFFF. Is
that definition acceptable, or did you have a different one in mind?

Here's what I came up with derived from Eric's advice (errors my own). The lower piece of code is to represent manageable parts of a 64-bit increasingcounter called the TSC. Don't worry about the details but I've included them here as I know it can be frustrating when people don't post enough info.

/* Define double-width integer types */
#if LONG_MAX / INT_MAX / INT_MAX == 2
typedef long intd_t;
typedef unsigned long uintd_t;
#elif LLONG_MAX / INT_MAX / INT_MAX == 2
typedef long long intd_t;
typedef unsigned long long uintd_t;
#else
#error "No viable candidate for intd_t and uintd_t"
#endif

/* Define types for the Time Stamp Counter */
#if INT_MAX == 32767
typedef unsigned int tsc_low_t; /* low 16 bits */
typedef uintd_t tsc_main_t; /* low 32 bits */
#elif INT_MAX == 2147483647
typedef unsigned int tsc_low_t; /* low 32 bits */
typedef uintd_t tsc_main_t; /* full 64 bits */
#elif INT_MAX == 9223372036854775807
typedef unsigned int tsc_low_t; /* full 64 bits */
typedef unsigned int tsc_main_t; /* full 64 bits */
#else
#error "Failed to define types for the TSC"
#endif

Your use of UINT_MAX may be clearer than my use of INT_MAX. I might change my code to use UINT_MAX instead. Given that the preprocessor accepts shiftsit might be possible to code those constants as (1 << shift_amount) - 1 but I would be wary of overflow.
You should also think about the case where your code is ported to a
platform which doesn't qualify as any one of those three categories.
It's perfectly legitimate to decide "I don't care what my code does if
ported to such a platform" - but that should be a conscious decision,
not the result of failing to pay attention to that possibility. If you
do care what happens on other platforms, you need to specify what that is..

Agreed. At least for the TSC the #error clause should suffice if it will report the error and abort the compilation. I have no need to support unusualinteger widths and only need them to be powers of 2 in the range 16 to 64.Given that, I suppose I could change the double-width integer definition but it seems good as it is.
Those aren't C standard types. The corresponding <stdint.h> types are
int16_t, int32_t, and int64_t.
Are you certain you need exact width types? Would int_fast16_t or
int_least16_t be acceptable? If so, you should use them, since the exact
width types are optional.

I will need some other integer types but in the context of the initial postin this thread they need to be exact widths as they will interface with assembly code.

There is no stdint.h included with my 16-bit compiler so I cannot use thosetypes (unless I define them myself - which I might have to do for other code).

James
 
J

James Kuyper

Here's what I came up with derived from Eric's advice (errors my own). The lower piece of code is to represent manageable parts of a 64-bit increasing counter called the TSC. Don't worry about the details but I've included them here as I know it can be frustrating when people don't post enough info. ....
/* Define types for the Time Stamp Counter */
#if INT_MAX == 32767
typedef unsigned int tsc_low_t; /* low 16 bits */
typedef uintd_t tsc_main_t; /* low 32 bits */
#elif INT_MAX == 2147483647
typedef unsigned int tsc_low_t; /* low 32 bits */
typedef uintd_t tsc_main_t; /* full 64 bits */
#elif INT_MAX == 9223372036854775807
typedef unsigned int tsc_low_t; /* full 64 bits */
typedef unsigned int tsc_main_t; /* full 64 bits */
#else
#error "Failed to define types for the TSC"
#endif

Your use of UINT_MAX may be clearer than my use of INT_MAX. I might change my code to use UINT_MAX instead. Given that the preprocessor accepts shifts it might be possible to code those constants as (1 << shift_amount) - 1 but I would be wary of overflow.

I think my use of hexadecimal constants instead of decimal ones makes it
much easier to be sure that the values of those constants are correct
(though there's still the possibility that I miscounted the 'F's).
To avoid the overflow problem, use 1ULL rather than 1, but that only
works for C99 or later.
 
M

Malcolm McLean

2. Even better than simply doubling the width in all cases is there a
clean and portable way to do the following:

For a 16-bit target generate a 32-bit integer.
For a 32-bit or 64-bit target generate a 64-bit integer.

Finally, what names would you use for the resulting wide integer types?
The real answer is to write the code in such a way that it doesn't rely
on any particular integer width, other than an integer being able to index
an arbitrary array. Whilst this is always possible, there are a few problems
where it's too fiddly and inefficient, for example if you need all the bits
of c = a * b.

In practice CHAR_BIT will always be 8, short will be 8, 16 or 32 bits, long
will be 16, 32 or 64 bits, and long long will be 32, 64 or 128 bits. On
a 32 bit system, short will be 16 and long will be 32 or 64. On a 64 bit
system short will be 16 or 32, long will be 32 or 64, and long long will
be 64 or 128. So there aren't that many possibilities, if you can accept
the compile breaking on a deliberately perverse or odd system.
 
J

Jorgen Grahn

The real answer is to write the code in such a way that it doesn't rely
on any particular integer width, other than an integer being able to index
an arbitrary array. Whilst this is always possible, there are a few problems
where it's too fiddly and inefficient, for example if you need all the bits
of c = a * b.

+1. The question "what are you really trying to do?" seems
appropriate. Some requirement hides behind the "I want an portable
integer exactly twice as wide as int" statement, but we can only guess
what it is. "int * int without overflow" was my guess, too.

/Jorgen
 
K

Keith Thompson

James Kuyper said:
You'll have to define what you mean by "N-bit" target. One plausible
definition: the target is 16 bit if UINT_MAX == 0xFFFF, 32-bit if
UINT_MAX == 0xFFFFFFFF, and 64-bit UINT_MAX === 0xFFFFFFFFFFFFFFFF. Is
that definition acceptable, or did you have a different one in mind?
[...]

Most systems that are generally thought of as "64-bit" have 32-bit int
(including the x86_64 system I'm typing this on).

Pointer size tends to be a more reliable indicator of "bitness", though
8-bit systems almost always have pointers bigger than 8 bits. Register
size is usually a better indication, but that tends not to be accessible
from C.

Part of the problem is that there's no good definition of what a 32-bit
or 64-bit system is.
 
K

Keith Thompson

James Harris said:
There is no stdint.h included with my 16-bit compiler so I cannot use
those types (unless I define them myself - which I might have to do
for other code).

Doug Gwyn, a member of the ISO C standard committee, created some C99
(then C9x) porting supplements, including an implementation of
<stdint.h> for pre-C99 compilers. It's public domain, so you can use it
any way you like.

http://www.lysator.liu.se/c/q8/
 
K

Keith Thompson

Malcolm McLean said:
In practice CHAR_BIT will always be 8, short will be 8, 16 or 32 bits, long
will be 16, 32 or 64 bits, and long long will be 32, 64 or 128 bits. On
a 32 bit system, short will be 16 and long will be 32 or 64. On a 64 bit
system short will be 16 or 32, long will be 32 or 64, and long long will
be 64 or 128. So there aren't that many possibilities, if you can accept
the compile breaking on a deliberately perverse or odd system.

Exceptions to CHAR_BIT==8 are admittedly rare; compilers for DSPs
(digital signal processors) are the most commonly cited examples.

short and int are both guaranteed to be at least 16 bits, long is
guaranteed to be at least 32 bits, and long long is guaranteed to be
at least 64 bits. The sizes are guaranteed to be non-decreasing
across the sequence signed char, short, int, long, long long.
(Actually the *ranges* are guaranteed to be non-decreasing, which
is the same thing in the absence of padding bits, which are rare.)

In practice, I've only seen short bigger than 16 bits on Cray
systems, and I've never seen long long with a size other than exactly
64 bits. (gcc supports 128-bit integers on some platforms, but it
still makes both long long and uintmax_t 64 bits.) There's some
pressure to provide predefined types of size 8, 16, 32, and 64 bits;
making short 32 bits with CHAR_BIT==8 means there is no 16-bit
integer type, which is inconvenient. (That could be worked around
with extended integer types, but those aren't commonly supported.)
 
M

Malcolm McLean

short and int are both guaranteed to be at least 16 bits, long is
guaranteed to be at least 32 bits, and long long is guaranteed to be
at least 64 bits.
You can't rely on that. Sometimes int is 8 bits, and long 16. Presumably short would be 8 bits, but it's quite useless type on such a system.
 
N

Noob

Malcolm said:
You can't rely on that.

By definition,

you can rely on INT_MAX and -INT_MIN being *AT LEAST* 0x7fff
you can rely on LONG_MAX and -LONG_MIN being *AT LEAST* 0x7fffffff
you can rely on LLONG_MAX and -LLONG_MIN being *AT LEAST* 0x7fffffffffffffff
Sometimes int is 8 bits, and long 16.

(Not on an implementation of C.)
Please explain how you'd stuff 65535 values within 8 bits.
 
J

James Kuyper

You can't rely on that. Sometimes int is 8 bits, and long 16. Presumably short would be 8 bits, but it's quite useless type on such a system.

It's quite true that the C standard guarantees what he said. The systems
you describe don't conform to that standard.
Once you expand the topic to include things that don't conform to the C
standard, the topic becomes too fuzzy to be worth talking about. Some
things that don't conform don't even have "int", they use "integer"
instead. Some things that don't conform don't even have data types: for
instance, your typical baseball is something doesn't conform to the C
standard.
 
M

Malcolm McLean

It's quite true that the C standard guarantees what he said. The systems
you describe don't conform to that standard.

Once you expand the topic to include things that don't conform to the C
standard, the topic becomes too fuzzy to be worth talking about. Some
things that don't conform don't even have "int", they use "integer"
instead. Some things that don't conform don't even have data types: for
instance, your typical baseball is something doesn't conform to the C
standard.
I'd disagree. A language can be nearly conforming, and still what anyone
would call C. Obviously as it gets steadily further from standard C
and from K and R C, the issue will arise, "is it still really C or is
it now effectively another language?". But compilers for tiny embedded
system that think it's more important that int fit in a register than
that it hold values of up 32767 aren't anywhere near that boundary.
 
K

Keith Thompson

Malcolm McLean said:
You can't rely on that. Sometimes int is 8 bits, and long
16. Presumably short would be 8 bits, but it's quite useless type on
such a system.

Yes, you can rely on that if you're programming in C.

A compiler with 8-bit int or 16-bit long is not a C compiler
(though it may be a compiler for a language resembling C).
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top