fenbf / cppstories-discussions

4 stars 1 forks source link

2022/safe-int-cmp-cpp20/ #96

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

Integer Conversions and Safe Comparisons in C++20 - C++ Stories

Sometimes, If you mix different integer types in an expression, you might end up with tricky cases. For example, comparing long with size_t might give different results than long with unsigned short. C++20 brings some help, and there’s no need to learn all the complex rules :) Conversion and Ranks   Let’s have a look at two comparisons:

https://www.cppstories.com/2022/safe-int-cmp-cpp20/

PaltryProgrammer commented 2 years ago

For integral addition/subtraction wrote specialized routines which add/subtract "mathematically" and check for overflows. The result type is specified by the caller.

fenbf commented 2 years ago

nice @PaltryProgrammer ! Do you have code somewhere? are those functions similar to c++20's cmp_** ?

2kaud commented 2 years ago

IMO never overlook a signed/unsigned warning in code. At some point it will probably come back to bite you - hard! Also, casting a signed to an unsigned (or vice-versa) in the code to remove the warnings is often the sign of ill-thought out code which could benefit from a re-think.

Also, IMO, it's never a good idea to use a -ve number to indicate an error and a +ve number as a valid value. Use something like std::optional or std::expected (C++23). Then you can have an unsigned type for the value and still indicate an error condition.

Also, when dealing with .size() or std::size() use a type of size_t - not an int (or unsigned). If you really need a signed type for some reason, then use std::ssize() (C++20) which has a type of std::ptrdiff_t (a signed type).

If you need an integral constant of type size_t then use the suffix uz (C++23) or for an integral constant of type std::ptrdiff_t then use suffix z (C++23) - otherwise previously you'll need to use a static_cast. By default, am integral numeric constant is of type int. This is especially important if using auto to initialise integral variables.

By the way, always remember that for an unsigned type of value 0, then decrementing is valid and although technically UB it will most likely result in a very large +ve number. Consider:

#include <iostream>

int main() {
    constexpr size_t init { 5 };

    for (size_t t { init }; t <= init; --t)
        std::cout << t << ' ';

    std::cout << '\n';
}

which displays (VS):

5 4 3 2 1 0
ninja-on-rye commented 2 years ago

Honestly, it's a real struggle. Like, int8_t and int16_t at a blink will convert up to native int, which is not great. uint8_t and uint16_t at a blink will convert up to native int and therefore also change their sign, which is worse.

2kaud commented 2 years ago

Oh yes. Implicit promotion can be a real 'gotcha' and can bite - hard! Unless you're got memory space issues (embedded etc), then why have the hassle of using int16_t etc? int8_t is just char - uint8_t is unsigned char.

2kaud commented 2 years ago

Don't forget that a numeric integral literal has a type of int. eg 6, 87, 789 are all of type int. And if you use an int with a type of fewer bits then the other type is promoted to type int. So if a is type uint16_t then a + 6 gives a type int as 6 is int and a is promoted under the rules above. Also note that from the standard:

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

So even if you add 2 int16_t values, the result type will be of type int - NOT type int16_t as might have been expected. This also applies to say adding say (char)7 to a type char. The result is still a type int. This is also the same for adding say uint8_t to uint8_t. The result type is still int!

fenbf commented 2 years ago

As I've read in "Beautiful C++," - just use signed types if you plan to do some arithmetic operations, and signed, in this case will give you correct results. Use unsigned only for working with sizeof and other rare cases.

2kaud commented 2 years ago

See the C++ Core Guidelines: ES.100 - Don't mix signed and unsigned arithmetic ES.102 - Use signed types for arithmetic

See: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-mix https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-signed

PaltryProgrammer commented 1 year ago

Greetings Kind Regards I do not know how C++ 20 cmp works . If you wish to view my code it is attached . Kind Regards

On Mon, Sep 12, 2022 at 6:49 AM JFT @.***> wrote:

IMO never overlook a signed/unsigned warning in code. At some point it will probably come back to bite you - hard! Also, casting a signed to an unsigned (or vice-versa) in the code to remove the warnings is often the sign of ill-thought out code which could benefit from a re-think.

Also, IMO, it's never a good idea to use a -ve number to indicate an error and a +ve number as a valid value. Use something like std::optional or std::expected (C++23). Then you can have an unsigned type for the value and still indicate an error condition.

Also, when dealing with .size() or std::size() use a type of size_t - not an int (or unsigned). If you really need a signed type for some reason, then use std::ssize() (C++20) which has a type of std::ptrdiff_t (a signed type).

If you need an integral constant of type size_t then use the suffix uz (C++23) or for an integral constant of type std::ptrdiff_t then use suffix z (C++23) - otherwise previously you'll need to use a static_cast. By default, am integral numeric constant is of type int.

By the way, always remember that for an unsigned type of value 0, then decrementing is valid and although technically UB it will most likely result in a very large +ve number. Consider:

include

int main() { constexpr size_t init { 5 };

for (size_t t { init }; t <= init; --t)
  std::cout << t << ' ';

std::cout << '\n';

}

which displays (VS):

5 4 3 2 1 0

— Reply to this email directly, view it on GitHub https://github.com/fenbf/cppstories-discussions/issues/96#issuecomment-1243557883, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALSJS4BZXZRA25QAC4R27G3V54DCTANCNFSM6AAAAAAQKIQXSM . You are receiving this because you were mentioned.Message ID: @.***>

-- Below is my "Signature" apologies if offends "I once put instant coffee into the microwave and went back in time." - Steven Wright "Shut up and calculate" - apparently N. David Mermin possibly Richard Feynman “I want to sing, I want to cry, I want to laugh. Everything together. And jump and dance. The day has arrived — yippee!” - Desmond Tutu “When the green flag drops the bullshit stops!” "It is cheaper to save the world than it is to ruin it." "I must have had lessons" - Reverend Jim Ignatowski / Christopher Lloyd "Dripping water hollows out stone, not through force, but through persistence." - Ovid, Roman poet

pragma once

pragma message("#include " FILE )

define MATH_CAST(castType, value) math_adjunct::cast(value)

define MATH_MIN(left, right) math_adjunct::min(left, right)

define MATH_MAX(left, right) math_adjunct::max(left, right)

define MATH_IS_NEGATIVE(value) math_adjunct::is_negative(value)

define MATH_IS_POSITIVE(value) math_adjunct::is_positive(value)

define MATH_IS_ZERO(value) math_adjunct::is_zero(value)

define MATH_ABSOLUTE_VALUE(value) math_adjunct::absolute_value(value)

extern const string numeric_overflow_msg; extern const string logical_overflow_msg; extern const string integral_subtraction_overflow_msg; extern const string integral_subtraction_assign_overflow_msg; extern const string integral_addition_logical_overflow_msg; extern const string integral_subtraction_logical_overflow_msg;

namespace math_adjunct { template requires CONCEPT_strict_integral constexpr bool is_negative(integralType _value); template requires CONCEPT_strict_integral constexpr bool is_positive(integralType _value); template requires CONCEPT_strict_integral constexpr bool is_zero(integralType _value); template requires signed_integral constexpr make_unsigned_t absolute_value(integralType _value); template requires unsigned_integral constexpr integralType absolute_value(integralType value);

template<size_t rank, bool isSigned>
struct rank_signed_type
{
    using type = conditional_t<rank == sizeof(char), char, conditional_t < rank == sizeof(short), conditional_t<isSigned, short, unsigned short>, conditional_t<rank == sizeof(int), conditional_t<isSigned, int, unsigned int>, conditional_t<rank == sizeof(long), conditional_t<isSigned, long, unsigned long>, conditional_t<rank == sizeof(long long), conditional_t<isSigned, long long, unsigned long long>, void>>>>>;
};

template<size_t rank, bool isSigned>
using rank_signed_type_t = rank_signed_type<rank, isSigned>::type;

template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
struct ccommon_type
{
    static constexpr size_t rank_T = sizeof(T);
    static constexpr size_t rank_U = sizeof(U);
    static constexpr bool is_signed_T = is_signed_v<T>;
    static constexpr bool is_signed_U = is_signed_v<U>;
    static constexpr size_t signed_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_T; else if (is_signed_U && !is_signed_T) return rank_U; else return static_cast<size_t>(0); }();
    static constexpr size_t unsigned_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_U; else if (is_signed_U && !is_signed_T) return rank_T; else return static_cast<size_t>(0); }();
    // same type -> common type
    // same rank same signed -> rank_T, signed_T
    // same rank different signed -> rank_T*2, signed 
    // different ranks same signed -> larger rank, signed T
    // different ranks different signed
            // signed rank < unsigned rank -> unsigned rank*2 signed
            // signed rank > unsigned rank -> signed type
    using type = conditional_t < is_same_v<T, U>, common_type_t<T, U>, conditional_t < rank_T == rank_U && is_signed_T == is_signed_U, rank_signed_type_t<rank_T, is_signed_T>, conditional_t < rank_T == rank_U && is_signed_T != is_signed_U, rank_signed_type_t<rank_T * 2, true>, conditional_t < rank_T != rank_U && is_signed_T == is_signed_U, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), is_signed_T>, conditional_t <std::cmp_less(signed_rank, unsigned_rank), rank_signed_type_t<unsigned_rank * 2, true>, rank_signed_type_t<signed_rank, true>>>>>>;
};
template<typename T, typename U>
using ccommon_type_t = ccommon_type<T, U>::type;
template<typename T, typename U>
concept CONCEPT_common_type = !is_same_v<void, ccommon_type_t<T, U>>;

template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
struct positive_common_type
{
    static constexpr size_t rank_T = sizeof(T);
    static constexpr size_t rank_U = sizeof(U);
    static constexpr bool is_signed_T = is_signed_v<T>;
    static constexpr bool is_signed_U = is_signed_v<U>;
    static constexpr size_t signed_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_T; else if (is_signed_U && !is_signed_T) return rank_U; else return static_cast<size_t>(0); }();
    static constexpr size_t unsigned_rank = [&]() { if (is_signed_T && !is_signed_U) return rank_U; else if (is_signed_U && !is_signed_T) return rank_T; else return static_cast<size_t>(0); }();
    // same type -> common type
    // same rank same signed -> rank_T, signed T
    // same rank different signed -> rank_T, false
    // different rank same signed -> larger rank, signed T
    // different rank different signed -> larger rank, sign of larger rank
    using type = conditional_t < is_same_v<T, U>, common_type_t<T, U>, conditional_t < rank_T == rank_U && is_signed_T == is_signed_U, rank_signed_type_t<rank_T, is_signed_T>, conditional_t < rank_T == rank_U && is_signed_T != is_signed_U, rank_signed_type_t<rank_T, false>, conditional_t < rank_T != rank_U && is_signed_T == is_signed_U, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), is_signed_T >, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), std::cmp_greater(rank_T, rank_U) ? is_signed_T : is_signed_U>>>>>;
};
template<typename T, typename U>
using positive_common_type_t = positive_common_type<T, U>::type;
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U> && signed_integral<T> && signed_integral<U>
struct negative_common_type
{
    static constexpr size_t rank_T = sizeof(T);
    static constexpr size_t rank_U = sizeof(U);
    // same type -> common type
    // same rank  -> rank_T, true
    // different rank -> larger rank, true
    using type = conditional_t < is_same_v<T, U>, common_type_t<T, U>, conditional_t < rank_T == rank_U, rank_signed_type_t<rank_T, true>, rank_signed_type_t < std::max<size_t>(rank_T, rank_U), true>>>;
};
template<typename T, typename U>
using negative_common_type_t = negative_common_type<T, U>::type;

template<typename argType> requires CONCEPT_strict_integral<argType>
struct promoted_integral;

// note these are hard coded and do not change automatically per any change in the
// language
//template<> struct promoted_integral<signed char> { using type = int; };
template<> struct promoted_integral<short> { using type = int; };

//template<> struct promoted_integral<unsigned char> { using type = int; };
template<> struct promoted_integral<unsigned short> { using type = int; };

//template<> struct promoted_integral<char> { using type = conditional_t<is_signed_v<char>, int, unsigned int>; };

//template<> struct promoted_integral<wchar_t> { using type = conditional_t<can_hold_range_v<int, wchar_t>, int, unsigned int>; };
//template<> struct promoted_integral<char8_t> { using type = conditional_t<can_hold_range_v<int, char8_t>, int, unsigned int>; };
//template<> struct promoted_integral<char16_t> { using type = conditional_t<can_hold_range_v<int, char16_t>, int, unsigned int>; };
//template<> struct promoted_integral<char32_t> { using type = conditional_t<can_hold_range_v<int, char32_t>, int, unsigned int>; };

template<> struct promoted_integral<int> { using type = int; };
template<> struct promoted_integral<unsigned int> { using type = unsigned int; };
template<> struct promoted_integral<long> { using type = long; };
template<> struct promoted_integral<unsigned long> { using type = unsigned long; };
template<> struct promoted_integral<long long > { using type = long long; };
template<> struct promoted_integral<unsigned long long> { using type = unsigned long long; };

template<typename T>
using promoted_integral_t = promoted_integral<T>::type;

template<typename leftArgType, typename rightArgType> requires CONCEPT_strict_integral<leftArgType>&& CONCEPT_strict_integral<rightArgType>
struct integral_binary_arithmetic_converted { using type = common_type_t<promoted_integral_t<leftArgType>, promoted_integral_t<rightArgType>>; };

template<typename castType, typename valueType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<valueType>
struct is_convertible_type
{
    static constexpr bool value = is_same_v<valueType, castType>
        ||
        (is_signed_v<castType> && is_signed_v<valueType> && sizeof(castType) >= sizeof(valueType))
        ||
        (is_signed_v<castType> && !is_signed_v<valueType> && sizeof(castType) > sizeof(valueType))
        ||
        (!is_signed_v<castType> && !is_signed_v<valueType> && sizeof(castType) >= sizeof(valueType));
};
template<typename castType, typename valueType>
constexpr bool is_convertible_type_v = is_convertible_type<castType, valueType>::value;

template<typename castType, typename valueType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<valueType>
bool is_convertible_value(valueType valueArg);
template<typename castType, typename argType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<argType>
castType cast(argType value);
template<typename argType> requires CONCEPT_strict_integral<argType>
struct climits
{
    using arg_type = argType;
    arg_type lower;
    arg_type upper;
    climits();
    template<typename lowerType, typename upperType> 
    climits(lowerType lower, upperType upper);
};

define MATH_CLIMITS_TYPE(_argtype) math_adjunct::climits<_arg_type_>

define MATH_CLIMITS(_argtype, ...) math_adjunct::climits<_arg_type_>(__VA_ARGS__)

// detection of overflow in unsigned integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap

template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_numeric_overflow(augendType augend, addendType addend);

template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_numeric_overflow(minuendType minuend, subtrahendType subtrahend);

template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_overflow(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits=MATH_CLIMITS(sumType));

template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_overflow(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits=MATH_CLIMITS(differenceType));

// detection of overflow in signed integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow

template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr optional<sumType> addition(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits = MATH_CLIMITS(sumType), bool throw_on_overflow = true);

template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr optional<differenceType> subtraction(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits = MATH_CLIMITS(differenceType), bool throw_on_overflow = true);

template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> min(T left, U right);
template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> max(T left, U right);

template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_min(T left, U right);
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_max(T left, U right);
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U> && signed_integral<T> && signed_integral<U>
constexpr negative_common_type_t<T, U> negative_min(T left, U right);
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>&& signed_integral<T>&& signed_integral<U>
constexpr negative_common_type_t<T, U> negative_max(T left, U right);

#ifdef _UNDEFINED_
[0] signed char or signed short can be converted to int;
[1] unsigned char, char8_t(since C++20) or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
[2] char can be converted to int or unsigned int depending on the underlying type : signed char or unsigned char(see above);
[3] wchar_t, char8_t, char16_t, and char32_t(since C++11) can be converted to the first type from the following list able to hold their entire value range : int, unsigned int, long, unsigned long, long long, unsigned long long(since C++11);
#endif

#ifdef _UNDEFINED_
[0] signed char or signed short can be converted to int;
[1] unsigned char, char8_t(since C++20) or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
[2] char can be converted to int or unsigned int depending on the underlying type : signed char or unsigned char(see above);
[3] wchar_t, char8_t, char16_t, and char32_t(since C++11) can be converted to the first type from the following list able to hold their entire value range : int, unsigned int, long, unsigned long, long long, unsigned long long(since C++11);
#endif

#ifdef _UNDEFINED_
left arg        right arg       condition
addition subtraction
signed          signed          a signed rank greater than each exists
signed          unsigned        a signed rank greater than each exists
unsigned        signed          a signed rank greater than each exists
unsigned        unsigned        an unsigned rank greater than each exists
#endif

#ifdef _UNDEFINED_
// identical addition and subtraction requires syntax
// sum = augend + addend
//augendType    addendType      sumType         requires
signed          signed      signed      sizeof sumType >= sizeof augendType && sizeof sumType >= sizeof addendType
signed          signed      unsigned    false
signed          unsigned    signed      sizeof sumType >= sizeof augendType && sizeof sumType > sizeof addendType
unsigned        signed      signed      sizeof sumType > sizeof augendType && sizeof sumType >= sizeof addendType
signed          unsigned    unsigned    false
unsigned        signed      unsigned    false
unsigned        unsigned    signed      sizeof sumType > sizeof augendType && sizeof sumType > sizeof addendType
unsigned        unsigned    unsigned    sizeof sumType >= sizeof augendType && sizeof sumType >= sizeof addendType

// difference = minuend - subtrahend
// minuendType  subtrahendType  differentType
signed          signed          signed      sizeof differenceType >= sizeof minuendType && sizeof differenceType >= sizeof subtrahendType
signed          signed          unsigned    false
signed          unsigned        signed      sizeof differenceType >= sizeof minuendType && sizeof differenceType > sizeof subtrahendType
unsigned        signed          signed      sizeof differenceType > sizeof minuendType && sizeof differenceType >= sizeof subtrahendType
signed          unsigned        unsigned    false
unsigned        signed          unsigned    false
unsigned        unsigned        signed      sizeof differenceType > sizeof minuendType && sizeof differenceType > sizeof subtrahendType
unsigned        unsigned        unsigned    sizeof differenceType >= sizeof minuendType && sizeof differenceType >= sizeof subtrahendType
#endif

#ifdef _UNDEFINED_
can cast conditions
arg type    result type
signed      signed
size arg type <= size result type
-> true
size arg type > size result type
arg < 0
->arg >= result type::min
arg > 0
->arg <= result type::max

signed      unsigned
size arg type < size result type
->arg >= 0
size arg type == size result type
->arg >= 0
size arg type > size result type
arg < 0
-> false
arg >= 0
->arg <= result type::max

unsigned    signed
->arg <= result type::max

unsigned    unsigned
->arg <= result type::max
#endif

template<typename leftArgType, typename rightArgType> struct greater_rank_binary_operator {};
template<> struct greater_rank_binary_operator<short, short> { using type = int; };
template<> struct greater_rank_binary_operator<short, unsigned short> { using type = int; };
template<> struct greater_rank_binary_operator<short, int> { using type = long long; };
template<> struct greater_rank_binary_operator<short, unsigned int> { using type = long long; };
template<> struct greater_rank_binary_operator<short, long> { using type = long long; };
template<> struct greater_rank_binary_operator<short, unsigned long> { using type = long long; };
template<> struct greater_rank_binary_operator<short, long long> { using type = void; };
template<> struct greater_rank_binary_operator<short, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned short, short> { using type = int; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned short> { using type = unsigned int; };
template<> struct greater_rank_binary_operator<unsigned short, int> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned int> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned short, long> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned long> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned short, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned short, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<int, short> { using type = long long; };
template<> struct greater_rank_binary_operator<int, unsigned short> { using type = long long; };
template<> struct greater_rank_binary_operator<int, int> { using type = long long; };
template<> struct greater_rank_binary_operator<int, unsigned int> { using type = long long; };
template<> struct greater_rank_binary_operator<int, long> { using type = long long; };
template<> struct greater_rank_binary_operator<int, unsigned long> { using type = long long; };
template<> struct greater_rank_binary_operator<int, long long> { using type = void; };
template<> struct greater_rank_binary_operator<int, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned int, short> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned short> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned int, int> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned int> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned int, long> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned long> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned int, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned int, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<long, short> { using type = long long; };
template<> struct greater_rank_binary_operator<long, unsigned short> { using type = long long; };
template<> struct greater_rank_binary_operator<long, int> { using type = long long; };
template<> struct greater_rank_binary_operator<long, unsigned int> { using type = long long; };
template<> struct greater_rank_binary_operator<long, long> { using type = long long; };
template<> struct greater_rank_binary_operator<long, unsigned long> { using type = long long; };
template<> struct greater_rank_binary_operator<long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<long, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long, short> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned short> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned long, int> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned int> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned long, long> { using type = long long; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned long> { using type = unsigned long long; };
template<> struct greater_rank_binary_operator<unsigned long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, short> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned short> { using type = void; };
template<> struct greater_rank_binary_operator<long long, int> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned int> { using type = void; };
template<> struct greater_rank_binary_operator<long long, long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<long long, unsigned long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, short> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned short> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, int> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned int> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, long long> { using type = void; };
template<> struct greater_rank_binary_operator<unsigned long long, unsigned long long> { using type = void; };

ifdef UNDEFINED

template<typename T, typename R> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>
struct cinteger
{
    using result_type = R;
    T value;
    math_adjunct::climits<R> limits;
    cinteger(T _value) : value(_value), limits(numeric_limits<R>::min(), numeric_limits<R>::max()) {}
    cinteger(T _value, R _lower_limit, R _upper_limit) : value(_value), limits(_lower_limit, _upper_limit) {}
};

template<typename T, typename R, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>&& CONCEPT_strict_integral<U>
R operator + (cinteger<T, R> augend, U addend)
{
    auto sum = addition(augend.value, addend, augend.limits);
    return *sum;
}
template<typename T, typename R, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>&& CONCEPT_strict_integral<U>
R operator - (cinteger<T, R> minuend, U subtrahend)
{
    auto difference = subtraction(minuend.value, subtrahend, minuend.limits);
    return *difference;
}

template<typename T> requires CONCEPT_strict_integral<T>
cinteger<T, T> make_cinteger(T value)
{
    return cinteger<T, T>(value);
}
template<typename T, typename R> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<R>
cinteger<T, R> make_cinteger(T value, R lower_limit, R upper_limit)
{
    return cinteger<T, R>(value, lower_limit, upper_limit);
}

endif

}

pragma once

pragma message("#include " FILE )

namespace math_adjunct { template requires CONCEPT_strict_integral constexpr bool is_negative(integralType _value) { integralType _0 = 0; return std::cmp_less(_value, _0); } template requires CONCEPT_strict_integral constexpr bool is_positive(integralType _value) { integralType _0 = 0; return std::cmp_greater(_value, _0); } template requires CONCEPT_strict_integral constexpr bool is_zero(integralType _value) { integralType _0 = 0; return std::cmp_equal(_value, _0); }

template<typename integralType> requires signed_integral<integralType>
constexpr make_unsigned_t<integralType> absolute_value(integralType _value)
{
    using result_type = make_unsigned_t<integralType>;
    if (_value < 0)
    {
        if (_value != numeric_limits<integralType>::min()) return -_value;
        else
        {
            ++_value;
            result_type absolute_value = -_value;
            return absolute_value + literal::_1u;
        }
    }
    else return _value;
}
template<typename integralType> requires unsigned_integral<integralType>
constexpr integralType absolute_value(integralType value) { return value; }

template<typename castType, typename valueType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<valueType>
bool is_convertible_value(valueType valueArg)
{
    bool value = valueArg == 0
        ||
        // cast type    value type      
        // signed       signed
        (
            signed_integral<castType> && signed_integral<valueType>
            &&
            (
                (valueArg < 0 && valueArg >= numeric_limits<castType>::min())
                ||
                (valueArg >= 0 && valueArg <= numeric_limits<castType>::max())
                )
            )
        ||
        (
            // unsigned     signed
            unsigned_integral<castType> && signed_integral<valueType>
            &&
            (
                valueArg >= 0 && valueArg <= numeric_limits<castType>::max()
                )
            )
        ||
        (
            // signed       unsigned
            signed_integral<castType> && unsigned_integral<valueType>
            &&
            valueArg <= numeric_limits<castType>::max()
            )
        ||
        (
            // unsigned     unsigned
            unsigned_integral<castType> && unsigned_integral<valueType>
            &&
            valueArg <= numeric_limits<castType>::max()
            );
    return value;
}
template<typename castType, typename argType> requires CONCEPT_strict_integral<castType>&& CONCEPT_strict_integral<argType>
castType cast(argType value)
{
    auto _can_cast = is_convertible_value<castType, argType>(value);
    MAKER_ASSERT(_can_cast)
    return static_cast<castType>(value);
}

template<typename argType> requires CONCEPT_strict_integral<argType>
climits<argType>::climits() : lower(numeric_limits<arg_type>::min()), upper(numeric_limits<arg_type>::max()) {}
template<typename argType> requires CONCEPT_strict_integral<argType>
template<typename lowerType, typename upperType>
climits<argType>::climits(lowerType lower, upperType upper) : lower(lower), upper(upper) { CALLER_ASSERT(is_convertible_value<argType>(lower) && is_convertible_value<argType>(upper)) }

// detection of overflow in unsigned integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap

template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_numeric_overflow(augendType augend, addendType addend)
{
    auto _can_cast = is_convertible_value<sumType>(augend) && is_convertible_value<sumType>(addend);
    if (!_can_cast) return true;
    return numeric_limits<sumType>::max() - static_cast<sumType>(augend) < static_cast<sumType>(addend);
}

template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_numeric_overflow(minuendType minuend, subtrahendType subtrahend)
{
    auto _can_cast = is_convertible_value<differenceType>(minuend) && is_convertible_value<differenceType>(subtrahend);
    if (!_can_cast) return true;
    return minuend < subtrahend;
}

template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr bool addition_overflow(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits)
{
    auto _can_cast = is_convertible_value<sumType>(augend) && is_convertible_value<sumType>(addend);
    if (!_can_cast) return true;
    auto overflow = addition_numeric_overflow<sumType>(augend, addend) || !(limits.upper >= static_cast<sumType>(augend) + static_cast<sumType>(addend)) || !(limits.lower <= static_cast<sumType>(augend) + static_cast<sumType>(addend));
    return overflow;
}

template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr bool subtraction_overflow(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits)
{
    auto _can_cast = is_convertible_value<differenceType>(minuend) && is_convertible_value<differenceType>(subtrahend);
    if (!_can_cast) return true;
    auto overflow = subtraction_numeric_overflow<differenceType>(minuend, subtrahend) || !(limits.upper >= static_cast<differenceType>(minuend) - static_cast<differenceType>(subtrahend)) || !(limits.lower <= static_cast<differenceType>(minuend) - static_cast<differenceType>(subtrahend));
    return overflow;
}

// detection of overflow in signed integral addition and subtraction
// https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow

template<typename sumType, typename augendType, typename addendType> requires CONCEPT_strict_integral<sumType>&& CONCEPT_strict_integral<augendType>&& CONCEPT_strict_integral<addendType>
constexpr optional<sumType> addition(augendType augend, addendType addend, MATH_CLIMITS_TYPE(sumType) limits, bool throw_on_overflow)
{
    bool _can_cast = is_convertible_value<sumType>(augend)&& is_convertible_value<sumType>(addend);
    if (!_can_cast) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<sumType>(); }
    else
    {
        bool _overflow = addition_overflow(augend, addend, limits);
        if (_overflow) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<sumType>(); }
        return optional<sumType>(static_cast<sumType>(augend) + static_cast<sumType>(addend));
    }
}

template<typename differenceType, typename minuendType, typename subtrahendType> requires CONCEPT_strict_integral<differenceType>&& CONCEPT_strict_integral<minuendType>&& CONCEPT_strict_integral<subtrahendType>
constexpr optional<differenceType> subtraction(minuendType minuend, subtrahendType subtrahend, MATH_CLIMITS_TYPE(differenceType) limits, bool throw_on_overflow)
{
    bool _can_cast = is_convertible_value<differenceType>(minuend) && is_convertible_value<differenceType>(subtrahend);
    if (!_can_cast) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<differenceType>(); }
    else
    {
        bool _overflow = subtraction_overflow(minuend, subtrahend, limits);
        if (_overflow) { if (throw_on_overflow) throw OVERFLOW_ERROR else return optional<differenceType>(); }
        return optional<differenceType>(static_cast<differenceType>(minuend) - static_cast<differenceType>(subtrahend));
    }
}

template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> min(T left, U right)
{
    using _common_type = ccommon_type_t<T, U>;
    return std::min(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_common_type<T, U>
constexpr ccommon_type_t<T, U> max(T left, U right)
{
    using _common_type = ccommon_type_t<T, U>;
    return std::max(static_cast<_common_type>(left), static_cast<_common_type>(right));
}

template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_min(T left, U right)
{
    MAKER_ASSERT(left >= 0 && right >= 0)
    using _common_type = positive_common_type_t<T, U>;
    return std::min(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>
constexpr positive_common_type_t<T, U> positive_max(T left, U right)
{
    MAKER_ASSERT(left >= 0 && right >= 0)
        using _common_type = positive_common_type_t<T, U>;
    return std::max(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U> && signed_integral<T> && signed_integral<U>
constexpr negative_common_type_t<T, U> negative_min(T left, U right)
{
    MAKER_ASSERT(left <= 0 && right <= 0)
        using _common_type = negative_common_type_t<T, U>;
    return std::min(static_cast<_common_type>(left), static_cast<_common_type>(right));
}
template<typename T, typename U> requires CONCEPT_strict_integral<T>&& CONCEPT_strict_integral<U>&& signed_integral<T>&& signed_integral<U>
constexpr negative_common_type_t<T, U> negative_max(T left, U right)
{
    MAKER_ASSERT(left <= 0 && right <= 0)
        using _common_type = negative_common_type_t<T, U>;
    return std::max(static_cast<_common_type>(left), static_cast<_common_type>(right));
}

}