Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pow for Integral Values

I need a version of pow for integers. I have 2 problems that I need to solve with pow:

  1. If the result is larger than my integral type I need to clamp to numeric_limits::max()
  2. I need to be able to deal with 41.99999 rounding up to 42, rather than down to 41

Does C++ provide me some kind of inline solution here, or am I stuck writing my own function:

template <typename T>
enable_if_t<is_integral_v<T>, T> mypow(const T base, unsigned int exp) {
    T result = exp == 0U ? base : 1;

    while(exp-- > 1U) {
        if(numeric_limits<T>::max() / result <= base) return numeric_limits<T>::max();
        result *= base;
    }
    return result;
}
like image 941
Jonathan Mee Avatar asked Jun 15 '17 15:06

Jonathan Mee


2 Answers

Does C++ provide me some kind of inline solution here

No, there is no integer pow in the standard library.

or am I stuck writing my own function

Yes, you get to write your own function. Note that your shown multiplication loop may be slower than using std::pow to implement the function, especially since you also have a branch and division in the loop:

template<class I>
I int_pow_no_overflow(I base, I exp)
{
    double max = std::numeric_limits<I>::max();
    double result = std::round(std::pow(base, exp));
    return result >= max
        ? max
        : result;
}

For a more generic approach, you may want to consider underflow as well.

There are as well other, faster algorithms (see for example Exponentiation by squaring) for integer exponentiation than the linear one you showed, but I'm unsure whether it would be worth considering them unless you deal with arbitrary precision arithmetic, or an embedded system with no floating point unit.

like image 90
eerorika Avatar answered Sep 20 '22 16:09

eerorika


Your code did not compile, you should if possible check your code compiles first, use your compiler, or check it on compiler explorer first.

Also, you forgot to take into account negative values. That's a very important feature of integral powers. Code below is for regular int type. I'll let you explore how you could extend it for other integral types.

#include <type_traits>
#include <iostream>
#include <cmath>
#include <limits>

using namespace std;

template <typename T>
enable_if_t< is_integral<T>::value, T> 
mypow(T base, unsigned int exp) 
{
    T result = T(1);
    bool sign = (base < 0);

    if (sign) base = -base;

    T temp = result;
    while(exp-- != 0) 
    {
        temp *= base;
        if (temp < result)
        {
            return (sign) ? numeric_limits<T>::min() 
                          : numeric_limits<T>::max();
        }
        result = temp;
    }
    return (sign && (exp & 1)) ? -result : result;
}

template <typename T>
enable_if_t< !is_integral<T>::value, int> 
mypow(const T& base, unsigned int exp) 
{
    T result = T(1);
    int i_base = int(floor(base + .5));
    bool sign = (i_base < 0);

    if (sign) i_base = -i_base;

    int temp = result;
    while(exp-- != 0) 
    {
        temp *= i_base;
        if (temp < result)
        {
            return (sign) ? numeric_limits<int>::min() : numeric_limits<int>::max();
        }
        result = temp;
    }
    return (sign && (exp & 1)) ? -result : result;
}

In real life, I would do this note the use of floor, even in the integral case.

  template<typename T>
   enable_if_t< is_integral<T>::value, T> 
   mypow(T x, unsigned int y) { return T(floor(pow(x, y) + .5)); }

   template<typename T>
   enable_if_t< !is_integral<T>::value, int> 
   mypow(T x, unsigned int y) { return int(floor(pow(floor(x + .5), y) + .5)); }
like image 33
Michaël Roy Avatar answered Sep 20 '22 16:09

Michaël Roy