// Licensed under the MIT License.
// Copyright David LeBlanc - dcl@dleblanc.net

/*-----------------------------------------------------------------------------------------------------------
c_safe_math
Version 1.0 - 6/21/22

This header implements a set of functions that check for integer overflows in C code.
It is based on code and logic from SafeInt.hpp, but ported to C.

Portions copied from SafeInt.hpp are Licensed under the MIT License,
and are originally copyrighted to Microsoft.
*/

#ifndef C_SAFE_MATH_IMPL
#define C_SAFE_MATH_IMPL

#if defined _MSC_VER
// Inline expansion warnings
#pragma warning(disable:4710 4711)
#endif

#ifdef __cplusplus
extern "C"
{
#endif

    // It is a bit tricky to sort out what compiler we are actually using,
    // do this once here, and avoid cluttering the code
#define VISUAL_STUDIO_COMPILER 0
#define CLANG_COMPILER 1
#define GCC_COMPILER 2
#define UNKNOWN_COMPILER -1

// Clang will sometimes pretend to be Visual Studio
// and does pretend to be gcc. Check it first, as nothing else pretends to be clang
#if defined __clang__
#define SAFEINT_COMPILER CLANG_COMPILER
#elif defined __GNUC__
#define SAFEINT_COMPILER GCC_COMPILER
#elif defined _MSC_VER
#define SAFEINT_COMPILER VISUAL_STUDIO_COMPILER
#else
#define SAFEINT_COMPILER UNKNOWN_COMPILER
#endif

// Various defines to help make working with multiple compilers easier - from SafeInt.hpp
#if SAFEINT_COMPILER == GCC_COMPILER || SAFEINT_COMPILER == CLANG_COMPILER
#define SAFEINT_NORETURN __attribute__((noreturn))
#define SAFEINT_STDCALL
#define SAFEINT_VISIBLE __attribute__ ((__visibility__("default")))
#define SAFEINT_WEAK __attribute__ ((weak))
#else
#define SAFEINT_NORETURN __declspec(noreturn)
#define SAFEINT_STDCALL __stdcall
#define SAFEINT_VISIBLE
#define SAFEINT_WEAK
#endif

#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER
    // limits.h checks __STDC_WANT_SECURE_LIB__, but doesn't include what sets it
#if !defined __STDC_WANT_SECURE_LIB__
#define __STDC_WANT_SECURE_LIB__ 0
#endif

#endif

#include <stdint.h>
#include <stdbool.h>
#include <limits.h>

// Figure out if we should use intrinsics
// If the user has already decided, let that override
#define SAFEINT_MULTIPLY_MATH        0 // no intrinsics, no built in, no 128-bit
#define SAFEINT_MULTIPLY_INTRINSICS  1 // 64-bit Visual Studio
#define SAFEINT_MULTIPLY_BUILTIN     2 // gcc, clang
#define SAFEINT_MULTIPLY_INT128      3 // Best case

// We might have 128-bit int support, check for that, as it should work best
#if !defined SAFEINT_HAS_INT128

#if defined __SIZEOF_INT128__ && __SIZEOF_INT128__ == 16
#define SAFEINT_HAS_INT128 1
#else
#define SAFEINT_HAS_INT128 0
#endif

#endif

#if SAFEINT_HAS_INT128
#define SAFEINT_MULTIPLY_METHOD SAFEINT_MULTIPLY_INT128
#else

#if !defined SAFEINT_USE_INTRINSICS
// If it is the Visual Studio compiler, then it has to be 64-bit, and not ARM64EC
#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER
#if defined _M_AMD64 && !defined _M_ARM64EC
#include <intrin.h>
#define SAFEINT_MULTIPLY_METHOD SAFEINT_MULTIPLY_INTRINSICS
#else
#define SAFEINT_MULTIPLY_METHOD SAFEINT_MULTIPLY_MATH
#endif

#else // Not VISUAL_STUDIO_COMPILER

    // Else for gcc and clang, we can use builtin functions
#if SAFEINT_COMPILER == CLANG_COMPILER || SAFEINT_COMPILER == GCC_COMPILER
#define SAFEINT_MULTIPLY_METHOD SAFEINT_MULTIPLY_BUILTIN
#else
#define SAFEINT_MULTIPLY_METHOD SAFEINT_MULTIPLY_MATH
#endif
#endif

#endif // SAFEINT_USE_INTRINSICS
#endif // SAFEINT_HAS_INT128

/*
    To replace safe_math_fail, wrap this header,
    implement safe_math_fail how you prefer,
    and set SAFE_MATH_FAIL_DEFINED
*/

#if !defined SAFE_MATH_FAIL_DEFINED
#define SAFE_MATH_FAIL_DEFINED
#include <stdlib.h>

SAFEINT_NORETURN
inline void safe_math_fail(const char* msg)
{
    (void)msg;
    abort();
}
#endif

#if !defined UINT64_MAX

#define INT8_MIN         (-127i8 - 1)
#define INT16_MIN        (-32767i16 - 1)
#define INT32_MIN        (-2147483647i32 - 1)
#define INT64_MIN        (-9223372036854775807i64 - 1)
#define INT8_MAX         127i8
#define INT16_MAX        32767i16
#define INT32_MAX        2147483647i32
#define INT64_MAX        9223372036854775807i64
#define UINT8_MAX        0xffui8
#define UINT16_MAX       0xffffui16
#define UINT32_MAX       0xffffffffui32
#define UINT64_MAX       0xffffffffffffffffui64

#endif

// Utility functions

// Purpose of this is to negate an int in a way
// where the compiler won't remove it if the input is a 
// compile time constant MIN_INT
inline int32_t negate32(int32_t in) { return (int32_t)(~(uint32_t)in + 1); }
inline int64_t negate64(int64_t in) { return (int64_t)(~(uint64_t)in + 1); }

inline uint32_t safe_abs32(int32_t in)
{
    if (in < 0)
        return ~(uint32_t)in + 1;

    return (uint32_t)in;
}

inline uint64_t safe_abs64(int64_t in)
{
    if (in < 0)
        return ~(uint64_t)in + 1;

    return (uint64_t)in;
}

// Checked casting functions
// 0 if the cast is safe, non-zero if unsafe
inline int check_cast_int8_int32(int32_t in) { return (in < INT8_MIN || in > INT8_MAX); }
inline int check_cast_int8_uint32(uint32_t in) { return in > INT8_MAX; }
inline int check_cast_int8_int64(int64_t in) { return in < INT8_MIN || in > INT8_MAX; }
inline int check_cast_int8_uint64(uint64_t in) { return (in > INT8_MAX); }
inline int check_cast_int16_int32(int32_t in) { return in < INT16_MIN || in > INT16_MAX; }
inline int check_cast_int16_uint32(uint32_t in) { return (in > INT16_MAX); }
inline int check_cast_int16_int64(int64_t in) { return (in < INT16_MIN || in > INT16_MAX); }
inline int check_cast_int16_uint64(uint64_t in) { return (in > INT16_MAX); }
inline int check_cast_int32_uint32(uint32_t in) { return (in > INT32_MAX); }
inline int check_cast_int32_int64(int64_t in) { return (in < INT32_MIN || in > INT32_MAX); }
inline int check_cast_int32_uint64(uint64_t in) { return (in > INT32_MAX); }
inline int check_cast_int64_uint64(uint64_t in) { return (in > INT64_MAX); }
inline int check_cast_uint8_int32(int32_t in) { return (in < 0 || in > UINT8_MAX); }
inline int check_cast_uint8_uint32(uint32_t in) { return (in > UINT8_MAX); }
inline int check_cast_uint8_int64(int64_t in) { return (in < 0 || in > UINT8_MAX); }
inline int check_cast_uint8_uint64(uint64_t in) { return (in > UINT8_MAX); }
inline int check_cast_uint16_int32(int32_t in) { return (in < 0 || in > UINT16_MAX); }
inline int check_cast_uint16_uint32(uint32_t in) { return (in > UINT16_MAX); }
inline int check_cast_uint16_int64(int64_t in) { return (in < 0 || in > UINT16_MAX); }
inline int check_cast_uint16_uint64(uint64_t in) { return (in > UINT16_MAX); }
inline int check_cast_uint32_int32(int32_t in) { return (in < 0); }
inline int check_cast_uint32_int64(int64_t in) { return (in < 0 || in > UINT32_MAX); }
inline int check_cast_uint32_uint64(uint64_t in) { return (in > UINT32_MAX); }
inline int check_cast_uint64_int64(int64_t in) { return (in < 0); }

inline int8_t safe_cast_int8_int32(int32_t in)
{
    if (!check_cast_int8_int32(in))
        safe_math_fail("safe_math_fail safe_cast_int8_int32");

    return (int8_t)in;
}

inline int8_t safe_cast_int8_uint32(uint32_t in)
{
    if (check_cast_int8_uint32(in))
        safe_math_fail("safe_math_fail safe_cast_int8_uint32");

    return (int8_t)in;
}

inline int8_t safe_cast_int8_int64(int64_t in)
{
    if (check_cast_int8_int64(in))
        safe_math_fail("safe_math_fail safe_cast_int8_int64");

    return (int8_t)in;
}

inline int8_t safe_cast_int8_uint64(uint64_t in)
{
    if (check_cast_int8_uint64(in))
        safe_math_fail("safe_math_fail safe_cast_int8_uint64");

    return (int8_t)in;
}

inline int16_t safe_cast_int16_int32(int32_t in)
{
    if (check_cast_int16_int32(in))
        safe_math_fail("safe_math_fail safe_cast_int16_int32");

    return (int16_t)in;
}

inline int16_t safe_cast_int16_uint32(uint32_t in)
{
    if (check_cast_int16_uint32(in))
        safe_math_fail("safe_math_fail safe_cast_int16_uint32");

    return (int16_t)in;
}

inline int16_t safe_cast_int16_int64(int64_t in)
{
    if (check_cast_int16_int64(in))
        safe_math_fail("safe_math_fail safe_cast_int16_int64");

    return (int16_t)in;
}

inline int16_t safe_cast_int16_uint64(uint64_t in)
{
    if (in > INT16_MAX)
        safe_math_fail("safe_math_fail safe_cast_int16_uint64");

    return (int16_t)in;
}

inline int32_t safe_cast_int32_uint32(uint32_t in)
{
    if (check_cast_int32_uint32(in))
        safe_math_fail("safe_math_fail safe_cast_int32_uint32");

    return (int32_t)in;
}

inline int32_t safe_cast_int32_int64(int64_t in)
{
    if (check_cast_int32_int64(in))
        safe_math_fail("safe_math_fail safe_cast_int32_int64");

    return (int32_t)in;
}

inline int32_t safe_cast_int32_uint64(uint64_t in)
{
    if (check_cast_int32_uint64(in))
        safe_math_fail("safe_math_fail safe_cast_int32_uint64");

    return (int32_t)in;
}

inline int64_t safe_cast_int64_uint64(uint64_t in)
{
    if (check_cast_int64_uint64(in))
        safe_math_fail("safe_math_fail safe_cast_int64_uint64");

    return (int64_t)in;
}

inline uint8_t safe_cast_uint8_int32(int32_t in)
{
    if (check_cast_uint8_int32(in))
        safe_math_fail("safe_math_fail safe_cast_uint8_int32");

    return (uint8_t)in;
}

inline uint8_t safe_cast_uint8_uint32(uint32_t in)
{
    if (check_cast_uint8_uint32(in))
        safe_math_fail("safe_math_fail safe_cast_uint8_uint32");

    return (uint8_t)in;
}

inline uint8_t safe_cast_uint8_int64(int64_t in)
{
    if (check_cast_uint8_int64(in))
        safe_math_fail("safe_math_fail safe_cast_uint8_int64");

    return (uint8_t)in;
}

inline uint8_t safe_cast_uint8_uint64(uint64_t in)
{
    if (check_cast_uint8_uint64(in))
        safe_math_fail("safe_math_fail safe_cast_uint8_uint64");

    return (uint8_t)in;
}

inline uint16_t safe_cast_uint16_int32(int32_t in)
{
    if (check_cast_uint16_int32(in))
        safe_math_fail("safe_math_fail safe_cast_uint16_int32");

    return (uint16_t)in;
}

inline uint16_t safe_cast_uint16_uint32(uint32_t in)
{
    if (check_cast_uint16_uint32(in))
        safe_math_fail("safe_math_fail safe_cast_uint16_uint32");

    return (uint16_t)in;
}

inline uint16_t safe_cast_uint16_int64(int64_t in)
{
    if (check_cast_uint16_int64(in))
        safe_math_fail("safe_math_fail safe_cast_uint16_int64");

    return (uint16_t)in;
}

inline uint16_t safe_cast_uint16_uint64(uint64_t in)
{
    if (check_cast_uint16_uint64(in))
        safe_math_fail("safe_math_fail safe_cast_int16_uint64");

    return (uint16_t)in;
}

inline uint32_t safe_cast_uint32_int32(int32_t in)
{
    if (check_cast_uint32_int32(in))
        safe_math_fail("safe_math_fail safe_cast_uint32_int32");

    return (uint32_t)in;
}

inline uint32_t safe_cast_uint32_int64(int64_t in)
{
    if (check_cast_uint32_int64(in))
        safe_math_fail("safe_math_fail safe_cast_int32_int64");

    return (uint32_t)in;
}

inline uint32_t safe_cast_uint32_uint64(uint64_t in)
{
    if (check_cast_uint32_uint64(in))
        safe_math_fail("safe_math_fail safe_cast_uint32_uint64");

    return (uint32_t)in;
}

inline uint64_t safe_cast_uint64_int64(int64_t in)
{
    if (check_cast_uint64_int64(in))
        safe_math_fail("safe_math_fail safe_cast_int64_uint64");

    return (uint64_t)in;
}

// Addition
/*
    For addition and multiplication, there will be checks for the following matrix:
    - int32
    - uint32
    - int64
    - uint64

    If you want to add smaller types, then do it inside the appropriate safe_cast function,
    or if adding one of the above and a smaller type, pass it into one that takes a larger
    size of the same type, for example, uint16 -> uint32.
*/

inline int32_t safe_add_int32_int32(int32_t a, int32_t b)
{
    return safe_cast_int32_int64((int64_t)a + b);
}

inline bool check_add_int32_int32(int32_t a, int32_t b, int32_t* ret)
{
    int64_t tmp = (int64_t)a + b;
    *ret = (int32_t)tmp;
    return check_cast_int32_int64(tmp) == 0;
}

inline int32_t safe_add_int32_uint32(int32_t a, uint32_t b)
{
    return safe_cast_int32_int64((int64_t)a + b);
}

inline bool check_add_int32_uint32(int32_t a, uint32_t b, int32_t* ret)
{
    int64_t tmp = (int64_t)a + b;
    *ret = (int32_t)tmp;
    return check_cast_int32_int64(tmp) == 0;
}

inline int32_t safe_add_int32_int64(int32_t a, int64_t b)
{
    int64_t tmp = (int64_t)((uint64_t)a + (uint64_t)b);
    
    if (a >= 0)
    {
        // mixed sign cannot overflow
        if (b >= 0 && tmp < a)
            safe_math_fail("safe_math_fail safe_add_int32_int64");
    }
    else
    {
        // lhs negative
        if (b < 0 && tmp > a)
            safe_math_fail("safe_math_fail safe_add_int32_int64");
    }

    return safe_cast_int32_int64(tmp);
}

inline bool check_add_int32_int64(int32_t a, int64_t b, int32_t* ret)
{
    int64_t tmp = (int64_t)((uint64_t)a + (uint64_t)b);
    *ret = (int32_t)tmp;

    if (a >= 0)
    {
        // mixed sign cannot overflow
        if (b >= 0 && tmp < a)
            return false;
    }
    else
    {
        // lhs negative
        if (b < 0 && tmp > a)
            return false;
    }

    return check_cast_int32_int64(tmp) == 0;
}

inline int32_t safe_add_int32_uint64(int32_t a, uint64_t b)
{
    if ((uint32_t)(b >> 32) == 0)
    {
        // Now it just happens to work out that the standard behavior does what we want
        // Adding explicit casts to show exactly what's happening here
        uint32_t tmp = (uint32_t)a + (uint32_t)b;

        if ((int32_t)tmp >= a)
        {
            return (int32_t)tmp;
        }
    }

    safe_math_fail("safe_math_fail safe_add_int32_uint64");
}

inline bool check_add_int32_uint64(int32_t a, uint64_t b, int32_t* ret)
{
    if ((uint32_t)(b >> 32) == 0)
    {
        // Now it just happens to work out that the standard behavior does what we want
        // Adding explicit casts to show exactly what's happening here
        uint32_t tmp = (uint32_t)a + (uint32_t)b;
        *ret = (int32_t)tmp;

        if ((int32_t)tmp >= a)
        {
            return true;
        }
    }

    return false;
}

inline uint32_t safe_add_uint32_int32(uint32_t a, int32_t b)
{
    return safe_cast_uint32_int64((int64_t)a + b);
}

inline bool check_add_uint32_int32(uint32_t a, int32_t b, uint32_t* ret)
{
    int64_t tmp = (int64_t)a + b;
    *ret = (uint32_t)tmp;
    return check_cast_uint32_int64(tmp) == 0;
}

inline uint32_t safe_add_uint32_uint32(uint32_t a, uint32_t b)
{
    uint32_t tmp = a + b;
    
    if (tmp < a)
    {
        safe_math_fail("safe_math_fail safe_add_uint32_uint32");
    }

    return tmp;
}

inline bool check_add_uint32_uint32(uint32_t a, uint32_t b, uint32_t* ret)
{
    uint32_t tmp = a + b;
    *ret = tmp;
    return tmp >= a;
}

inline uint32_t safe_add_uint32_int64(uint32_t a, int64_t b)
{
    if (b < 0)
    {
        if (a >= safe_abs64(b)) //negation is safe, since rhs is 64-bit
        {
            return (uint32_t)(a + b);
        }
    }
    else
    {
        // now we know that rhs can be safely cast into an std::uint64_t
        uint64_t tmp = (uint64_t)a + (uint64_t)b;

        // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff
        // it is not possible for the operation above to overflow, so just check max
        return safe_cast_uint32_uint64(tmp);
    }

    safe_math_fail("safe_math_fail safe_add_uint32_int64");
}

inline bool check_add_uint32_int64(uint32_t a, int64_t b, uint32_t* ret)
{
    if (b < 0)
    {
        if (a >= safe_abs64(b)) //negation is safe, since rhs is 64-bit
        {
            *ret = (uint32_t)(a + b);
            return true;
        }
    }
    else
    {
        // now we know that rhs can be safely cast into an std::uint64_t
        uint64_t tmp = (uint64_t)a + (uint64_t)b;

        // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff
        // it is not possible for the operation above to overflow, so just check max
        *ret = (uint32_t)tmp;
        return check_cast_uint32_uint64(tmp) == 0;
    }

    return false;
}

inline uint32_t safe_add_uint32_uint64(uint32_t a, uint64_t b)
{
    uint64_t tmp = (uint64_t)a + b;
    
    if (tmp >= a && tmp <= UINT32_MAX)
    {
        return (uint32_t)tmp;
    }

    safe_math_fail("safe_math_fail safe_add_uint32_uint64");
}

inline bool check_add_uint32_uint64(uint32_t a, uint64_t b, uint32_t* ret)
{
    uint64_t tmp = (uint64_t)a + b;
    *ret = (uint32_t)tmp;

    return (tmp >= a && tmp <= UINT32_MAX);
}

inline int64_t safe_add_int64_int32(int64_t a, int32_t b)
{
    int64_t tmp = (int64_t)((uint64_t)a + (uint64_t)b);

    if (a >= 0)
    {
        // mixed sign cannot overflow
        if (b >= 0 && tmp < a)
            safe_math_fail("safe_math_fail safe_add_int64_int32");
    }
    else
    {
        // lhs negative
        if (b < 0 && tmp > a)
            safe_math_fail("safe_math_fail safe_add_int64_int32");
    }

    return tmp;
}

inline bool check_add_int64_int32(int64_t a, int32_t b, int64_t* ret)
{
    int64_t tmp = (int64_t)((uint64_t)a + (uint64_t)b);
    *ret = tmp;

    if (a >= 0)
    {
        // mixed sign cannot overflow
        if (b >= 0 && tmp < a)
            return false;
    }
    else
    {
        // lhs negative
        if (b < 0 && tmp > a)
            return false;
    }

    return true;
}

inline int64_t safe_add_int64_uint32(int64_t a, uint32_t b)
{
    uint64_t tmp = (uint64_t)a + (uint64_t)b;

    if ((int64_t)tmp >= a)
    {
        return (int64_t)tmp;
    }

    safe_math_fail("safe_math_fail safe_add_int64_uint32");
}

inline bool check_add_int64_uint32(int64_t a, uint32_t b, int64_t* ret)
{
    uint64_t tmp = (uint64_t)a + (uint64_t)b;
    *ret = (int64_t)tmp;

    return ((int64_t)tmp >= a);
}

inline int64_t safe_add_int64_int64(int64_t a, int64_t b)
{
    int64_t tmp = (int64_t)((uint64_t)a + (uint64_t)b);

    if (a >= 0)
    {
        // mixed sign cannot overflow
        if (b >= 0 && tmp < a)
            safe_math_fail("safe_math_fail safe_add_int64_int64");
    }
    else
    {
        // lhs negative
        if (b < 0 && tmp > a)
            safe_math_fail("safe_math_fail safe_add_int64_int64");
    }

    return tmp;
}

inline bool check_add_int64_int64(int64_t a, int64_t b, int64_t* ret)
{
    int64_t tmp = (int64_t)((uint64_t)a + (uint64_t)b);
    *ret = tmp;

    if (a >= 0)
    {
        // mixed sign cannot overflow
        if (b >= 0 && tmp < a)
            return false;
    }
    else
    {
        // lhs negative
        if (b < 0 && tmp > a)
            return false;
    }

    return true;
}

inline int64_t safe_add_int64_uint64(int64_t a, uint64_t b)
{
    uint64_t tmp = (uint64_t)a + b;

    if ((int64_t)tmp >= a)
    {
        return (int64_t)tmp;
    }

    safe_math_fail("safe_math_fail safe_add_int64_uint64");
}

inline bool check_add_int64_uint64(int64_t a, uint64_t b, int64_t* ret)
{
    uint64_t tmp = (uint64_t)a + b;
    *ret = (int64_t)tmp;

    return ((int64_t)tmp >= a);
}

inline uint64_t safe_add_uint64_int32(uint64_t a, int32_t b)
{
    uint64_t tmp = 0;

    if (b < 0)
    {
        // So we're effectively subtracting
        tmp = safe_abs32(b);

        if (tmp <= a)
        {
            return a - tmp;
        }
    }
    else
    {
        // now we know that rhs can be safely cast into an std::uint64_t
        tmp = (uint64_t)a + (uint64_t)b;

        // We added and it did not become smaller
        if (tmp >= a)
        {
            return tmp;
        }
    }

    safe_math_fail("safe_math_fail safe_add_uint64_int32");
}

inline bool check_add_uint64_int32(uint64_t a, int32_t b, uint64_t* ret)
{
    uint64_t tmp = 0;

    if (b < 0)
    {
        // So we're effectively subtracting
        tmp = safe_abs32(b);

        if (tmp <= a)
        {
            *ret = a - tmp;
            return true;
        }
    }
    else
    {
        // now we know that rhs can be safely cast into an std::uint64_t
        tmp = (uint64_t)a + (uint64_t)b;

        // We added and it did not become smaller
        if (tmp >= a)
        {
            *ret = tmp;
            return true;
        }
    }

    return false;
}


inline uint64_t safe_add_uint64_uint32(uint64_t a, uint32_t b)
{
    uint64_t tmp = (uint64_t)a + (uint64_t)b;

    // We added and it didn't get smaller
    if (tmp >= a)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_add_uint64_uint32");
}

inline bool check_add_uint64_uint32(uint64_t a, uint32_t b, uint64_t* ret)
{
    uint64_t tmp = (uint64_t)a + (uint64_t)b;
    *ret = tmp;

    // We added and it didn't get smaller
    return (tmp >= a);
}

inline uint64_t safe_add_uint64_int64(uint64_t a, int64_t b)
{
    uint64_t tmp = 0;

    if (b < 0)
    {
        // So we're effectively subtracting
        tmp = safe_abs64(b);

        if (tmp <= a)
        {
            return a - tmp;
        }
    }
    else
    {
        // now we know that rhs can be safely cast into an std::uint64_t
        tmp = (uint64_t)a + (uint64_t)b;

        // We added and it did not become smaller
        if (tmp >= a)
        {
            return tmp;
        }
    }

    safe_math_fail("safe_math_fail safe_add_uint64_int64");
}

inline bool check_add_uint64_int64(uint64_t a, int64_t b, uint64_t* ret)
{
    uint64_t tmp = 0;

    if (b < 0)
    {
        // So we're effectively subtracting
        tmp = safe_abs64(b);

        if (tmp <= a)
        {
            *ret = a - tmp;
            return true;
        }
    }
    else
    {
        // now we know that rhs can be safely cast into an std::uint64_t
        tmp = (uint64_t)a + (uint64_t)b;

        // We added and it did not become smaller
        if (tmp >= a)
        {
            *ret = tmp;
            return true;
        }
    }

    return false;
}

inline uint64_t safe_add_uint64_uint64(uint64_t a, uint64_t b)
{
    uint64_t tmp = a + b;

    if(tmp < a)
        safe_math_fail("safe_math_fail safe_add_uint64_uint64");

    return tmp;
}

inline bool check_add_uint64_uint64(uint64_t a, uint64_t b, uint64_t* ret)
{
    uint64_t tmp = a + b;
    *ret = tmp;
    return (tmp >= a);
}

// As we're working in C, use defines
// It would be nice to use an enum, but the compiler 
// will complain that it isn't a proper C++ enum
#define SAFE_INT_MUL_FAIL 0
#define SAFE_INT_MUL_SUCCESS 1

// Multiplication primatives
#if SAFEINT_MULTIPLY_METHOD == SAFEINT_MULTIPLY_INT128

inline int MultiplyUint64(uint64_t a, uint64_t b, uint64_t* pRet)
{
    unsigned __int128 tmp = (unsigned __int128)a * (unsigned __int128)b;

    if ((tmp >> 64) == 0)
    {
        *pRet = (uint64_t)tmp;
        return SAFE_INT_MUL_SUCCESS;
    }

    return SAFE_INT_MUL_FAIL;
}

inline int MultiplyInt64(int64_t a, int64_t b, int64_t* pRet)
{
    __int128 tmp = (__int128)a * (__int128)b;
    *pRet = (int64_t)tmp;
    int64_t tmp_high = (int64_t)((unsigned __int128)tmp >> 64);

    // If only one input is negative, result must be negative, or zero
    if ((a ^ b) < 0)
    {
        if ((tmp_high == -1 && *pRet < 0) ||
            (tmp_high == 0 && *pRet == 0))
        {
            return SAFE_INT_MUL_SUCCESS;
        }
    }
    else
    {
        if (tmp_high == 0 && (uint64_t)*pRet <= (uint64_t)INT64_MAX)
        {
            return SAFE_INT_MUL_SUCCESS;
        }
    }

    return SAFE_INT_MUL_FAIL;
}

#elif SAFEINT_MULTIPLY_METHOD == SAFEINT_MULTIPLY_INTRINSICS // Implies Visual Studio compiler

// As usual, unsigned is easy
inline int MultiplyUint64(uint64_t a, uint64_t b, uint64_t * pRet)
{
    uint64_t ulHigh = 0;
    *pRet = _umul128(a, b, &ulHigh);
    return ulHigh == 0 ? SAFE_INT_MUL_SUCCESS : SAFE_INT_MUL_FAIL;
}

// Signed, is not so easy
inline int MultiplyInt64(int64_t a, int64_t b, int64_t* pRet)
{
    int64_t llHigh = 0;
    *pRet = _mul128(a, b, &llHigh);

    // Now we need to figure out what we expect
    // If llHigh is 0, then treat *pRet as unsigned
    // If llHigh is < 0, then treat *pRet as signed

    if ((a ^ b) < 0)
    {
        // Negative (or zero) result expected
        if (llHigh == -1 && *pRet < 0 ||
            llHigh == 0 && *pRet == 0)
        {
            // Everything is within range
            return SAFE_INT_MUL_SUCCESS;
        }
    }
    else
    {
        // Result should be positive
        // Check for overflow
        if (llHigh == 0 && (uint64_t)*pRet <= (uint64_t)INT64_MAX)
            return SAFE_INT_MUL_SUCCESS;
    }
    return SAFE_INT_MUL_FAIL;
}
#elif SAFEINT_MULTIPLY_METHOD == SAFEINT_MULTIPLY_BUILTIN // Implies gcc or clang

inline int MultiplyUint64(uint64_t a, uint64_t b, uint64_t* pRet)
{
    return !__builtin_umulll_overflow(a, b, (unsigned long long*)pRet) ? SAFE_INT_MUL_SUCCESS : SAFE_INT_MUL_FAIL;
}

inline int MultiplyInt64(int64_t a, int64_t b, int64_t* pRet)
{
    return !__builtin_smulll_overflow(a, b, (long long*)pRet) ? SAFE_INT_MUL_SUCCESS : SAFE_INT_MUL_FAIL;
}

#elif SAFEINT_MULTIPLY_METHOD == SAFEINT_MULTIPLY_MATH // Just going to have to do the math...

inline int MultiplyUint64(uint64_t a, uint64_t b, uint64_t* pRet)
{
    uint32_t a_high = a >> 32;
    uint32_t a_low = (uint32_t)a;
    uint32_t b_high = b >> 32;
    uint32_t b_low = (uint32_t)b;
    uint64_t tmp = 0;
    uint64_t tmp2 = 0;

    /*
    * Now we have the equivalent of (a_high * 2^32 + a_low) * (b_high * 2^32 + b_low)
    * Expanding:
    * result = a_high * b_high * 2^64 + a_high * b_low * 2^32 + b_high * a_low * 2^32 + a_low * b_low
    * We now get to short circult some things - if a_high > 0 && b_high > 0, fail
    * and this then implies that only one of the two middle expressions must be evaluated and checked if the result is >= 2^32
    * finally, do the last term, check addition
    */

    if (a_high > 0 && b_high > 0)
    {
        return SAFE_INT_MUL_FAIL;
    }

    if (a_high > 0)
    {
        tmp = (uint64_t)a_high * b_low;
    }
    else
    {
        tmp = (uint64_t)b_high * a_low;
    }

    if (tmp >> 32 != 0)
    {
        return SAFE_INT_MUL_FAIL;
    }

    tmp2 = (uint64_t)a_low * b_low;
    *pRet = (tmp << 32) + tmp2;
    return *pRet >= tmp2 ? SAFE_INT_MUL_SUCCESS : SAFE_INT_MUL_FAIL;
}

inline int MultiplyInt64(int64_t a, int64_t b, int64_t* pRet)
{
    bool aNegative = false;
    bool bNegative = false;

    uint64_t tmp = 0;
    int64_t a1 = a;
    int64_t b1 = b;

    if (a1 < 0)
    {
        aNegative = true;
        a1 = (int64_t)safe_abs64(a1);
    }

    if (b1 < 0)
    {
        bNegative = true;
        b1 = (int64_t)safe_abs64(b);
    }

    if (MultiplyUint64((uint64_t)a1, (uint64_t)b1, &tmp))
    {
        // The unsigned multiplication didn't overflow
        if (aNegative ^ bNegative)
        {
            // Result must be negative
            if (tmp <= (uint64_t)INT64_MIN)
            {
                *pRet = (int64_t)negate64((int64_t)tmp);
                return SAFE_INT_MUL_SUCCESS;
            }
        }
        else
        {
            // Result must be positive
            if (tmp <= (uint64_t)INT64_MAX)
            {
                *pRet = (int64_t)tmp;
                return SAFE_INT_MUL_SUCCESS;
            }
        }
    }

    return SAFE_INT_MUL_FAIL;
}

#else // Shouldn't happen, go find out what's broken
// If you are aware of intrinsics for some other platform, please file an issue
# error Intrinsics enabled, no available intrinics defined
#endif

inline int32_t safe_mul_int32_int32(int32_t a, int32_t b)
{
    int64_t tmp = (int64_t)a * (int64_t)b;
    return safe_cast_int32_int64(tmp);
}

inline bool check_mul_int32_int32(int32_t a, int32_t b, int32_t* ret)
{
    int64_t tmp = (int64_t)a * (int64_t)b;
    *ret = (int32_t)tmp;
    return check_cast_int32_int64(tmp) == 0;
}

inline int32_t safe_mul_int32_uint32(int32_t a, uint32_t b)
{
    int64_t tmp = (int64_t)a * (int64_t)b;
    return safe_cast_int32_int64(tmp);
}

inline bool check_mul_int32_uint32(int32_t a, uint32_t b, int32_t* ret)
{
    int64_t tmp = (int64_t)a * (int64_t)b;
    *ret = (int32_t)tmp;
    return check_cast_int32_int64(tmp) == 0;
}

inline int32_t safe_mul_int32_int64(int32_t a, int64_t b)
{
    int64_t tmp = 0;

    if (MultiplyInt64((int64_t)a, b, &tmp))
    {
        return safe_cast_int32_int64(tmp);
    }

    safe_math_fail("safe_math_fail safe_mul_int32_int64");
}

inline bool check_mul_int32_int64(int32_t a, int64_t b, int32_t* ret)
{
    int64_t tmp = 0;

    if (MultiplyInt64((int64_t)a, b, &tmp))
    {
        *ret = (int32_t)tmp;
        return check_cast_int32_int64(tmp) == 0;
    }

    return false;
}

inline int32_t safe_mul_int32_uint64(int32_t a, uint64_t b)
{
    uint64_t tmp = 0;
    if (a < 0)
    {
        // Flip sign, use the unsigned function
        uint64_t a2 = safe_abs64(a);
        if (MultiplyUint64(a2, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= (uint64_t)INT32_MAX + 1)
        {
            // Not too big, flip it back
            return (int32_t)(tmp + 1);
        }
    }
    else
    {
        if (MultiplyUint64((uint64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= INT32_MAX)
        {
            return (int32_t)tmp;
        }
    }
 
    safe_math_fail("safe_math_fail safe_mul_int32_uint64");
}

inline bool check_mul_int32_uint64(int32_t a, uint64_t b, int32_t* ret)
{
    uint64_t tmp = 0;
    if (a < 0)
    {
        // Flip sign, use the unsigned function
        uint64_t a2 = safe_abs64(a);
        if (MultiplyUint64(a2, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= (uint64_t)INT32_MAX + 1)
        {
            // Not too big, flip it back
            *ret = (int32_t)(tmp + 1);
            return true;
        }
    }
    else
    {
        if (MultiplyUint64((uint64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= INT32_MAX)
        {
            *ret = (int32_t)tmp;
            return true;
        }
    }

    return false;
}

inline uint32_t safe_mul_uint32_int32(uint32_t a, int32_t b)
{
    int64_t tmp = (int64_t)a * (int64_t)b;
    return safe_cast_uint32_int64(tmp);
}

inline bool check_mul_uint32_int32(uint32_t a, int32_t b, uint32_t* ret)
{
    int64_t tmp = (int64_t)a * (int64_t)b;
    *ret = (uint32_t)tmp;
    return check_cast_uint32_int64(tmp) == 0;
}

inline uint32_t safe_mul_uint32_uint32(uint32_t a, uint32_t b)
{
    uint64_t tmp = (uint64_t)a * (uint64_t)b;
    return safe_cast_uint32_uint64(tmp);
}

inline bool check_mul_uint32_uint32(uint32_t a, uint32_t b, uint32_t* ret)
{
    uint64_t tmp = (uint64_t)a * (uint64_t)b;
    *ret = (uint32_t)tmp;
    return check_cast_uint32_uint64(tmp) == 0;
}

inline uint32_t safe_mul_uint32_int64(uint32_t a, int64_t b)
{
    int64_t tmp = 0;

    if (MultiplyInt64((int64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= UINT32_MAX && tmp >= 0)
    {
        return (uint32_t)tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_uint32_int64");
}

inline bool check_mul_uint32_int64(uint32_t a, int64_t b, uint32_t* ret)
{
    int64_t tmp = 0;

    if (MultiplyInt64((int64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= UINT32_MAX && tmp >= 0)
    {
        *ret = (uint32_t)tmp;
        return true;
    }

    return false;
}

inline uint32_t safe_mul_uint32_uint64(uint32_t a, uint64_t b)
{
    uint64_t tmp = 0;

    if (MultiplyUint64((uint64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= UINT32_MAX)
    {
        return (uint32_t)tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_uint32_uint64");
}

inline bool check_mul_uint32_uint64(uint32_t a, uint64_t b, uint32_t* ret)
{
    uint64_t tmp = 0;

    if (MultiplyUint64((uint64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= UINT32_MAX)
    {
        *ret = (uint32_t)tmp;
        return true;
    }

    return false;
}

inline int64_t safe_mul_int64_int32(int64_t a, int32_t b)
{
    int64_t tmp = 0;

    if (MultiplyInt64(a, (int64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_int64_int32");
}

inline bool check_mul_int64_int32(int64_t a, int32_t b, int64_t* ret)
{
    int64_t tmp = 0;

    if (MultiplyInt64(a, (int64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        *ret = tmp;
        return true;
    }

    return false;
}

inline int64_t safe_mul_int64_uint32(int64_t a, uint32_t b)
{
    int64_t tmp = 0;

    if (MultiplyInt64(a, (int64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_int64_uint32");
}

inline bool check_mul_int64_uint32(int64_t a, uint32_t b, int64_t* ret)
{
    int64_t tmp = 0;

    if (MultiplyInt64(a, (int64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        *ret = tmp;
        return true;
    }

    return false;
}

inline int64_t safe_mul_int64_int64(int64_t a, int64_t b)
{
    int64_t tmp = 0;

    if (MultiplyInt64(a, b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_int64_int64");
}

inline bool check_mul_int64_int64(int64_t a, int64_t b, int64_t* ret)
{
    int64_t tmp = 0;

    if (MultiplyInt64(a, b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        *ret = tmp;
        return true;
    }

    return false;
}

inline int64_t safe_mul_int64_uint64(int64_t a, uint64_t b)
{
    uint64_t tmp = 0;

    if (a < 0)
    {
        uint64_t a2 = safe_abs64(a);

        if (MultiplyUint64(a2, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= (uint64_t)0x8000000000000000)
        {
            return negate64((int64_t)tmp);
        }
    }
    else
    {
        if (MultiplyUint64((uint64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= (uint64_t)INT64_MAX)
        {
            return (int64_t)tmp;
        }
    }

    safe_math_fail("safe_math_fail safe_mul_int64_uint64");
}

inline bool check_mul_int64_uint64(int64_t a, uint64_t b, int64_t* ret)
{
    uint64_t tmp = 0;

    if (a < 0)
    {
        uint64_t a2 = safe_abs64(a);

        if (MultiplyUint64(a2, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= (uint64_t)0x8000000000000000)
        {
            *ret = negate64((int64_t)tmp);
            return true;
        }
    }
    else
    {
        if (MultiplyUint64((uint64_t)a, b, &tmp) == SAFE_INT_MUL_SUCCESS && tmp <= (uint64_t)INT64_MAX)
        {
            *ret = (int64_t)tmp;
            return true;
        }
    }

    return false;
}

inline uint64_t safe_mul_uint64_int32(uint64_t a, int32_t b)
{
    uint64_t tmp;

    if (b < 0)
    {
        if (a == 0)
            return 0;

        safe_math_fail("safe_math_fail safe_mul_uint64_int32");
    }
   
    if (MultiplyUint64(a, (uint64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_uint64_int32");
}

inline bool check_mul_uint64_int32(uint64_t a, int32_t b, uint64_t* ret)
{
    uint64_t tmp;

    if (b < 0)
    {
        if (a == 0)
        {
            *ret = 0;
            return true;
        }

        return false;
    }

    if (MultiplyUint64(a, (uint64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        *ret = tmp;
        return true;
    }

    return false;
}

inline uint64_t safe_mul_uint64_uint32(uint64_t a, uint32_t b)
{
    uint64_t tmp;

    if (MultiplyUint64(a, (uint64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_uint64_uint32");
}

inline bool check_mul_uint64_uint32(uint64_t a, uint32_t b, uint64_t* ret)
{
    uint64_t tmp;

    if (MultiplyUint64(a, (uint64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        *ret = tmp;
        return true;
    }

    return false;
}

inline uint64_t safe_mul_uint64_int64(uint64_t a, int64_t b)
{
    uint64_t tmp;

    if (b < 0)
    {
        if (a == 0)
            return 0;

        safe_math_fail("safe_math_fail safe_mul_uint64_int32");
    }

    if (MultiplyUint64(a, (uint64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_uint64_int64");
}

inline bool check_mul_uint64_int64(uint64_t a, int64_t b, uint64_t* ret)
{
    uint64_t tmp;

    if (b < 0)
    {
        if (a == 0)
        {
            *ret = 0;
            return true;
        }

        return false;
    }

    if (MultiplyUint64(a, (uint64_t)b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        *ret = tmp;
        return true;
    }

    return false;
}

inline uint64_t safe_mul_uint64_uint64(uint64_t a, uint64_t b)
{
    uint64_t tmp;

    if (MultiplyUint64(a, b, &tmp) == SAFE_INT_MUL_SUCCESS)
    {
        return tmp;
    }

    safe_math_fail("safe_math_fail safe_mul_uint64_uint64");
}

inline bool check_mul_uint64_uint64(uint64_t a, uint64_t b, uint64_t* ret)
{
    return (MultiplyUint64(a, b, ret) == SAFE_INT_MUL_SUCCESS);
}

inline int32_t safe_div_int32_int32(int32_t a, int32_t b)
{
    if (b != 0 && !(a == INT32_MIN && b == -1))
    {
        return a / b;
    }
    safe_math_fail("safe_math_fail safe_div_int32_int32");
}

inline bool check_div_int32_int32(int32_t a, int32_t b, int32_t* ret)
{
    if (b != 0 && !(a == INT32_MIN && b == -1))
    {
        *ret = a / b;
        return true;
    }
    return false;
}

inline int32_t safe_div_int32_uint32(int32_t a, uint32_t b)
{
    if (b != 0)
    {
        return (int32_t)((int64_t)a / (int64_t)b);
    }
    safe_math_fail("safe_math_fail safe_div_int32_uint32");
}

inline bool check_div_int32_uint32(int32_t a, uint32_t b, int32_t* ret)
{
    if (b != 0)
    {
        *ret = (int32_t)((int64_t)a / (int64_t)b);
        return true;
    }

    return false;
}

inline int32_t safe_div_int32_int64(int32_t a, int64_t b)
{
    if (b != 0 && !(a == INT32_MIN && b == -1))
    {
        return (int32_t)(a / b);
    }
    safe_math_fail("safe_math_fail safe_div_int32_int64");
}

inline bool check_div_int32_int64(int32_t a, int64_t b, int32_t* ret)
{
    if (b != 0 && !(a == INT32_MIN && b == -1))
    {
        *ret = (int32_t)(a / b);
        return true;
    }

    return false;
}

inline int32_t safe_div_int32_uint64(int32_t a, uint64_t b)
{
    if (b == 0)
    {
        safe_math_fail("safe_math_fail safe_div_int32_uint64");
    }

    if (a > 0)
    {
        return (int32_t)((uint64_t)a / b);
    }
    else
    {
        uint64_t a2 = (uint64_t)safe_abs32(a);
        a2 /= b;
        return (int32_t)negate32((int32_t)a2);
    }
}

inline bool check_div_int32_uint64(int32_t a, uint64_t b, int32_t* ret)
{
    if (b == 0)
    {
        return false;
    }

    if (a > 0)
    {
        *ret = (int32_t)((uint64_t)a / b);
        return true;
    }
    else
    {
        uint64_t a2 = (uint64_t)safe_abs32(a);
        a2 /= b;
        *ret = (int32_t)negate32((int32_t)a2);
        return true;
    }
}

inline uint32_t safe_div_uint32_int32(uint32_t a, int32_t b)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        safe_math_fail("safe_math_fail safe_div_uint32_int32");
    }

    if (a == 0) // zero divided by anything is zero
    {
        return 0;
    }

    if (b > 0) // if b is positive, just do the math
    {
        return (a / (uint32_t)b);
    }
    else // now have to check magnitude
    {
        uint32_t tmp = safe_abs32(b);

        if (a < tmp)
        {
            return 0;
        }
    }

    safe_math_fail("safe_math_fail safe_div_uint32_int32");
}

inline bool check_div_uint32_int32(uint32_t a, int32_t b, uint32_t* ret)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        return false;
    }

    if (a == 0) // zero divided by anything is zero
    {
        *ret = 0;
        return true;
    }

    if (b > 0) // if b is positive, just do the math
    {
        *ret = (a / (uint32_t)b);
        return true;
    }
    else // now have to check magnitude
    {
        uint32_t tmp = safe_abs32(b);

        if (a < tmp)
        {
            *ret = 0;
            return true;
        }
    }

    return false;
}

inline uint32_t safe_div_uint32_uint32(uint32_t a, uint32_t b)
{
    if (b > 0)
    {
        return (uint32_t)(a / b);
    }
    safe_math_fail("safe_math_fail safe_div_uint32_uint32");
}

inline bool check_div_uint32_uint32(uint32_t a, uint32_t b, uint32_t* ret)
{
    if (b > 0)
    {
        *ret = (uint32_t)(a / b);
        return true;
    }

    return false;
}

inline uint32_t safe_div_uint32_int64(uint32_t a, int64_t b)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        safe_math_fail("safe_math_fail safe_div_uint32_int64");
    }

    if (a == 0) // zero divided by anything is zero
    {
        return 0;
    }

    if (b > 0) // if b is positive, just do the math
    {
        return (uint32_t)(a / b);
    }
    else // now have to check magnitude
    {
        uint64_t tmp = safe_abs64(b);

        if (a < tmp)
        {
            return 0;
        }
    }

    safe_math_fail("safe_math_fail safe_div_uint32_int64");
}

inline bool check_div_uint32_int64(uint32_t a, int64_t b, uint32_t* ret)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        return false;
    }

    if (a == 0) // zero divided by anything is zero
    {
        *ret = 0;
        return true;
    }

    if (b > 0) // if b is positive, just do the math
    {
        *ret = (uint32_t)(a / b);
        return true;
    }
    else // now have to check magnitude
    {
        uint64_t tmp = safe_abs64(b);

        if (a < tmp)
        {
            *ret = 0;
            return true;
        }
    }

    return false;
}

inline uint32_t safe_div_uint32_uint64(uint32_t a, uint64_t b)
{
    if (b > 0)
    {
        return (uint32_t)(a / b);
    }
    safe_math_fail("safe_math_fail safe_div_uint32_uint64");
}

inline bool check_div_uint32_uint64(uint32_t a, uint64_t b, uint32_t* ret)
{
    if (b > 0)
    {
        *ret = (uint32_t)(a / b);
        return true;
    }
    return false;
}

inline int64_t safe_div_int64_int32(int64_t a, int32_t b)
{
    if(b == 0 || (b == -1 && a == INT64_MIN))
        safe_math_fail("safe_math_fail safe_div_int64_int32");

    return a / b;
}

inline bool check_div_int64_int32(int64_t a, int32_t b, int64_t* ret)
{
    if (b == 0 || (b == -1 && a == INT64_MIN))
        return false;

    *ret = a / b;
    return true;
}

inline int64_t safe_div_int64_uint32(int64_t a, uint32_t b)
{
    if (b == 0)
        safe_math_fail("safe_math_fail safe_div_int64_int32");

    return a / b;
}

inline bool check_div_int64_uint32(int64_t a, uint32_t b, int64_t* ret)
{
    if (b == 0)
        return false;

    *ret = a / b;
    return true;
}

inline int64_t safe_div_int64_int64(int64_t a, int64_t b)
{
    if (b == 0 || (b == -1 && a == INT64_MIN))
        safe_math_fail("safe_math_fail safe_div_int64_int32");

    return a / b;
}

inline bool check_div_int64_int64(int64_t a, int64_t b, int64_t* ret)
{
    if (b == 0 || (b == -1 && a == INT64_MIN))
        return false;

    *ret = a / b;
    return true;

}

inline int64_t safe_div_int64_uint64(int64_t a, uint64_t b)
{
    if (b == 0)
        safe_math_fail("safe_math_fail safe_div_int64_int32");

    if(a >= 0)
    {
        return (int64_t)((uint64_t)a / b);
    }
    else
    {
        // Need to get the magnitude, divide, and then negate
        uint64_t tmp = safe_abs64(a);
        tmp /= b;
        return negate64((int64_t)tmp);
    }
}

inline bool check_div_int64_uint64(int64_t a, uint64_t b, int64_t* ret)
{
    if (b == 0)
        return false;

    if(a >= 0)
    {
        *ret = (int64_t)((uint64_t)a / b);
    }
    else
    {
        // Need to get the magnitude, divide, and then negate
        uint64_t tmp = safe_abs64(a);
        tmp /= b;
        *ret = negate64((int64_t)tmp);
    }
        return true;
}

inline uint64_t safe_div_uint64_int32(uint64_t a, int32_t b)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        safe_math_fail("safe_math_fail safe_div_int64_int32");
    }

    if (a == 0) // zero divided by anything is zero
    {
        return 0;
    }

    if (b > 0) // if b is positive, just do the math
    {
        return a / (uint64_t)b;
    }
    else // now have to check magnitude
    {
        uint32_t tmp = safe_abs32(b);

        if (a < tmp)
        {
            return 0;
        }
    }

    safe_math_fail("safe_math_fail safe_div_int64_int32");
}

inline bool check_div_uint64_int32(uint64_t a, int32_t b, uint64_t* ret)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        return false;
    }

    if (a == 0) // zero divided by anything is zero
    {
        *ret = 0;
        return true;
    }

    if (b > 0) // if b is positive, just do the math
    {
        *ret = a / (uint64_t)b;
        return true;
    }
    else // now have to check magnitude
    {
        uint32_t tmp = safe_abs32(b);

        if (a < tmp)
        {
            *ret = 0;
            return true;
        }
    }

    return false;
}

inline uint64_t safe_div_uint64_uint32(uint64_t a, uint32_t b)
{
    if (b != 0)
        return a / b;

    safe_math_fail("safe_math_fail safe_div_int64_uint32");
}

inline bool check_div_uint64_uint32(uint64_t a, uint32_t b, uint64_t* ret)
{
    if (b != 0)
    {
        *ret = a / b;
        return true;
    }
    
    return false;
}

inline uint64_t safe_div_uint64_int64(uint64_t a, int64_t b)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        safe_math_fail("safe_math_fail safe_div_int64_int32");
    }

    if (a == 0) // zero divided by anything is zero
    {
        return 0;
    }

    if (b > 0) // if b is positive, just do the math
    {
        return a / (uint64_t)b;
    }
    else // now have to check magnitude
    {
        uint64_t tmp = safe_abs64(b);

        if (a < tmp)
        {
            return 0;
        }
    }

    safe_math_fail("safe_math_fail safe_div_int64_int32");
}

inline bool check_div_uint64_int64(uint64_t a, int64_t b, uint64_t* ret)
{
    // Follow original SafeInt logic for this case
    if (b == 0) // div 0 always a problem
    {
        return false;
    }

    if (a == 0) // zero divided by anything is zero
    {
        *ret = 0;
        return true;
    }

    if (b > 0) // if b is positive, just do the math
    {
        *ret = a / (uint64_t)b;
        return true;
    }
    else // now have to check magnitude
    {
        uint64_t tmp = safe_abs64(b);

        if (a < tmp)
        {
            *ret = 0;
            return true;
        }
    }

    return false;
}

inline uint64_t safe_div_uint64_uint64(uint64_t a, uint64_t b)
{
    if (b != 0)
        return a / b;

    safe_math_fail("safe_math_fail safe_div_int64_uint32");
}

inline bool check_div_uint64_uint64(uint64_t a, uint64_t b, uint64_t* ret)
{
    if (b != 0)
    {
        *ret = a / b;
        return true;
    }

    return false;
}

inline int32_t safe_sub_int32_int32(int32_t a, int32_t b)
{
    int64_t tmp = (int64_t)a - (int64_t)b;
    return safe_cast_int32_int64(tmp);
}

inline bool check_sub_int32_int32(int32_t a, int32_t b, int32_t* ret)
{
    int64_t tmp = (int64_t)a - (int64_t)b;
    *ret = (int32_t)tmp;
    return check_cast_int32_int64(tmp) == 0;
}

inline int32_t safe_sub_int32_uint32(int32_t a, uint32_t b)
{
    int64_t tmp = (int64_t)a - (int64_t)b;
    return safe_cast_int32_int64(tmp);
}

inline bool check_sub_int32_uint32(int32_t a, uint32_t b, int32_t* ret)
{
    int64_t tmp = (int64_t)a - (int64_t)b;
    *ret = (int32_t)tmp;
    return check_cast_int32_int64(tmp) == 0;
}

inline int32_t safe_sub_int32_int64(int32_t a, int64_t b)
{
    // We have 4 fairly complex cases:
    // lhs positive, rhs positive - rhs could be larger than lhs can represent
    // lhs positive, rhs negative - additive case - check tmp >= lhs and tmp > max int
    // lhs negative, rhs positive - check tmp <= lhs and tmp < min int
    // lhs negative, rhs negative - addition cannot internally overflow, check against max

    int64_t tmp = (int64_t)((uint64_t)a - (uint64_t)b);

    if (a >= 0)
    {
        // first case
        if (b >= 0)
        {
            if (tmp >= INT32_MIN)
            {
                return (int32_t)tmp;
            }
        }
        else
        {
            // second case
            if (tmp >= a && tmp <= INT32_MAX)
            {
                return (int32_t)tmp;
            }
        }
    }
    else
    {
        // lhs < 0
        // third case
        if (b >= 0)
        {
            if (tmp <= a && tmp >= INT32_MIN)
            {
                return (int32_t)tmp;
            }
        }
        else
        {
            // fourth case
            if (tmp <= INT32_MAX)
            {
                return (int32_t)tmp;
            }
        }
    }

    safe_math_fail("safe_math_fail safe_sub_int32_int64");
}

inline bool check_sub_int32_int64(int32_t a, int64_t b, int32_t* ret)
{
    // See above for documentation
    int64_t tmp = (int64_t)((uint64_t)a - (uint64_t)b);

    if (a >= 0)
    {
        // first case
        if (b >= 0)
        {
            if (tmp >= INT32_MIN)
            {
                *ret = (int32_t)tmp;
                return true;
            }
        }
        else
        {
            // second case
            if (tmp >= a && tmp <= INT32_MAX)
            {
                *ret = (int32_t)tmp;
                return true;
            }
        }
    }
    else
    {
        // lhs < 0
        // third case
        if (b >= 0)
        {
            if (tmp <= a && tmp >= INT32_MIN)
            {
                *ret = (int32_t)tmp;
                return true;
            }
        }
        else
        {
            // fourth case
            if (tmp <= INT32_MAX)
            {
                *ret = (int32_t)tmp;
                return true;
            }
        }
    }

    return false;
}

inline int32_t safe_sub_int32_uint64(int32_t a, uint64_t b)
{
    // We need the absolute value of INT32_MIN
    // This will give it to us without extraneous compiler warnings
    const uint64_t AbsMinInt32 = (uint64_t)INT32_MAX + 1;

    if (a < 0)
    {
        if (b <= AbsMinInt32 - safe_abs32(a))
        {
            return (int32_t)(a - (int64_t)b);
        }
    }
    else
    {
        if (b <= AbsMinInt32 + (uint64_t)a)
        {
            return (int32_t)(a - (int64_t)b);
        }
    }

    safe_math_fail("safe_math_fail safe_sub_int32_uint64");
}

inline bool check_sub_int32_uint64(int32_t a, uint64_t b, int32_t* ret)
{
    // We need the absolute value of INT32_MIN
    // This will give it to us without extraneous compiler warnings
    const uint64_t AbsMinInt32 = (uint64_t)INT32_MAX + 1;

    if (a < 0)
    {
        if (b <= AbsMinInt32 - safe_abs32(a))
        {
            *ret = (int32_t)(a - (int64_t)b);
            return true;
        }
    }
    else
    {
        if (b <= AbsMinInt32 + (uint64_t)a)
        {
            *ret = (int32_t)((int64_t)a - (int64_t)b);
            return true;
        }
    }

    return false;
}

inline uint32_t safe_sub_uint32_int32(uint32_t a, int32_t b)
{
    int64_t tmp = (int64_t)a - (int64_t)b;
    return safe_cast_uint32_int64(tmp);
}

inline bool check_sub_uint32_int32(uint32_t a, int32_t b, uint32_t* ret)
{
    int64_t tmp = (int64_t)a - (int64_t)b;
    *ret = (uint32_t)tmp;
    return check_cast_uint32_int64(tmp) == 0;
}

inline uint32_t safe_sub_uint32_uint32(uint32_t a, uint32_t b)
{
    if (a >= b)
        return a - b;

    safe_math_fail("safe_math_fail safe_sub_uint32_uint32");
}

inline bool check_sub_uint32_uint32(uint32_t a, uint32_t b, uint32_t* ret)
{
    if (a >= b)
    {
        *ret = a - b;
        return true;
    }

    return false;
}

inline uint32_t safe_sub_uint32_int64(uint32_t a, int64_t b)
{
    // must first see if rhs is positive or negative
    if (b >= 0)
    {
        if ((uint64_t)b <= a)
        {
            return (uint32_t)(a - (uint32_t)b);
        }
    }
    else
    {
        // we're now effectively adding
        // since lhs is 32-bit, and rhs cannot exceed 2^63
        // this addition cannot overflow
        uint64_t tmp = a + (uint64_t)negate64(b); // negation safe

        // but we could exceed UINT32_MAX
        if (tmp <= UINT32_MAX)
        {
            return (uint32_t)tmp;
        }
    }

    safe_math_fail("safe_math_fail safe_sub_uint32_int64");
}

inline bool check_sub_uint32_int64(uint32_t a, int64_t b, uint32_t* ret)
{
    // must first see if rhs is positive or negative
    if (b >= 0)
    {
        if ((uint64_t)b <= a)
        {
            *ret = (uint32_t)(a - (uint32_t)b);
            return true;
        }
    }
    else
    {
        // we're now effectively adding
        // since lhs is 32-bit, and rhs cannot exceed 2^63
        // this addition cannot overflow
        uint64_t tmp = a + (uint64_t)negate64(b); // negation safe

        // but we could exceed UINT32_MAX
        if (tmp <= UINT32_MAX)
        {
            *ret = (uint32_t)tmp;
            return true;
        }
    }

    return false;
}

inline uint32_t safe_sub_uint32_uint64(uint32_t a, uint64_t b)
{
    if (a >= b)
        return (uint32_t)(a - b);

    safe_math_fail("safe_math_fail safe_sub_uint32_uint64");
}

inline bool check_sub_uint32_uint64(uint32_t a, uint64_t b, uint32_t* ret)
{
    if (a >= b)
    {
        *ret = (uint32_t)(a - b);
        return true;
    }
    return false;
}

inline int64_t safe_sub_int64_int32(int64_t a, int32_t b)
{
    // we have essentially 4 cases:
    //
    // 1) lhs positive, rhs positive - overflow not possible
    // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error
    // 3) lhs negative, rhs positive - check result <= lhs
    // 4) lhs negative, rhs negative - overflow not possible

    int64_t tmp = (int64_t)((uint64_t)a - (uint64_t)b);

    // Note - ideally, we can order these so that true conditionals
    // lead to success, which enables better pipelining
    // It isn't practical here
    if ((a >= 0 && b < 0 && tmp < a) || // condition 2
        (b >= 0 && tmp > a))              // condition 3
    {
        safe_math_fail("safe_math_fail safe_sub_int64_int32");
    }

    return tmp;
}

inline bool check_sub_int64_int32(int64_t a, int32_t b, int64_t* ret)
{
    int64_t tmp = (int64_t)((uint64_t)a - (uint64_t)b);

    // Note - ideally, we can order these so that true conditionals
    // lead to success, which enables better pipelining
    // It isn't practical here
    if ((a >= 0 && b < 0 && tmp < a) || // condition 2
        (b >= 0 && tmp > a))              // condition 3
    {
        return false;
    }

    *ret = tmp;
    return true;
}

inline int64_t safe_sub_int64_uint32(int64_t a, uint32_t b)
{
    // lhs is a 64-bit int, rhs unsigned int32 or smaller
    // perform test as unsigned to prevent unwanted optimizations
    uint64_t tmp = (uint64_t)a - (uint64_t)b;

    if ((int64_t)tmp <= a)
    {
        return (int64_t)tmp;
    }

    safe_math_fail("safe_math_fail safe_sub_int64_int64");
}

inline bool check_sub_int64_uint32(int64_t a, uint32_t b, int64_t* ret)
{
    uint64_t tmp = (uint64_t)a - (uint64_t)b;

    if ((int64_t)tmp <= a)
    {
        *ret = (int64_t)tmp;
        return true;
    }

    return false;
}

inline int64_t safe_sub_int64_int64(int64_t a, int64_t b)
{
    // we have essentially 4 cases:
    //
    // 1) lhs positive, rhs positive - overflow not possible
    // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error
    // 3) lhs negative, rhs positive - check result <= lhs
    // 4) lhs negative, rhs negative - overflow not possible

    int64_t tmp = (int64_t)((uint64_t)a - (uint64_t)b);

    // Note - ideally, we can order these so that true conditionals
    // lead to success, which enables better pipelining
    // It isn't practical here
    if ((a >= 0 && b < 0 && tmp < a) || // condition 2
        (b >= 0 && tmp > a))              // condition 3
    {
        safe_math_fail("safe_math_fail safe_sub_int64_int64");
    }

    return tmp;
}

inline bool check_sub_int64_int64(int64_t a, int64_t b, int64_t* ret)
{
    int64_t tmp = (int64_t)((uint64_t)a - (uint64_t)b);

    // Note - ideally, we can order these so that true conditionals
    // lead to success, which enables better pipelining
    // It isn't practical here
    if ((a >= 0 && b < 0 && tmp < a) || // condition 2
        (b >= 0 && tmp > a))              // condition 3
    {
        return false;
    }

    *ret = tmp;
    return true;
}

inline int64_t safe_sub_int64_uint64(int64_t a, uint64_t b)
{
    // if we subtract, and it gets larger, there's a problem
    // Perform test as unsigned to prevent unwanted optimizations
    uint64_t tmp = (uint64_t)a - b;

    if ((int64_t)tmp <= a)
    {
        return (int64_t)tmp;
    }

    safe_math_fail("safe_math_fail safe_sub_int64_uint64");
}

inline bool check_sub_int64_uint64(int64_t a, uint64_t b, int64_t* ret)
{
    uint64_t tmp = (uint64_t)a - b;
    *ret = (int64_t)tmp;

    return ((int64_t)tmp <= a);
}

inline uint64_t safe_sub_uint64_int32(uint64_t a, int32_t b)
{
    // lhs is an uint64_t, rhs signed
    // must first see if rhs is positive or negative
    if (b >= 0)
    {
        if ((uint64_t)b <= a)
        {
            return (uint64_t)(a - (uint64_t)b);
        }
    }
    else
    {
        uint64_t tmp = a;
        // we're now effectively adding
        uint64_t result = a + safe_abs64(b);

        if (result >= tmp)
            return result;
    }

    safe_math_fail("safe_math_fail safe_sub_uint64_int32");
}

inline bool check_sub_uint64_int32(uint64_t a, int32_t b, uint64_t* ret)
{
    if (b >= 0)
    {
        if ((uint64_t)b <= a)
        {
            *ret = (uint64_t)(a - (uint64_t)b);
            return true;
        }
    }
    else
    {
        uint64_t tmp = a;
        // we're now effectively adding
        uint64_t result = a + safe_abs64(b);

        if (result >= tmp)
        {
            *ret = result;
            return true;
        }
    }

    return false;
}

inline uint64_t safe_sub_uint64_uint32(uint64_t a, uint32_t b)
{
    uint64_t tmp = a - b;

    if (tmp <= a)
        return tmp;

    safe_math_fail("safe_math_fail safe_sub_uint64_uint32");
}

inline bool check_sub_uint64_uint32(uint64_t a, uint32_t b, uint64_t* ret)
{
    uint64_t tmp = a - b;
    *ret = tmp;
    return (tmp <= a);
}

inline uint64_t safe_sub_uint64_int64(uint64_t a, int64_t b)
{
    uint64_t result = 0;

    // must first see if rhs is positive or negative
    if (b >= 0)
    {
        if ((uint64_t)b <= a)
        {
            return (a - (uint64_t)b);
        }
    }
    else
    {
        // we're now effectively adding
        result = a + safe_abs64(b);

        if (result >= a)
            return result;
    }

    safe_math_fail("safe_math_fail safe_sub_uint64_int64");
}

inline bool check_sub_uint64_int64(uint64_t a, int64_t b, uint64_t* ret)
{
    uint64_t result = 0;

    // must first see if rhs is positive or negative
    if (b >= 0)
    {
        if ((uint64_t)b <= a)
        {
            *ret = (a - (uint64_t)b);
            return true;
        }
    }
    else
    {
        // we're now effectively adding
        result = a + safe_abs64(b);

        if (result >= a)
        {
            *ret = result;
            return true;
        }
    }

    return false;
}

inline uint64_t safe_sub_uint64_uint64(uint64_t a, uint64_t b)
{
    uint64_t tmp = a - b;

    if (tmp <= a)
        return tmp;

    safe_math_fail("safe_math_fail safe_sub_uint64_uint64");
}

inline bool check_sub_uint64_uint64(uint64_t a, uint64_t b, uint64_t* ret)
{
    uint64_t tmp = a - b;
    *ret = tmp;
    return (tmp <= a);
}

#ifdef __cplusplus
} 
#endif

#endif // C_SAFE_MATH_IMPL
