首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >找到两个值的平均值,取两个

找到两个值的平均值,取两个
EN

Code Review用户
提问于 2021-06-10 06:36:37
回答 2查看 248关注 0票数 5

这是我对std::midpoint的概括,其中包含了为求两个值的平均值收到的建议。除了像std::midpoint一样支持算术类型和指针之外,它还支持迭代器、复数和用户定义的类型,如bignum、理性主义和不动点数。

除了第一次评审的建议外,我还做了一些其他的修改:

  • 拒绝将bool作为参数类型
  • 对指针差的迂回处理--如果结果对于std::ptrdiff_t来说太大了:如果数组太大(大于PTRDIFF_MAX元素,但小于SIZE_MAX字节),那么两个指针之间的差异可能不能表示为std::ptrdiff_t,那么减去两个这样的指针的结果是没有定义的。- 优先选择 (CC-BY-SA3.0)
代码语言:javascript
复制
#include <array>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstddef>
#include <iterator>
#include <type_traits>
#include <utility>

namespace toby
{
namespace detail
{
    // A point on an affine line can be compared with another, and has
    // a difference type (which may be the same type).
    template<typename T>
    concept affine_point = std::regular<T> && requires(T a, T b) {
        a < b;
        a + (b - a);
    };

    // Subtraction helper, for pedantic handling of very large arrays
    template<typename T>
    auto distance(const T& a, const T& b) {
        return b - a;
    }
    template<typename T>
    requires ( sizeof (T) < SIZE_MAX / PTRDIFF_MAX )
    std::size_t distance(const T* a, const T* b) {
        if (b < a) {
            std::swap(a, b);
        }
        // If an array has more than PTRDIFF_MAX elements,
        // subtraction is undefined if the result is not
        // representable as std::ptrdiff_t.
        std::size_t gap = 1;
        while (a + gap < b - gap) {
            gap *= 2;
        }
        // (b - gap - a) promotes to size_t if necessary
        return b - gap - a + gap;
    }

    void midpoint(bool, bool) = delete;

    template<affine_point T>
    constexpr T midpoint(const T& a, const T& b)
    {
        if (a == b) {
            // This ensures infinities are correctly returned.
            return a;
        }

        if constexpr (std::is_signed_v<T>) {
            if ((a < 0) != (b < 0)) {
                // Values are opposite sign; avoid overflow when
                // magnitudes are large.
                return (a + b) / 2;
            }
        }

        if (a < b) {
            return a + distance(a, b) / 2;
        } else {
            return b + distance(b, a) / 2;
        }
    }

    // Iterators
    // If not random-access, then a MUST be before b
    template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> S>
    constexpr Iter midpoint(Iter a, const S& b)
    {
        std::ranges::advance(a, std::ranges::distance(a, b) / 2);
        return a;
    }

    // Aggregate types follow
    // Pattern can be extended, e.g. for popular geometry types

    template<affine_point T>
    constexpr std::complex<T> midpoint(const std::complex<T>& a, const std::complex<T>& b)
    {
        return {
            midpoint(a.real(), b.real()),
            midpoint(a.imag(), b.imag())
        };
    }

    template<affine_point T, std::size_t N>
    constexpr std::array<T,N> midpoint(const std::array<T,N>& a, const std::array<T,N>& b)
    {
        std::array<T,N> result;
        auto f = [](auto&& x, auto&& y) { return midpoint(x,y); };
        std::transform(a.begin(), a.end(), b.begin(), result.begin(), f);
        return result;
    }
}

using detail::midpoint;
}

//测试

代码语言:javascript
复制
#include <gtest/gtest.h>

using toby::midpoint;

#include <climits>
TEST(midpoint, int)
{
    EXPECT_EQ(midpoint(0, 0), 0);
    EXPECT_EQ(midpoint(0, 1), 0);
    EXPECT_EQ(midpoint(0, 2), 1);
    EXPECT_EQ(midpoint(1, 3), 2);
    EXPECT_EQ(midpoint(4, 1), 2);
    EXPECT_EQ(midpoint(INT_MIN, 0), INT_MIN/2);
    EXPECT_EQ(midpoint(INT_MAX, 0), INT_MAX/2);
    EXPECT_EQ(midpoint(INT_MAX, -INT_MAX), 0);
}

#include <limits>
TEST(midpoint, double)
{
    static constexpr auto inf = std::numeric_limits<double>::infinity();
    static constexpr auto nan = std::numeric_limits<double>::quiet_NaN();
    EXPECT_EQ(midpoint(0.0, 0.0), 0.0);
    EXPECT_EQ(midpoint(1.0, 2.0), 1.5);
    EXPECT_EQ(midpoint(1.0, inf), inf);
    EXPECT_EQ(midpoint(1.0, -inf), -inf);
    EXPECT_EQ(midpoint(inf, inf), inf);
    EXPECT_EQ(midpoint(-inf, -inf), -inf);
    EXPECT_TRUE(std::isnan(midpoint(inf, -inf)));
    EXPECT_TRUE(std::isnan(midpoint(nan, 0.0)));
    EXPECT_TRUE(std::isnan(midpoint(0.0, nan)));
    EXPECT_TRUE(std::isnan(midpoint(nan, nan)));
}

#include <complex>
TEST(midpoint, complex)
{
    auto const a = std::complex{2,10};
    auto const b = std::complex{0,20};
    auto const c = std::complex{1,15};
    EXPECT_EQ(midpoint(a, b), c);
}

TEST(midpoint, pointer)
{
    char const s[50] = {};
    EXPECT_EQ(midpoint(s+1, s+25), s+13);
    EXPECT_EQ(midpoint(s+25, s+1), s+13);
}

#include <string_view>
TEST(midpoint, iterator)
{
    auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
    EXPECT_EQ(*midpoint(s.begin(), s.end()), 'n');
    EXPECT_EQ(*midpoint(s.end(), s.begin()), 'n');
}

#include <list>
TEST(midpoint, bidi_iterator)
{
    auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
    auto const l = std::list(s.begin(), s.end());
    EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
}

#include <forward_list>
TEST(midpoint, forward_iterator)
{
    auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
    auto const l = std::forward_list(s.begin(), s.end());
    EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
}

#include <array>
TEST(midpoint, std_array)
{
    auto const a = std::array{ 0, 10, 20};
    auto const b = std::array{10, 10, 10};
    auto const c = std::array{ 5, 10, 15};
    EXPECT_EQ(midpoint(a, b), c);
}
EN

回答 2

Code Review用户

发布于 2021-06-10 08:23:01

  1. std::transform是用<algorithm>定义的。你应该把这个包括进去。
  2. 让它以最小的工作范围工作:
代码语言:javascript
复制
template <std::ranges::range range>
constexpr auto midpoint(range &&r)
{
    return midpoint(std::ranges::begin(r), std::ranges::end(r));
}
  1. 我查了一下“仿射”是什么意思。也许没什么大不了的,但对可读性有一点影响。
票数 3
EN

Code Review用户

发布于 2021-06-13 20:21:19

最后版本,包含了其他答案的建议。

作出的改动:

  • 使用检测成语来确保应用约束(包括一个简单的is_detected_v实现,因为我的平台还没有标准的)。
  • 使用std::midpoint()处理指针类型。
  • 约束bool而不是重载,因此显式专门化会受到影响。
  • 使用std::forward_iterator概念,禁止单程迭代器.
  • 接受一个范围作为迭代器的替代。

代码语言:javascript
复制
#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstddef>
#include <iterator>
#include <numeric>
#include <ranges>
#include <type_traits>
#include <utility>

namespace toby
{
namespace detail
{
    // A point on an affine line can be compared with another, and has
    // a difference type (which may be the same type).
    template<typename T>
    concept affine_point
    =   std::regular<T>
        && !std::is_same_v<std::decay_t<T>, bool>
        && requires(T a, T b) { a < b; a + (b - a); };

    template<typename T>
    constexpr auto midpoint(const T* a, const T* b) {
        return std::midpoint(a, b);
    }
    template<affine_point T>
    constexpr T midpoint(const T& a, const T& b)
    {
        if (a == b) {
            // This ensures infinities are correctly returned.
            return a;
        }

        if constexpr (std::is_signed_v<T>) {
            if ((a < 0) != (b < 0)) {
                // Values are opposite sign; avoid overflow when
                // magnitudes are large.
                return (a + b) / 2;
            }
        }

        if (a < b) {
            return a + (b - a) / 2;
        } else {
            return b + (a - b) / 2;
        }
    }


    // Iterators and ranges
    // If not random-access, then a MUST precede b
    template<std::forward_iterator Iter, std::sentinel_for<Iter> S>
    constexpr Iter midpoint(Iter a, const S& b)
    {
        std::ranges::advance(a, std::ranges::distance(a, b) / 2);
        return a;
    }
    template <std::ranges::range Range>
    constexpr auto midpoint(const Range& r)
    {
        return midpoint(std::ranges::begin(r), std::ranges::end(r));
    }

    // Aggregate types follow
    // Pattern can be extended, e.g. for popular geometry types

    template<affine_point T>
    constexpr std::complex<T> midpoint(const std::complex<T>& a, const std::complex<T>& b)
    {
        return {
            midpoint(a.real(), b.real()),
            midpoint(a.imag(), b.imag())
        };
    }

    template<affine_point T, std::size_t N>
    constexpr std::array<T,N> midpoint(const std::array<T,N>& a, const std::array<T,N>& b)
    {
        std::array<T,N> result;
        auto f = [](auto&& x, auto&& y) { return midpoint(x,y); };
        std::transform(a.begin(), a.end(), b.begin(), result.begin(), f);
        return result;
    }
}

using detail::midpoint;
}

//测试

代码语言:javascript
复制
#include <gtest/gtest.h>

using toby::midpoint;

#include <climits>
TEST(midpoint, int)
{
    EXPECT_EQ(midpoint(0, 0), 0);
    EXPECT_EQ(midpoint(0, 1), 0);
    EXPECT_EQ(midpoint(0, 2), 1);
    EXPECT_EQ(midpoint(1, 3), 2);
    EXPECT_EQ(midpoint(4, 1), 2);
    EXPECT_EQ(midpoint(INT_MIN, 0), INT_MIN/2);
    EXPECT_EQ(midpoint(INT_MAX, 0), INT_MAX/2);
    EXPECT_EQ(midpoint(INT_MAX, -INT_MAX), 0);
}

#include <limits>
TEST(midpoint, double)
{
    static constexpr auto inf = std::numeric_limits<double>::infinity();
    static constexpr auto nan = std::numeric_limits<double>::quiet_NaN();
    EXPECT_EQ(midpoint(0.0, 0.0), 0.0);
    EXPECT_EQ(midpoint(1.0, 2.0), 1.5);
    EXPECT_EQ(midpoint(1.0, inf), inf);
    EXPECT_EQ(midpoint(1.0, -inf), -inf);
    EXPECT_EQ(midpoint(inf, inf), inf);
    EXPECT_EQ(midpoint(-inf, -inf), -inf);
    EXPECT_TRUE(std::isnan(midpoint(inf, -inf)));
    EXPECT_TRUE(std::isnan(midpoint(nan, 0.0)));
    EXPECT_TRUE(std::isnan(midpoint(0.0, nan)));
    EXPECT_TRUE(std::isnan(midpoint(nan, nan)));
}

#include <complex>
TEST(midpoint, complex)
{
    auto const a = std::complex{2,10};
    auto const b = std::complex{0,20};
    auto const c = std::complex{1,15};
    EXPECT_EQ(midpoint(a, b), c);
}

TEST(midpoint, pointer)
{
    char const s[50] = {};
    EXPECT_EQ(midpoint(s+1, s+25), s+13);
    EXPECT_EQ(midpoint(s+25, s+1), s+13);
}

#include <string_view>
TEST(midpoint, iterator)
{
    auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
    EXPECT_EQ(*midpoint(s.begin(), s.end()), 'n');
    EXPECT_EQ(*midpoint(s.end(), s.begin()), 'n');
    EXPECT_EQ(*midpoint(s), 'n');
}

#include <list>
TEST(midpoint, bidi_iterator)
{
    auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
    auto const l = std::list(s.begin(), s.end());
    EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
    EXPECT_EQ(*midpoint(l), 'n');
}

#include <forward_list>
TEST(midpoint, forward_iterator)
{
    auto const s = std::string_view{"abcdefghijklmnopqrstuvwxyz"};
    auto const l = std::forward_list(s.begin(), s.end());
    EXPECT_EQ(*midpoint(l.begin(), l.end()), 'n');
    EXPECT_EQ(*midpoint(l), 'n');
}

#include <array>
TEST(midpoint, std_array)
{
    auto const a = std::array{ 0, 10, 20};
    auto const b = std::array{10, 10, 10};
    auto const c = std::array{ 5, 10, 15};
    EXPECT_EQ(midpoint(a, b), c);
}

#if 1
// use detection idiom to ensure invalid overloads don't exist
// Inspired from library fundamentals TS v3
namespace {
    template <class AlwaysVoid, template<class...> class Op, class... Args>
    struct detector : std::false_type {};

    template <template<class...> class Op, class... Args>
    struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};

    template <template<class...> class Op, class... Args>
    constexpr bool is_detected_v = detector<void, Op, Args...>::value;
}
#else

#include <experimental/type_traits>
    using std::is_detected_v;

#endif

template<typename T, typename U = T>
using midpoint_exists = decltype(midpoint(std::declval<T>(), std::declval<U>()));


//static constexpr bool a = is_detected<midpoint_exists, int, int>;
static_assert(is_detected_v<midpoint_exists, int>);
static_assert(!is_detected_v<midpoint_exists, bool>);
static_assert(!is_detected_v<midpoint_exists, bool&>);

static_assert(!is_detected_v<midpoint_exists, std::istream_iterator<char>>);
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/262873

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档