Limiting Range of Value Types in C++

Limiting range of value types in C++

You can do this using templates -- try something like this:

template< typename T, int min, int max >class LimitedValue {
template< int min2, int max2 >LimitedValue( const LimitedValue< T, min2, max2 > &other )
{
static_assert( min <= min2, "Parameter minimum must be >= this minimum" );
static_assert( max >= max2, "Parameter maximum must be <= this maximum" );

// logic
}
// rest of code
};

Cast type with range limit

Since C11 you can use the new _Generic selection feature

#define GET_MIN(VALUE) _Generic((VALUE), \
char : CHAR_MIN, \
signed char : SCHAR_MIN, \
short : SHRT_MIN, \
int : INT_MIN, \
long : LONG_MIN, \
long long : LLONG_MIN, \
default : 0 /* unsigned types */)

#define GET_MAX(VALUE) _Generic((VALUE), \
char : CHAR_MAX, \
unsigned char : UCHAR_MAX, \
signed char : SCHAR_MAX, \
short : SHRT_MAX, \
unsigned short : USHRT_MAX, \
int : INT_MAX, \
unsigned int : UINT_MAX, \
long : LONG_MAX, \
unsigned long : ULONG_MAX, \
long long : LLONG_MAX, \
unsigned long long : ULLONG_MAX)

#define CLAMP(TO, X) ((X) < GET_MIN((TO)(X)) \
? GET_MIN((TO)(X)) \
: ((X) > GET_MAX((TO)(X)) ? GET_MAX((TO)(X)) : (TO)(X)))

You can remove the unnecessary types to make it shorter. After that just call it as CLAMP(type, value) like this

int main(void)
{
printf("%d\n", CLAMP(char, 1234));
printf("%d\n", CLAMP(char, -1234));
printf("%d\n", CLAMP(int8_t, 12));
printf("%d\n", CLAMP(int8_t, -34));

printf("%d\n", CLAMP(unsigned char, 1234));
printf("%d\n", CLAMP(unsigned char, -1234));
printf("%d\n", CLAMP(uint8_t, 12));
printf("%d\n", CLAMP(uint8_t, -34));
}

This way you can clamp to almost any types, including floating-point types or _Bool if you add more types to the support list. Beware of the type width and signness issues when using it

Demo on Godlbolt

You can also use the GNU typeof or __auto_type extensions to make the CLAMP macro cleaner and safer. These extensions also work in older C versions so you can use them in you don't have access to C11


Another simple way to do this in older C versions is to specify the destination bitwidth

// Note: Won't work for (unsigned) long long and needs some additional changes
#define CLAMP_SIGN(DST_BITWIDTH, X) \
((X) < -(1LL << ((DST_BITWIDTH) - 1)) \
? -(1LL << ((DST_BITWIDTH) - 1)) \
: ((X) > ((1LL << ((DST_BITWIDTH) - 1)) - 1) \
? ((1LL << ((DST_BITWIDTH) - 1)) - 1) \
: (X)))

#define CLAMP_UNSIGN(DST_BITWIDTH, X) \
((X) < 0 ? 0 : \
((X) > ((1LL << (DST_BITWIDTH)) - 1) ? \
((1LL << (DST_BITWIDTH)) - 1) : (X)))

// DST_BITWIDTH < 0 for signed types, > 0 for unsigned types
#define CLAMP(DST_BITWIDTH, X) (DST_BITWIDTH) < 0 \
? CLAMP_SIGN(-(DST_BITWIDTH), (X)) \
: CLAMP_UNSIGN((DST_BITWIDTH), (X))

Beside the fact that it doesn't work for long long and unsigned long long without some changes, this also implies the use of 2's complements. You can call CLAMP with a negative bit width to indicate a signed type or call CLAMP_SIGN/CLAMP_UNSIGN direction

Another disadvantage is that it just clamps the values and doesn't cast to the expected type (but you can use typeof or __auto_type as above to return the correct type)

Demo on Godbolt

CLAMP_SIGN(8, 300)
CLAMP_SIGN(8, -300)
CLAMP_UNSIGN(8, 1234)
CLAMP_UNSIGN(8, -1234)
CLAMP(-8, 1234)
CLAMP(-8, -1234)
CLAMP(8, 12)
CLAMP(8, -34)

What range of values can integer types store in C++?

The minimum ranges you can rely on are:

  • short int and int: -32,767 to 32,767
  • unsigned short int and unsigned int: 0 to 65,535
  • long int: -2,147,483,647 to 2,147,483,647
  • unsigned long int: 0 to 4,294,967,295

This means that no, long int cannot be relied upon to store any 10-digit number. However, a larger type, long long int, was introduced to C in C99 and C++ in C++11 (this type is also often supported as an extension by compilers built for older standards that did not include it). The minimum range for this type, if your compiler supports it, is:

  • long long int: -9,223,372,036,854,775,807 to 9,223,372,036,854,775,807
  • unsigned long long int: 0 to 18,446,744,073,709,551,615

So that type will be big enough (again, if you have it available).


A note for those who believe I've made a mistake with these lower bounds: the C requirements for the ranges are written to allow for ones' complement or sign-magnitude integer representations, where the lowest representable value and the highest representable value differ only in sign. It is also allowed to have a two's complement representation where the value with sign bit 1 and all value bits 0 is a trap representation rather than a legal value. In other words, int is not required to be able to represent the value -32,768.

range on int, short and other data types in C on my processor?

From this, take a look at the footnote:

  • the actual value depends on the particular system and library implementation, but shall reflect the limits of these types in the target platform.

If you run the following code on your system, it should shed some insight because the value returned may or may not differ from the ones in the above link.

    #include <stdio.h>
#include <float.h>
#include <limits.h>

int main() {
printf("\t\tUsing <limits.h> library definitions...\n");
printf("CHAR\n");
printf("signed char max: %d\n", SCHAR_MAX);
printf("unsigned char max: %u\n", UCHAR_MAX); // Note use of u, formatting output
printf("signed char min: %d\n", SCHAR_MIN);
printf("SHORT\n");
printf("signed short min: %d\n", SHRT_MIN);
printf("signed short max: %d\n", SHRT_MAX);
printf("unsigned short max: %d\n", USHRT_MAX);
printf("INT\n");
printf("signed int max: %d\n", INT_MAX);
printf("unsigned int max: %u\n", UINT_MAX);
printf("signed int min: %d\n", INT_MIN);
printf("LONG\n");
printf("signed long max: %d\n", LONG_MAX);
printf("unsigned long max: %u\n", ULONG_MAX);
printf("signed long min: %d\n", LONG_MIN);
printf("FLOAT\n");
printf("signed float max: %e\n", FLT_MAX);
printf("signed float min: %e\n", FLT_MIN);
printf("DOUBLE\n");
printf("signed double max: %e\n", DBL_MAX);
printf("signed double min: %e\n", DBL_MIN);
return 0;
}

Is there any way in C# that I can limit the range of an int variable?

No. This is a good example of why exposing public fields is a bad idea - you have no control over how they're used.

If you change it into a property, you can validate the value in the setter:

// TODO: Use a better name than either foo or aBtn
private static int foo;

public static int Foo
{
get => foo;
set => foo = value >= 0 && value < 6
? value
: throw new ArgumentOutOfRangeException("Some useful error message here");
}

If you don't like using the conditional ?: operator there, you can use a block-bodied setter:

public static int Foo
{
get => foo;
set
{
if (value < 0 || value > 5)
{
throw new ArgumentOutOfRangeException("Some useful error message");
}
foo = value;
}
}

Or better, have a utilty method that validates a value and returns the input if it's in range, or throws an exception otherwise. You can then use something like:

public static int Foo
{
get => foo;
set => foo = Preconditions.CheckArgumentRange(nameof(value), value, 0, 5);
}

Here's a slightly modified version of CheckArgumentRange from Noda Time. (The real version has a separate method to do the throwing, which I suspect is for performance reasons, to allow the comparison part to be inlined.)

internal static int CheckArgumentRange(
string paramName, int value, int minInclusive, int maxInclusive)
{
if (value < minInclusive || value > maxInclusive)
{
throw new ArgumentOutOfRangeException(paramName, value,
$"Value should be in range [{minInclusive}-{maxInclusive}]");
}
return value;
}


Related Topics



Leave a reply



Submit