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.