Preprocessor limitation workarounds

T

Thad Smith

D said:
The problem I have is when MY_MAX_VALUE -- or, some
arithmetic expression which effectively becomes MY_MAX_VALUE
exceeds the capabilities of a 32 bit int (for older compilers).

For example, extend the first example to include:

....
#elif (MY_MAX_VALUE < ULLONG_MAX)
typedef unsigned long long mytype_t;
#else
#error "Unable to represent MY_MAX_VALUE with standard data types"
#endif

If, for example, MY_MAX_VALUE won't fit in a ULONG, then it
is too big for "older" preprocessors to handle predictably.

Syntax:
#if constant-expression

A constant expression may be a floating point expression. Compute or
assign MY_MAX_VALUE as a floating point value:

#define MAX_ROWS 1000
#define MAX_COLS 1000
#define VIDEO_FPS 30
#define MAX_LENGTH_SECS 2000
#define MAX_VIDEO_PIXELS \
(1.*MAX_ROWS*MAX_COLS*VIDEO_FPS*MAX_LENGTH_SECS)
....
#if MAX_VIDEO_PIXELS < ULONG_MAX
....

This doesn't imply linking any fp routines in your application.

I have used floating point constant expressions to determine integer
constants for embedded applications. Cast to an appropriate integer
type, where needed.
 
B

Ben Pfaff

Thad Smith said:
#define MAX_VIDEO_PIXELS \
(1.*MAX_ROWS*MAX_COLS*VIDEO_FPS*MAX_LENGTH_SECS)
...
#if MAX_VIDEO_PIXELS < ULONG_MAX

That's not portable C. If your compiler allows it, it's an
implementation extension. See C99:

6.6 Constant expressions
....
6 An integer constant expression96) shall have integer type
and shall only have operands that are integer constants,
enumeration constants, character constants, sizeof
expressions whose results are integer constants, and
floating constants that are the immediate operands of
casts. Cast operators in an integer constant expression
shall only convert arithmetic types to integer types, except
as part of an operand to the sizeof operator.

6.10.1 Conditional inclusion
Constraints

1 The expression that controls conditional inclusion shall be
an integer constant expression except that: it shall not
contain a cast; ...
 
T

Thad Smith

Ben said:
That's not portable C. If your compiler allows it, it's an
implementation extension. See C99: ....
1 The expression that controls conditional inclusion shall be
an integer constant expression except that: it shall not
contain a cast; ...

Oops! I missed the integer qualification. I thought that was the case,
but saw the syntax and looked up constant expression, failing to note
the textual description.
 
D

D Yuniskis

Thad said:
Syntax:
#if constant-expression

A constant expression may be a floating point expression. Compute or

Huh?? Not in any (standard) C that I've used! (though I
have one family of compilers that will let me do this;
using this feature is a death sentence :< )
assign MY_MAX_VALUE as a floating point value:

#define MAX_ROWS 1000
#define MAX_COLS 1000
#define VIDEO_FPS 30
#define MAX_LENGTH_SECS 2000
#define MAX_VIDEO_PIXELS \
(1.*MAX_ROWS*MAX_COLS*VIDEO_FPS*MAX_LENGTH_SECS)
...
#if MAX_VIDEO_PIXELS < ULONG_MAX
...

This doesn't imply linking any fp routines in your application.

I have used floating point constant expressions to determine integer
constants for embedded applications. Cast to an appropriate integer
type, where needed.

Are you sure you used these expressions in preprocessor conditionals?
And not:

#define XTAL_FREQUENCY (3.2E+7)
 
P

Peter Nilsson

christian.bau said:
Shifting: Shifts up to 15 bit positions are _guaranteed_ to be correct
on _every_ compiler. So lets say you need to store unsigned values up
to 100000, which requires 17 bits:

#if (UCHAR_MAX >> 15) >= 3
  // unsigned char holds values up to 100000
#elif (USHORT_MAX >> 15) >= 3
  // unsigned short holds values up to 100000
#elif (UINT_MAX >> 15) >= 3
  // unsigned int holds values up to 100000
#else
  // unsigned long holds values up to 100000
#endif

#if UCHAR_MAX >= 100000
// unsigned char holds values up to 100000
#elif USHORT_MAX >= 100000
// unsigned short holds values up to 100000
#elif UINT_MAX >= 100000
// unsigned int holds values up to 100000
#else
// unsigned long holds values up to 100000
#endif
Now the one that worries you more. Say checking that all numbers
up to 1000000000000 (12 zeroes) fits. This needs 40 bits. So:

#if ((UCHAR_MAX >> 15) >> 15) >= 1023
  // unsigned char holds values up to 10^12
#elif ((USHORT_MAX >> 15) >> 15) >= 1023
  // unsigned short holds values up to 10^12
#elif ((UINT_MAX >> 15) >> 15) >= 1023
  // unsigned int holds values up to 10^12
#elif ((ULONG_MAX >> 15) >> 15) >= 1023
  // unsigned long holds values up to 10^12
#else
  // unsigned long long holds values up to 10^12
#endif

If we're talking about a nice decimal number, then I'd do...

#if UCHAR_MAX / 1000000 / 1000000 >= 1
// unsigned char holds values up to 10^12
#elif USHORT_MAX / 1000000 / 1000000 >= 1
// unsigned short holds values up to 10^12
#elif UINT_MAX / 1000000 / 1000000 >= 1
// unsigned int holds values up to 10^12
#elif ULONG_MAX / 1000000 / 1000000 >= 1
// unsigned long holds values up to 10^12
#elif ULLONG_MAX / 1000000 / 1000000 >= 1
// unsigned long long exists and holds values up to 10^12
#elif UINT_MAX / 1000000 / 1000000 >= 1
// uint_max_t exists and holds values up to 10^12
#else
// problems!
#endif
 
D

D Yuniskis

Eric said:
No, I didn't miss the limitation. I stated the assumption
that FOO was "an integer constant expression," but your example
is a C90 I.C.E. only if ULONG_MAX is at least 10000000000. (In
C99, UINTMAX_MAX is guaranteed to exceed 10000000000, so you're
home.)

Exactly. But ULLONGs exceed this!

Here's what my BigDecimal headers had to do (PSEUDO-code...
off the top of my head as it has been a while since I
tinkered with this):

#ifndef RADIX
# define RADIX (10)
#endif

#if (RADIX % 10) != 0
# error "RADIX must be an integral power of 10."
#endif

// pick a type to hold a single RADIX "digit"
#if (RADIX - 1) < UCHAR_MAX
typedef unsigned char digit_t;
#elif (RADIX - 1) < USHRT_MAX
typedef unsigned short int digit_t;
#elif (RADIX - 1) < ULONG_MAX
typedef unsigned long int digit_t;
#elif (RADIX - 1) < ULLONG_MAX
typedef unsigned long long int digit_t;
#else
# error "Unable to represent RADIX using standard data types"
#endif

// pick a data type to hold a digit sum
#if (RADIX - 1) + (RADIX - 1) + 1 < UCHAR_MAX
typedef unsigned char sum_t;
#elif ...
....
#else
# error "Unable to represent the sum of two digits"
#endif

// pick a data type to represent a digit product
#if ((RADIX - 1) * (RADIX - 1)) + (RADIX - 2) < UCHAR_MAX
typedef unsigned char product_t;
#elif ...
....
#else
# error "Unable to represent the product of two digits"
#endif

// other similar invariants imposed on the data types ...

Choice of RADIX characterizes the algorithm in space and time.
For a "typical" compiler, I would opt for a RADIX of "10"
for speed and "99999999" for space (efficiency).

[there are other constraints not shown that influence
these choices]

As you can see, the conditions for the "space" choice can't
be handled by the preprocessor even if the compiler supports
long longs!
You can do this if you like, but it's unnecessary. Any
unrecognized tokens in an #if or #elif expression are taken to
have the value zero.

Doing it draws attention to how these cases are handled
(so folks don't have to worry about the nooks and crannies
of the Standard)
Perhaps I'm missing something, but I don't see what you're,
er, missing. Your cross-compiler knows the <limits.h> values
for the execution environment, and can do integer arithmetic in
accordance with those values, even if they're different from the

Yes. But it can't do *all* the arithmetic that I need to do!
(hence the subject line: "Preprocessor limitation workarounds")
values used by the host platform. In a pinch, you can cross-
compile the helper and run *it* on the target platform.

The target may be a little 8 bit MCU with a few KB of
code (in masked ROM). Nothing gets run there except
finished applications. :>

My objection to running an executable crafted *just* to perform
these calculations (those that the preprocessor can't be
guaranteed to perform accurately) is that I now have to write
a piece of code to run on the *host* at "compile time"
(i.e., so that changes to RADIX cause that host tool to
recompute the required header files). That means having
a *native* compiler available (for the host) and rewriting
*that* code while I am writing the *real* (application) code.

I much prefer having a tool that I can write *once* and
then *apply* at compile time as the conditions in my #if's
evolve (withthe code I am writing).

Hence the reason I have been looking at bc(1), dc(1), etc.
I.e., give me some *unconstrained* arithmetic capability
that I can script and "coerce" data/decisions from. Neither
bc nor dc seem to *easily* support this. So, I may have
to hack together something that gives me that functionality
and then merge its results back into my source stream.
 
D

D Yuniskis

Hi Christian,

christian.bau said:
The limits for any signed or unsigned type are always so that _all_
positive values less than 2^n can be represented, and all values >=
2^n cannot be represented, for some n (and we know n >= 8 for unsigned
char, n >= 16 for unsigned short/int, n >= 32 for unsigned long, n >=
64 for unsigned long long). So for example the questions "can T hold
all values up to 800000" and "can T hold all 20 bit values" are

Huh? 0x800000 is a 24 bit number...
equivalent. That could help you to get around preprocessor limits.

Shifting: Shifts up to 15 bit positions are _guaranteed_ to be correct
on _every_ compiler. So lets say you need to store unsigned values up
to 100000, which requires 17 bits:

#if (UCHAR_MAX >> 15) >= 3
// unsigned char holds values up to 100000
#elif (USHORT_MAX >> 15) >= 3
// unsigned short holds values up to 100000
#elif (UINT_MAX >> 15) >= 3
// unsigned int holds values up to 100000
#else
// unsigned long holds values up to 100000
#endif

Great. Now let me replace the "100000" in your commentary
with FOO. (i.e., I want to find the smallest type that
will contain some *arbitrary* value). :-/
Now the one that worries you more. Say checking that all numbers up to
1000000000000 (12 zeroes) fits. This needs 40 bits. So:

#if ((UCHAR_MAX >> 15) >> 15) >= 1023
// unsigned char holds values up to 10^12
#elif ((USHORT_MAX >> 15) >> 15) >= 1023
// unsigned short holds values up to 10^12
#elif ((UINT_MAX >> 15) >> 15) >= 1023
// unsigned int holds values up to 10^12
#elif ((ULONG_MAX >> 15) >> 15) >= 1023
// unsigned long holds values up to 10^12
#else
// unsigned long long holds values up to 10^12
#endif

I must be missing something (?). How is this going to
help me find the smallest type that can contain
3028? And, without rewriting any code, 9937820552?
(i.e., I am looking for a *general* solution not
a specific one)
 
D

D Yuniskis

Keith said:
Ok, but 800000 is a 20 bit number.

Ack, my bad! Given the talk of signed/unsigned I subconciously
assumed we were talking about sign bit in a (hex) value... <:-/
 
E

Eric Sosman

D said:
Eric said:
D said:
[...]
I think you missed one of the points:

#define FOO (10000000000/10000000000)

is an integer constant expression that *evaluates* to something
not exceeding ULONG_MAX (i.e., "1"). But, the preprocessor
can't *evaluate* it (because all of its component expressions
must also satisfy this criteria.

No, I didn't miss the limitation. I stated the assumption
that FOO was "an integer constant expression," but your example
is a C90 I.C.E. only if ULONG_MAX is at least 10000000000. (In
C99, UINTMAX_MAX is guaranteed to exceed 10000000000, so you're
home.)

Exactly. But ULLONGs exceed this!

ULLONG_MAX cannot exceed UINTMAX_MAX, by definition.
Here's what my BigDecimal headers had to do (PSEUDO-code...
off the top of my head as it has been a while since I
tinkered with this):

#ifndef RADIX
# define RADIX (10)
#endif

#if (RADIX % 10) != 0
# error "RADIX must be an integral power of 10."
#endif

Since you've already chosen to waste some space by working
in powers of ten instead of powers of two, I'm not sure why
"most tightly" (from one of your messages elsethread) is so
important to you.
// pick a type to hold a single RADIX "digit"
#if (RADIX - 1) < UCHAR_MAX
typedef unsigned char digit_t;
#elif (RADIX - 1) < USHRT_MAX
typedef unsigned short int digit_t;
#elif (RADIX - 1) < ULONG_MAX
typedef unsigned long int digit_t;
#elif (RADIX - 1) < ULLONG_MAX
typedef unsigned long long int digit_t;
#else
# error "Unable to represent RADIX using standard data types"
#endif

I'm not sure why you use < instead of <= (I mention it
because you've done the same elsethread, and I'm beginning
to think it might not be just a typo).

Also, the #error message is at best misleading, since
your #if ladder does not exhaust all the "standard" integer
types.
// pick a data type to hold a digit sum
#if (RADIX - 1) + (RADIX - 1) + 1 < UCHAR_MAX
typedef unsigned char sum_t;
#elif ...
...
#else
# error "Unable to represent the sum of two digits"
#endif

Do some simple algebra:

#if RADIX - 1 < /*sic*/ UCHAR_MAX - RADIX
// pick a data type to represent a digit product
#if ((RADIX - 1) * (RADIX - 1)) + (RADIX - 2) < UCHAR_MAX
typedef unsigned char product_t;
#elif ...
...
#else
# error "Unable to represent the product of two digits"
#endif

Algebra again.
// other similar invariants imposed on the data types ...

Choice of RADIX characterizes the algorithm in space and time.
For a "typical" compiler, I would opt for a RADIX of "10"
for speed and "99999999" for space (efficiency).

The latter choice would make one of your #error directives
fire. Also, the belief that "smaller RADIX means greater speed"
needs more thought. It is possible that individual arithmetic
operations might be faster in smaller types than in wider types,
but for a given range of numbers you will need more digits and
thus perform more operations. A given value V will have roughly
eight times as many radix-10 digits as radix-100000000 digits.
As you can see, the conditions for the "space" choice can't
be handled by the preprocessor even if the compiler supports
long longs!

The preprocessor *is* powerful enough for at least some of
the calculations you've shown. But no, it's not all-powerful.
That's why several people have suggested you write helper programs
in a more powerful language -- C, for example -- to do what the
preprocessor cannot do, or can do only with difficulty.

But if you won't take advice ... Well, one wonders why you
bother asking for it. Good-bye.
 
D

D Yuniskis

Eric said:
D said:
Eric said:
D Yuniskis wrote:
[...]
I think you missed one of the points:

#define FOO (10000000000/10000000000)

is an integer constant expression that *evaluates* to something
not exceeding ULONG_MAX (i.e., "1"). But, the preprocessor
can't *evaluate* it (because all of its component expressions
must also satisfy this criteria.

No, I didn't miss the limitation. I stated the assumption
that FOO was "an integer constant expression," but your example
is a C90 I.C.E. only if ULONG_MAX is at least 10000000000. (In
C99, UINTMAX_MAX is guaranteed to exceed 10000000000, so you're
home.)

Exactly. But ULLONGs exceed this!

ULLONG_MAX cannot exceed UINTMAX_MAX, by definition.

I don't see UINTMAX_MAX in C89 (see my original post)
Since you've already chosen to waste some space by working
in powers of ten instead of powers of two, I'm not sure why
"most tightly" (from one of your messages elsethread) is so
important to you.

I work in powers of 10 (in this library) because it
allows "exact" representations of decimal values.

"Most tightly" because it would be foolish to use
"ints" with RADIX=10 as even 16 bit ints would result in
half the memory being "empty".
I'm not sure why you use < instead of <= (I mention it
because you've done the same elsethread, and I'm beginning
to think it might not be just a typo).

It's a (consistent) typo on my part. :> It changes things
by "1" (hardly significant to the problem being discussed)
Also, the #error message is at best misleading, since
your #if ladder does not exhaust all the "standard" integer
types.

As I mentioned, I'm writing this off the top of my head
merely to illustratet to readers the *type* of thing I
am trying to do. I would assume someone could grasp
the *intent* of the #error without worrying about my
choice of words, here (I suspect there are *spelling*
errors in my posts as well! :> )
Do some simple algebra:

#if RADIX - 1 < /*sic*/ UCHAR_MAX - RADIX

Yes, but it still doesn't *solve* (all of) the problem(s).
And, it obfuscates what the conditional is testing for
(especially important when trying to give an example in
a forum where folks don't know *why* one would want to
do something "crazy" like this...)
Algebra again.


The latter choice would make one of your #error directives
fire.

Why? ULLONG_MAX will handle the most demanding of the
conditions (i.e., product-t).
Also, the belief that "smaller RADIX means greater speed"

It's not "a belief". It's the result of *measured*
design tradeoffs. Again, my original post stipulates
that this is used in a variety of targets (with varying
processor characteristics as well as application needs)
needs more thought. It is possible that individual arithmetic
operations might be faster in smaller types than in wider types,
but for a given range of numbers you will need more digits and
thus perform more operations. A given value V will have roughly
eight times as many radix-10 digits as radix-100000000 digits.

But, you're assuming all V's are equally likely.
If, for example, you are dealing with small-ish V's
the overhead of pulling wide values from memory,
manipulating them and then re-storing them dominates
the process.
The preprocessor *is* powerful enough for at least some of
the calculations you've shown. But no, it's not all-powerful.
That's why several people have suggested you write helper programs
in a more powerful language -- C, for example -- to do what the
preprocessor cannot do, or can do only with difficulty.

And, you will note, that I have explained that that is the
approach I *have* taken.

You will also note the reasons I have explained for not liking
this approach.

And the motivation for seeing if others facing similar issues
have taken any *other* approaches to solve the problem.
But if you won't take advice ... Well, one wonders why you
bother asking for it. Good-bye.

Don't forget your ball and bat...
 
E

Eric Sosman

Richard said:
It is perfectly reasonable to seek advice and then reject it. Before
one can make a good decision, one needs to know all (or at least
many) of the reasonable options, so that one can select from amongst
them. A good advisor will present as many options as can reasonably
be presented, with pros and cons for each one. That way, the advisee
(ghastly word) can make their decision in an informed way. It may
well be that this results in taking a decision that is not the one
the advisor would have chosen.

Yes, but all he does is repeat the same complaint over and
over, adding a few more exclamation points each time. That's
not an attitude I feel I can help him with.
 
D

D Yuniskis

Eric said:
Yes, but all he does is repeat the same complaint over and
over, adding a few more exclamation points each time. That's
not an attitude I feel I can help him with.

Because you are restating things that I have already DISCOUNTED
in my ORIGINAL POST:

"... different platforms (portability)"
"... legacy systems"
"... tools that are no longer supported"
"... I often am working in a C89 context)"
"... exceeds the capabilities of a 32 bit int (for older compilers)"
"... too big for "older" preprocessors to handle predictably"
"... executable to run which "does the math" and makes the choice"
"... presents other problems as it relies on there being a compiler for
the *host* available at build time"
"... Any suggestions on alternative techniques"

I have patiently re-explained each of this issues each time you
have posed a solution that has conflicted with them.

Obviously, you don't have a solution beyond what I already had.
That's fine. But, complaining because I had to keep *reiterating*
my initial constraints just says, to me, that you chose to ignore
them.
 

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,778
Messages
2,569,605
Members
45,237
Latest member
AvivMNS

Latest Threads

Top