0

I am writing the below linear interpolation function, which is meant to be generic, but current result is not.

The function finds desired quantity of equally distant points linear in between two given boundary points. Both desired quantity and boundaries are given as parameters. As return, a vector of linear interpolated values is returned.

The issue I have concerns to return type, which always appear to be integer, even when it should have some mantissa, for example:

vec = interpolatePoints(5, 1, 4);
for (auto val : vec) std::cout << val << std::endl; // prints 4, 3, 2, 1

But it should have printed: 4.2, 3.4, 2.6, 1.8

What should I do to make it generic and have correct return values?

code:

template <class T>
std::vector<T> interpolatePoints(T lower_limit, T high_limit, const unsigned int quantity) {
    auto step = ((high_limit - lower_limit)/(double)(quantity+1));

    std::vector<T> interpolated_points;
    for(unsigned int i = 1; i <= quantity; i++) {
        interpolated_points.push_back((std::min(lower_limit, high_limit) + (step*i)));
    }
    return interpolated_points;
}
meetnick
  • 1,196
  • 2
  • 12
  • 28
  • 3
    You are passing integer arguments, so `T` is deduced as `int`. Also, your code-description of the intended and actual behavior is confusing - don't write `return something;` to mean "the function returned `something`". Showing the real function call and how you evaluated the results would help: `auto vec = interpolatePoints(5, 1, 4); for (auto val : vec) std::cout << val << std::endl; // prints 4, 3, 2, 1` would be better. – Max Langhof Nov 27 '19 at 12:41
  • 1
    `lower_limit`, `high_limit` and `quantity` and runtime values. The return type is a compile-time value. Therefore, it is conceptually not possible to decide on the return type based on the given arguments, unless you employ some serious metaprogramming and transform the three arguments into template arguments. However, I highly doubt that selecting `int` instead of `float` just because the returned values fit makes any sense - why don't you just always return floats? – flyx Nov 27 '19 at 12:57
  • Thank you for the tip, @MaxLanghof. But what am I supposed to do in order to print 4.2, 3.4, 2.6, 1.8? – meetnick Nov 27 '19 at 12:58
  • 2
    What would you expect your function should return if the caller passes an `int`, `unsigned int`, `long`, `unsigned long`, `long long`, `unsigned long long`, `char`? – MadScientist Nov 27 '19 at 12:59
  • @flyx you are right! I will make it always return float. – meetnick Nov 27 '19 at 13:03
  • @MadScientist WOW! I totally missed it! for now it is only necessary to use with int, unsigned integer and float/double values as boundaries. I'll make it return double so it will covers all the current needs – meetnick Nov 27 '19 at 13:05
  • 1
    @shabang Or `double`. And while you are at it, you should seriously consider also taking just `double` (or `float`), otherwise users will be annoyed when passing e.g. an `int` and a `float` (see https://godbolt.org/z/BQ8j6P). What does making the function generic achieve? – Max Langhof Nov 27 '19 at 13:05
  • Return `std::common_type_t` to handle `long double`, too. – Evg Nov 27 '19 at 13:06
  • 1
    Why do you use `abs` in `abs(max(high_limit, lower_limit) - min(high_limit, lower_limit));`? Just write `if (high_limit < lower_limit) std::swap(lower_limit, high_limit;` at the top. – Evg Nov 27 '19 at 13:10
  • @MaxLanghof making function generic I wish to write less code, and make it more maintainable. Linear interpolation is really simple algorithm so I wish I it wouldn't be necessary to duplicate it with polymorphic functions only with different parameters and return types. – meetnick Nov 27 '19 at 13:11
  • 1
    @shabang The question is why you would ever need different parameters than `double`. In which case is using `double` for parameters and return type insufficient? If you had a version using `double`, why would you need other versions? If the answers is "you don't" then making it generic means _more_ code and _less_ maintainability. – Max Langhof Nov 27 '19 at 13:15
  • @Evg I just did consider to get rid of abs because it really makes no sense now. About the std::swap part I can't because this function makes it double direction, otherwise it will only interpolates either decrease or increasing values – meetnick Nov 27 '19 at 13:23
  • @MaxLanghof you are totally right! Thank you for this. I'm feeling quite ashamed right now – meetnick Nov 27 '19 at 13:26
  • 1
    @shabang Don't feel ashamed, that wasn't the intention! It's good that you are thinking about ways to avoid code reuse and improve maintainability, and generic implementations can be a great way to do that. But as with any tool, check if applying it is really needed - live and learn. :) – Max Langhof Nov 27 '19 at 14:22

2 Answers2

3

After some simplifications the function might look like:

template<typename T, typename N, typename R = std::common_type_t<double, T>>
std::vector<R> interpolate(T lo_limit, T hi_limit, N n) {
    const auto lo = static_cast<R>(lo_limit);
    const auto hi = static_cast<R>(hi_limit);
    const auto step = (hi - lo) / (n + 1);

    std::vector<R> pts(n);
    const auto gen = [=, i = N{0}]() mutable { return lo + step * ++i; };
    std::generate(pts.begin(), pts.end(), gen);
    return pts;
}

The type of elements in the returned std::vector is std::common_type_t<double, T>. For int, it is double, for long double, it is long double. double looks like a reasonable default type.

Evg
  • 25,259
  • 5
  • 41
  • 83
  • Wow! Worked great! I have tested it on online compiler because I have unexpected token '=' on line const auto gen = [=, i = N{0}]() mutable { return lo + step * ++i; }; – meetnick Nov 27 '19 at 13:48
  • 1
    @shabang, try `... gen = [lo, step, i = N{0}]() ...`. – Evg Nov 27 '19 at 13:49
  • I can compile and run, it works. But my IDE still shows unexpected token '='. – meetnick Nov 27 '19 at 13:57
  • could you explain me or give any reference to study and understand this line: const auto gen = [=, i = N{0}]() mutable { return lo + step * ++i; }; – meetnick Nov 27 '19 at 14:04
  • 1
    @shabang, if it is Visual Studio, it might have incomplete support of the language standard in IntelliSense. – Evg Nov 27 '19 at 14:04
  • QtCreator. But you don't need to bother, it's quite off-topic. However I appreciate so much your implementation and support. – meetnick Nov 27 '19 at 14:06
  • @shabang, it's a [lambda](https://stackoverflow.com/questions/7627098/what-is-a-lambda-expression-in-c11) that produces the next element each time you call it, `gen()`. `std::generate` will call it `n` times and put results into `pts`. – Evg Nov 27 '19 at 14:07
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203188/discussion-between-shabang-and-evg). – meetnick Nov 27 '19 at 14:09
2

You just have to pass correct type:

auto vec = interpolatePoints(5., 1., 4); // T deduced as double

Demo

And in C++20, you might use std::lerp, to have:

template <class T>
std::vector<T> interpolatePoints(T lower_limit, T high_limit, const unsigned int quantity) {
    auto step = 1 / (quantity + 1.);

    std::vector<T> interpolated_points;
    for(unsigned int i = 1; i <= quantity; i++) {
        interpolated_points.push_back(std::lerp(lower_limit, high_limit, step * i));
    }
    return interpolated_points;
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302