C++ 编译时std::ratio平方根的有理逼近

C++ 编译时std::ratio平方根的有理逼近,c++,c++11,recursion,template-meta-programming,C++,C++11,Recursion,Template Meta Programming,我试图找到编译时std::ratio的平方根的合理近似值。从定义的坐标转换参数中推导出来非常有用,这些参数本身定义为std::ratio 有一个,但作为这个问题的一个条件,如果这个比率没有整数根,那么失败是可以的,这与我想要的相反。相反,我希望找到最接近的合理近似值 我提出了以下元程序,该程序根据根计算根,已知只需几次迭代即可产生(相对)精确的结果: namespace detail { // implementation of ratio_sqrt // N is an std

我试图找到编译时
std::ratio
的平方根的合理近似值。从定义的坐标转换参数中推导出来非常有用,这些参数本身定义为
std::ratio

有一个,但作为这个问题的一个条件,如果这个比率没有整数根,那么失败是可以的,这与我想要的相反。相反,我希望找到最接近的合理近似值

我提出了以下元程序,该程序根据根计算根,已知只需几次迭代即可产生(相对)精确的结果:

namespace detail
{
    // implementation of ratio_sqrt
    // N is an std::ratio, not an int
    template<class N , class K = std::ratio<4>, std::intmax_t RecursionDepth = 5>
    struct ratio_sqrt_impl
    {
        static_assert(/* N is std::ratio */);

        // Recursive Newton-Raphson
        // EQUATION: K_{n+1} = (K_{n} - N / K_{n}) / 2
        // WHERE:
        // K_{n+1} : square root approximation
        // K_{n}   : previous square root approximation
        // N       : ratio whose square root we are finding
        using type = typename ratio_sqrt_impl<N, std::ratio_subtract<K,
          std::ratio_divide<std::ratio_subtract<std::ratio_multiply<K, K>, N>, 
          std::ratio_multiply<std::ratio<2>, K>>>, RecursionDepth - 1>::type;
    };
    template<class N, class K>
    struct ratio_sqrt_impl<N, K, 1>
    {
        using type = K;
    };
}

template<class Ratio>
using ratio_sqrt = typename detail::ratio_sqrt_impl<Ratio>::type;
名称空间详细信息
{
//比率的实现
//N是一个std::ratio,而不是int
模板
结构比率
{
静态断言(/*N是std::ratio*/);
//递归牛顿-拉夫森
//方程:K{n+1}=(K{n}-n/K{n})/2
//其中:
//K{n+1}:平方根近似
//K_{n}:以前的平方根近似
//N:我们找到的平方根的比率
使用type=typename ratio\u sqrt\u impl::type;
};
模板
结构比率
{
使用类型=K;
};
}
模板
使用ratio\u sqrt=typename detail::ratio\u sqrt\u impl::type;
使用示例用法:

// Error calculations
using rt2 = ratio_sqrt<std::ratio<2>>;
std::cout << (sqrt(2) - ((double)rt2::num / rt2::den))/sqrt(2) << std::endl;

scalar_t result = pow<2>(scalar_t((double)rt2::num / rt2::den));
std::cout << (2 - result.toDouble()) / 2 << std::endl;

using rt4 = ratio_sqrt<std::ratio<4>>;
std::cout << (sqrt(4) - ((double)rt4::num / rt4::den)) / sqrt(4) << std::endl;

using rt10 = ratio_sqrt<std::ratio<10>>;
std::cout << (sqrt(10) - ((double)rt10::num / rt10::den)) / sqrt(10) << std::endl;
//错误计算
使用rt2=比率_sqrt;

std::cout我想我理解这个问题,你说的是由于整数中的(乘法)溢出,在溢出之前,你不能进行超过4或5次迭代。。。这仍然是一个糟糕的近似值

我认为你唯一能做的就是做更好的初步估计。很可能是硬编码的。在本例中,我显示的范围足够大,但您可以硬编码更多的初始猜测,以进一步扩大范围

#include<ratio>
#include<iostream>

namespace detail
{

    template<class R>
    struct estimatimate{
        typedef 
        typename std::conditional<
            std::ratio_less_equal<R, std::ratio<1,100>>::value, 
            std::ratio<1,10>,
            typename std::conditional<
                std::ratio_less_equal<R, std::ratio<25,100>>::value, 
                std::ratio<5,10>,
                typename std::conditional<
                    std::ratio_less_equal<R, std::ratio<1>>::value, 
                    std::ratio<1>,
                    typename std::conditional<
                        std::ratio_less_equal<R, std::ratio<25>>::value, 
                        std::ratio<5>,
                        typename std::conditional<
                            std::ratio_less_equal<R, std::ratio<100>>::value,
                            std::ratio<10>,
                            typename std::conditional<
                                std::ratio_less_equal<R, std::ratio<2500>>::value,
                                std::ratio<50>,
                                typename std::conditional<
                                    std::ratio_less_equal<R, std::ratio<10000>>::value,
                                    std::ratio<100>,
                                    std::ratio<500>
                                >::type
                            >::type
                        >::type
                    >::type
                >::type
            >::type
        >::type type;
    };

    template<class N , class K = typename estimatimate<N>::type, std::intmax_t RecursionDepth = 4>
    struct ratio_sqrt_impl
    {
    //    static_assert(/* N is std::ratio */);

        // Recursive Newton-Raphson
        // EQUATION: K_{n+1} = (K_{n} - N / K_{n}) / 2
        // WHERE:
        // K_{n+1} : square root approximation
        // K_{n}   : previous square root approximation
        // N       : ratio whose square root we are finding
        using type = typename ratio_sqrt_impl<N, std::ratio_subtract<K,
          std::ratio_divide<std::ratio_subtract<std::ratio_multiply<K, K>, N>, 
          std::ratio_multiply<std::ratio<2>, K>>>, RecursionDepth - 1>::type;
    };
    template<class N, class K>
    struct ratio_sqrt_impl<N, K, 1>
    {
        using type = K;
    };
}

template<class Ratio>
using ratio_sqrt = typename detail::ratio_sqrt_impl<Ratio>::type;

template<class Ratio>
double value(){
    return ((double)Ratio::num)/Ratio::den;
}

#include<cmath>
using namespace std;
int main(){
//  std::cout << value<std::ratio<1,10>>() << std::endl;
//  std::cout << value<ratio_sqrt<std::ratio<4,1>>>() << std::endl;

    std::cout << value<ratio_sqrt<std::ratio<1,128>>>() << " " << sqrt(value<std::ratio<1,128>>()) << std::endl;
    std::cout << value<ratio_sqrt<std::ratio<5000,1>>>() << " " << sqrt(value<std::ratio<5000,1>>()) << std::endl;

}
//output:
// 0.0883883 0.0883883
// 70.7107 70.7107
#包括
#包括
名称空间详细信息
{
模板
结构估计{
类型定义
类型名称std::条件<
标准::比率减去相等::值,
标准:比率,
类型名称std::条件<
标准::比率减去相等::值,
标准:比率,
类型名称std::条件<
标准::比率减去相等::值,
标准:比率,
类型名称std::条件<
标准::比率减去相等::值,
标准:比率,
类型名称std::条件<
标准::比率减去相等::值,
标准:比率,
类型名称std::条件<
标准::比率减去相等::值,
标准:比率,
类型名称std::条件<
标准::比率减去相等::值,
标准:比率,
标准:比率
>::类型
>::类型
>::类型
>::类型
>::类型
>::类型
>::类型类型;
};
模板
结构比率
{
//静态断言(/*N是std::ratio*/);
//递归牛顿-拉夫森
//方程:K{n+1}=(K{n}-n/K{n})/2
//其中:
//K{n+1}:平方根近似
//K_{n}:以前的平方根近似
//N:我们找到的平方根的比率
使用type=typename ratio\u sqrt\u impl::type;
};
模板
结构比率
{
使用类型=K;
};
}
模板
使用ratio\u sqrt=typename detail::ratio\u sqrt\u impl::type;
模板
双值(){
返回((双)比率::num)/比率::den;
}
#包括
使用名称空间std;
int main(){

//std::cout问题是你使用牛顿法计算平方根,如果你想得到一个数值近似,这是正确的,但是,如果你想找到最佳有理近似,你必须使用。下面是我的程序的结果:

hidden $ g++ -std=c++11 sqrt.cpp && ./a.out
sqrt(2/1) ~ 239/169, error=1.23789e-05, eps=0.0001
sqrt(2/1) ~ 114243/80782, error=5.41782e-11, eps=1e-10
sqrt(2/1) ~ 3880899/2744210, error=4.68514e-14, eps=1e-13
sqrt(2/1) ~ 131836323/93222358, error=0, eps=1e-16
sqrt(2/10001) ~ 1/71, error=5.69215e-05, eps=0.0001
sqrt(2/10001) ~ 1977/139802, error=2.18873e-11, eps=1e-10
sqrt(2/10001) ~ 13860/980099, error=7.36043e-15, eps=1e-13
sqrt(2/10001) ~ 1950299/137913860, error=3.64292e-17, eps=1e-16
sqrt(10001/2) ~ 495/7, error=7.21501e-05, eps=0.0001
sqrt(10001/2) ~ 980099/13860, error=3.68061e-11, eps=1e-10
sqrt(10001/2) ~ 415701778/5878617, error=1.42109e-14, eps=1e-13
sqrt(10001/2) ~ 970297515/13721393, error=0, eps=1e-16
sqrt(1060/83) ~ 461/129, error=2.19816e-05, eps=0.0001
sqrt(1060/83) ~ 2139943/598809, error=9.07718e-13, eps=1e-10
sqrt(1060/83) ~ 6448815/1804538, error=1.77636e-14, eps=1e-13
sqrt(1060/83) ~ 545951360/152770699, error=4.44089e-16, eps=1e-16
sqrt(1/12494234) ~ 1/3534, error=5.75083e-08, eps=0.0001
sqrt(1/12494234) ~ 32/113111, error=2.9907e-11, eps=1e-10
sqrt(1/12494234) ~ 419/1481047, error=6.02961e-14, eps=1e-13
sqrt(1/12494234) ~ 129879/459085688, error=4.49944e-18, eps=1e-16
sqrt(82378/1) ~ 18369/64, error=5.40142e-05, eps=0.0001
sqrt(82378/1) ~ 37361979/130174, error=1.16529e-11, eps=1e-10
sqrt(82378/1) ~ 1710431766/5959367, error=5.68434e-14, eps=1e-13
sqrt(82378/1) ~ 15563177213/54224136, error=0, eps=1e-16
sqrt(68389/3346222) ~ 197/1378, error=4.13769e-07, eps=0.0001
sqrt(68389/3346222) ~ 17801/124517, error=2.17069e-11, eps=1e-10
sqrt(68389/3346222) ~ 581697/4068938, error=4.30211e-15, eps=1e-13
sqrt(68389/3346222) ~ 16237871/113583000, error=2.77556e-17, eps=1e-16
sqrt(2/72) ~ 1/6, error=0, eps=0.0001
sqrt(10000/1) ~ 100/1, error=0, eps=0.0001
sqrt(0/20) ~ 0/1, error=0, eps=0.0001
我的程序用(几乎)最小的分子和分母找到满足错误界限的近似值。如果在我的代码中将
int
更改为更长的整数类型,它可以使用更小的
eps

我的代码(它在g++-4.8.4下编译,IMHO,如果允许使用constexpr,代码会简单得多):


更新

修复了iSqrt中超大输入的溢出问题

test\u dump
的输入包不再需要终止
bool

此代码计算
std::ratio
它与IdeOne的Visual Studio 2013和g++配合使用

这使用中间搜索来收敛于目标值。搜索作为连分数通过相同的收敛点,但需要更多的迭代。这类似于遍历Stern Brocot树,其中每个节点都是更接近某个实际目标值的有理近似值

这会一直收敛,直到最后一个中间点的分子或分母通过限制,接近触发整数溢出的点

在底部,计算了几个测试用例

#include <iostream>
#include <iomanip>
#include <ratio>
#include <cmath>  // for sqrt(double) -- only used for testing

// old versions of std::numeric_limits can't be used at compile-time
// integer_limits is a replacement, for two's complement integer types
template <typename T>
struct integer_limits {
    static const bool is_integer = T(1)/T(3) == T(0);
    static_assert(is_integer, "integer_limits got non-integer type");
    static const bool is_signed = T(1)-T(3) < T(1);
    static const int  bit_count = sizeof(T) * 8; // 8-bit char safely assumed
    static const T    min_value = is_signed ? T(1) << (bit_count-1) : T(0);
    static const T    max_value = ~min_value;
};

// iLog2: static intmax_t log2, used by iSqrt to calculate an initial guess
template <intmax_t n>
struct iLog2 {
    template <intmax_t b, intmax_t c=0>
    struct L2 {
        static const intmax_t value = L2<b/2, c+1>::value;
    };
    template <intmax_t c>
    struct L2<1,c> {
        static const intmax_t value = c;
    };
    static_assert(n > 0, "Log2 got n <= 0");
    static const intmax_t value = L2<n>::value;
};

// iSqrt: static intmax_t square root
// starts off using iLog2 to get an approximate result range
// uses standard binary search to narrow the range and converge
// on the square root's floor value
template <intmax_t n>
struct iSqrt {
    static_assert(n >= 0, "iSqrt got a negative n");

    // main recursive binary search loop
    template <intmax_t low, intmax_t high, bool end=(low >= high)>
    struct srch {
        static const intmax_t mid       = low + (high - low)/2;
        static const intmax_t ndivm     = n / mid;
        static const int      mid_cmp   = int(mid > ndivm) - int(mid < ndivm);
        static const intmax_t next_high = mid_cmp < 0 ? high - (mid == low)  : mid;
        static const intmax_t next_low  = mid_cmp > 0 ? low                  : mid;
        static const intmax_t value     = srch<next_low, next_high>::value;
    };
    // terminating specialization for the binary search (end is true)
    template <intmax_t low, intmax_t high>
    struct srch<low, high, true> {
        static_assert(low*low <= n && (low+1)*(low+1) > n, "bad result");
        static const intmax_t value = low;
    };
    // initial range and kickoff
    static const intmax_t low_approx = intmax_t(1) << iLog2<n>::value / 2;
    static const intmax_t high_approx = low_approx << 1;
    static const intmax_t value = srch<low_approx, high_approx>::value;
};
// Specializations handling sqrt(0) and sqrt(1)
template <> struct iSqrt<0> { static const intmax_t value = 0; };
template <> struct iSqrt<1> { static const intmax_t value = 1; };

// std::ratio mediant calculation, used in the mediant search
template <typename a, typename b>
struct mediant {
    typedef std::ratio<a::num + b::num, a::den + b::den> type;
};

// conditional type selection utility
template <typename A, typename B, bool sel_a> struct select_t { typedef A type; };
template <typename A, typename B> struct select_t<A, B, false>{ typedef B type; };

// Sqrt calculates the square root of a std::ratio
template <typename r>
struct Sqrt {
    // log_limit limits how big (in bits) the intmax_t can get before ending the mediant search
    // the fraction here comes from trial and error.  Some implementations of
    // std::ratio might be more likely to overflow than others, so you can experiement with
    // this to try to get more precision.
    // This search tests convergents against the input by squaring them, so there is
    // a hard limit at 50% (half the bits).  My std::ratio implementation also
    // seems to need a lot of cushioning when it does comparisons.
    static const int log_limit = (integer_limits<intmax_t>::bit_count-1) * 31/100;
    static const intmax_t root_limit = intmax_t(1) << log_limit;

    // The main mediant search loop.  This is similar to walking the Stern-Brocot tree
    // toward a real target.  Each iteration brings the ratio closer to the target by
    // increasing both the numerator and denominator.  The convergents of a mediant
    // search intersect the convergents of a continued fraction search, but with more
    // iterations and intermediate convergents.  However, it is probably simpler to implement
    // for compile-time than the Euclidean algorithm used for continued fractions.
    // This is not a binary search, although it looks similar.  A binary search would
    // not work well for this.
    template <typename low, typename high, bool done=false>
    struct srch {
        static_assert(std::ratio_greater_equal<high, low>::value, "Sqrt::srch got low > high");
        typedef typename mediant<low, high>::type med;
        typedef typename std::ratio_multiply<med, med> med_sq;
        static const bool med_over = std::ratio_greater<med_sq, r>::value;
        typedef typename select_t<low, med,  med_over>::type next_low;
        typedef typename select_t<med, high, med_over>::type next_high;
        static const bool stop = med::num > root_limit || med::den > root_limit;
        typedef typename srch<next_low, next_high, stop>::type type;
    };
    // The terminating specialization for the mediant search, triggered by the
    // last mediant's numerator or denominator exceeding root_limit
    // The low and high convergents are squared and compared with the input
    // in order to select the best result.
    template <typename low, typename high>
    struct srch<low, high, true> {
        typedef typename std::ratio_multiply<low,  low>  low_sq;
        typedef typename std::ratio_multiply<high, high> high_sq;
        typedef std::ratio_subtract<r, low_sq>  err_sq_low;
        typedef std::ratio_subtract<high_sq, r> err_sq_high;
        static const bool low_closer = std::ratio_less<err_sq_low, err_sq_high>::value;
        typedef typename select_t<low, high, low_closer>::type type;
    };
    // init checks the first approximation if it the root of a perfect square or if the numerator is zero
    // (in case on a non-canonical zero-valued ratio (unlikely, though))
    // when the input (r) is not a perfect square, this sets up the high and low ratios for the mediant
    // search.  These are set up in a way which ensures coprime numerators and denominators.
    // Then init kicks off the search loop.
    template <intmax_t num, intmax_t den, bool done = (num*num==r::num && den*den==r::den) || num==0>
    struct init {
        typedef std::ratio<num,     den + 1> low;
        typedef std::ratio<num + 1, den>     high;
        typedef typename srch<low, high>::type type;
    };
    // the init perfect square shortcut specialization
    template <intmax_t num, intmax_t den> struct init<num, den, true> {typedef std::ratio<num, den> type; };

    // An approximate numerator and denominator of the result is calculated 
    // by taking the integer square roots of the input ratio's num and den.
    // Then these values are passed to init for processing.
    static const intmax_t approx_num = iSqrt<r::num>::value;
    static const intmax_t approx_den = iSqrt<r::den>::value;
    typedef typename init<approx_num, approx_den>::type type;
};

// Diagnostic junk follows....
template <typename r>
void dump_ratio() {
    std::cout << r::num << '/' << r::den;
}

template <intmax_t N, intmax_t D>
std::ostream& operator << (std::ostream &os, typename std::ratio<N,D> r) {
    os << r.num << '/' << r.den;
    return os;
}

template <typename r>
double get_double() {
    return double(r::num) / r::den;
}

template <typename ...Rs> struct test_dump;

template <typename R, typename ...Rs>
struct test_dump<R, Rs...> {
    static void go() {
        std::cout << std::setprecision(14);
        double flt_in = get_double<R>();
        std::cout << " in:   " << R{} << " : " << flt_in << " (rat => double)\n";
        using rat_sqrt = typename Sqrt<R>::type;
        std::cout << " sqrt: " << rat_sqrt{} << '\n';
        double flt_rat_sqrt = get_double<rat_sqrt>();
        std::cout << flt_rat_sqrt << " (rat => double)\n";
        double sqrt_flt_in = sqrt(flt_in);
        double err = flt_rat_sqrt - sqrt_flt_in;
        std::cout << sqrt_flt_in << " : (sqrt in) ";
        std::cout << std::setprecision(4);
        std::cout << "err: " << err << "\n\n";
        test_dump<Rs...>::go();
    }
};

template <>
struct test_dump<> { static void go() {} };


int main() {
    test_dump<
        std::ratio<1060,   83>,
        std::ratio<1,      12494234>,
        std::ratio<82378,  1>,
        std::ratio<478723, 23423554>,
        std::ratio<2,      1>,
        std::ratio<2,      72>,
        std::ratio<2,      10001>,
        std::ratio<1000,   1>,
        std::ratio<10001,  2>
    >::go();
}

这有帮助吗?最终,初始条件4给出了给定sqrt操作的迭代次数。也许你可以基于此使用更好的初始纯整数猜测:@alfC是的,这看起来是一个很好的初始猜测方法,但我正在计算的根可以是任何有理数(由
std::ratio
表示),因此我不确定我将使用什么作为
Sqrt
的输入,也许您1)可以手动为每个递归编写两个步骤2)增加
-ftemplate depth=
递归深度
template <template <std::intmax_t N> class Predicate, typename enabled = void>
struct BinarySearch {
    template <std::intmax_t N>
    struct SafeDouble_ {
        static const std::intmax_t value = 2 * N;
        static_assert(value > 0, "Overflows when computing 2 * N");
    };

    template <intmax_t Lower, intmax_t Upper, typename Condition1 = void, typename Condition2 = void>
    struct DoubleSidedSearch_ : DoubleSidedSearch_<Lower, Upper,
        typename std::conditional<(Upper - Lower == 1), std::true_type, std::false_type>::type,
        typename std::conditional<((Upper - Lower>1 && Predicate<Lower + (Upper - Lower) / 2>::value)), std::true_type, std::false_type>::type> {};

    template <intmax_t Lower, intmax_t Upper>
    struct DoubleSidedSearch_<Lower, Upper, std::false_type, std::false_type> : DoubleSidedSearch_<Lower, Lower + (Upper - Lower) / 2> {};

    template <intmax_t Lower, intmax_t Upper, typename Condition2>
    struct DoubleSidedSearch_<Lower, Upper, std::true_type, Condition2> : std::integral_constant<intmax_t, Lower>{};

    template <intmax_t Lower, intmax_t Upper, typename Condition1>
    struct DoubleSidedSearch_<Lower, Upper, Condition1, std::true_type> : DoubleSidedSearch_<Lower + (Upper - Lower) / 2, Upper>{};

    template <std::intmax_t Lower, class enabled1 = void>
    struct SingleSidedSearch_ : SingleSidedSearch_<Lower, typename std::conditional<Predicate<SafeDouble_<Lower>::value>::value, std::true_type, std::false_type>::type>{};

    template <std::intmax_t Lower>
    struct SingleSidedSearch_<Lower, std::false_type> : DoubleSidedSearch_<Lower, SafeDouble_<Lower>::value> {};

    template <std::intmax_t Lower>
    struct SingleSidedSearch_<Lower, std::true_type> : SingleSidedSearch_<SafeDouble_<Lower>::value>{};

    const static std::intmax_t value = SingleSidedSearch_<1>::value;
};
#include <iostream>
#include <iomanip>
#include <ratio>
#include <cmath>  // for sqrt(double) -- only used for testing

// old versions of std::numeric_limits can't be used at compile-time
// integer_limits is a replacement, for two's complement integer types
template <typename T>
struct integer_limits {
    static const bool is_integer = T(1)/T(3) == T(0);
    static_assert(is_integer, "integer_limits got non-integer type");
    static const bool is_signed = T(1)-T(3) < T(1);
    static const int  bit_count = sizeof(T) * 8; // 8-bit char safely assumed
    static const T    min_value = is_signed ? T(1) << (bit_count-1) : T(0);
    static const T    max_value = ~min_value;
};

// iLog2: static intmax_t log2, used by iSqrt to calculate an initial guess
template <intmax_t n>
struct iLog2 {
    template <intmax_t b, intmax_t c=0>
    struct L2 {
        static const intmax_t value = L2<b/2, c+1>::value;
    };
    template <intmax_t c>
    struct L2<1,c> {
        static const intmax_t value = c;
    };
    static_assert(n > 0, "Log2 got n <= 0");
    static const intmax_t value = L2<n>::value;
};

// iSqrt: static intmax_t square root
// starts off using iLog2 to get an approximate result range
// uses standard binary search to narrow the range and converge
// on the square root's floor value
template <intmax_t n>
struct iSqrt {
    static_assert(n >= 0, "iSqrt got a negative n");

    // main recursive binary search loop
    template <intmax_t low, intmax_t high, bool end=(low >= high)>
    struct srch {
        static const intmax_t mid       = low + (high - low)/2;
        static const intmax_t ndivm     = n / mid;
        static const int      mid_cmp   = int(mid > ndivm) - int(mid < ndivm);
        static const intmax_t next_high = mid_cmp < 0 ? high - (mid == low)  : mid;
        static const intmax_t next_low  = mid_cmp > 0 ? low                  : mid;
        static const intmax_t value     = srch<next_low, next_high>::value;
    };
    // terminating specialization for the binary search (end is true)
    template <intmax_t low, intmax_t high>
    struct srch<low, high, true> {
        static_assert(low*low <= n && (low+1)*(low+1) > n, "bad result");
        static const intmax_t value = low;
    };
    // initial range and kickoff
    static const intmax_t low_approx = intmax_t(1) << iLog2<n>::value / 2;
    static const intmax_t high_approx = low_approx << 1;
    static const intmax_t value = srch<low_approx, high_approx>::value;
};
// Specializations handling sqrt(0) and sqrt(1)
template <> struct iSqrt<0> { static const intmax_t value = 0; };
template <> struct iSqrt<1> { static const intmax_t value = 1; };

// std::ratio mediant calculation, used in the mediant search
template <typename a, typename b>
struct mediant {
    typedef std::ratio<a::num + b::num, a::den + b::den> type;
};

// conditional type selection utility
template <typename A, typename B, bool sel_a> struct select_t { typedef A type; };
template <typename A, typename B> struct select_t<A, B, false>{ typedef B type; };

// Sqrt calculates the square root of a std::ratio
template <typename r>
struct Sqrt {
    // log_limit limits how big (in bits) the intmax_t can get before ending the mediant search
    // the fraction here comes from trial and error.  Some implementations of
    // std::ratio might be more likely to overflow than others, so you can experiement with
    // this to try to get more precision.
    // This search tests convergents against the input by squaring them, so there is
    // a hard limit at 50% (half the bits).  My std::ratio implementation also
    // seems to need a lot of cushioning when it does comparisons.
    static const int log_limit = (integer_limits<intmax_t>::bit_count-1) * 31/100;
    static const intmax_t root_limit = intmax_t(1) << log_limit;

    // The main mediant search loop.  This is similar to walking the Stern-Brocot tree
    // toward a real target.  Each iteration brings the ratio closer to the target by
    // increasing both the numerator and denominator.  The convergents of a mediant
    // search intersect the convergents of a continued fraction search, but with more
    // iterations and intermediate convergents.  However, it is probably simpler to implement
    // for compile-time than the Euclidean algorithm used for continued fractions.
    // This is not a binary search, although it looks similar.  A binary search would
    // not work well for this.
    template <typename low, typename high, bool done=false>
    struct srch {
        static_assert(std::ratio_greater_equal<high, low>::value, "Sqrt::srch got low > high");
        typedef typename mediant<low, high>::type med;
        typedef typename std::ratio_multiply<med, med> med_sq;
        static const bool med_over = std::ratio_greater<med_sq, r>::value;
        typedef typename select_t<low, med,  med_over>::type next_low;
        typedef typename select_t<med, high, med_over>::type next_high;
        static const bool stop = med::num > root_limit || med::den > root_limit;
        typedef typename srch<next_low, next_high, stop>::type type;
    };
    // The terminating specialization for the mediant search, triggered by the
    // last mediant's numerator or denominator exceeding root_limit
    // The low and high convergents are squared and compared with the input
    // in order to select the best result.
    template <typename low, typename high>
    struct srch<low, high, true> {
        typedef typename std::ratio_multiply<low,  low>  low_sq;
        typedef typename std::ratio_multiply<high, high> high_sq;
        typedef std::ratio_subtract<r, low_sq>  err_sq_low;
        typedef std::ratio_subtract<high_sq, r> err_sq_high;
        static const bool low_closer = std::ratio_less<err_sq_low, err_sq_high>::value;
        typedef typename select_t<low, high, low_closer>::type type;
    };
    // init checks the first approximation if it the root of a perfect square or if the numerator is zero
    // (in case on a non-canonical zero-valued ratio (unlikely, though))
    // when the input (r) is not a perfect square, this sets up the high and low ratios for the mediant
    // search.  These are set up in a way which ensures coprime numerators and denominators.
    // Then init kicks off the search loop.
    template <intmax_t num, intmax_t den, bool done = (num*num==r::num && den*den==r::den) || num==0>
    struct init {
        typedef std::ratio<num,     den + 1> low;
        typedef std::ratio<num + 1, den>     high;
        typedef typename srch<low, high>::type type;
    };
    // the init perfect square shortcut specialization
    template <intmax_t num, intmax_t den> struct init<num, den, true> {typedef std::ratio<num, den> type; };

    // An approximate numerator and denominator of the result is calculated 
    // by taking the integer square roots of the input ratio's num and den.
    // Then these values are passed to init for processing.
    static const intmax_t approx_num = iSqrt<r::num>::value;
    static const intmax_t approx_den = iSqrt<r::den>::value;
    typedef typename init<approx_num, approx_den>::type type;
};

// Diagnostic junk follows....
template <typename r>
void dump_ratio() {
    std::cout << r::num << '/' << r::den;
}

template <intmax_t N, intmax_t D>
std::ostream& operator << (std::ostream &os, typename std::ratio<N,D> r) {
    os << r.num << '/' << r.den;
    return os;
}

template <typename r>
double get_double() {
    return double(r::num) / r::den;
}

template <typename ...Rs> struct test_dump;

template <typename R, typename ...Rs>
struct test_dump<R, Rs...> {
    static void go() {
        std::cout << std::setprecision(14);
        double flt_in = get_double<R>();
        std::cout << " in:   " << R{} << " : " << flt_in << " (rat => double)\n";
        using rat_sqrt = typename Sqrt<R>::type;
        std::cout << " sqrt: " << rat_sqrt{} << '\n';
        double flt_rat_sqrt = get_double<rat_sqrt>();
        std::cout << flt_rat_sqrt << " (rat => double)\n";
        double sqrt_flt_in = sqrt(flt_in);
        double err = flt_rat_sqrt - sqrt_flt_in;
        std::cout << sqrt_flt_in << " : (sqrt in) ";
        std::cout << std::setprecision(4);
        std::cout << "err: " << err << "\n\n";
        test_dump<Rs...>::go();
    }
};

template <>
struct test_dump<> { static void go() {} };


int main() {
    test_dump<
        std::ratio<1060,   83>,
        std::ratio<1,      12494234>,
        std::ratio<82378,  1>,
        std::ratio<478723, 23423554>,
        std::ratio<2,      1>,
        std::ratio<2,      72>,
        std::ratio<2,      10001>,
        std::ratio<1000,   1>,
        std::ratio<10001,  2>
    >::go();
}
 in:   1060/83 : 12.771084337349 (rat => double)
 sqrt: 28986/8111
3.5736653926766 (rat => double)
3.5736653924716 : (sqrt in) err: 2.05e-010

 in:   1/12494234 : 8.0036919430195e-008 (rat => double)
 sqrt: 174/615041
0.00028290796873704 (rat => double)
0.00028290796989515 : (sqrt in) err: -1.158e-012

 in:   82378/1 : 82378 (rat => double)
 sqrt: 585799/2041
287.01567858893 (rat => double)
287.01567901423 : (sqrt in) err: -4.253e-007

 in:   68389/3346222 : 0.020437675683203 (rat => double)
 sqrt: 88016/615667
0.14296039904689 (rat => double)
0.14296039900337 : (sqrt in) err: 4.352e-011

 in:   2/1 : 2 (rat => double)
 sqrt: 665857/470832
1.4142135623747 (rat => double)
1.4142135623731 : (sqrt in) err: 1.595e-012

 in:   1/36 : 0.027777777777778 (rat => double)
 sqrt: 1/6
0.16666666666667 (rat => double)
0.16666666666667 : (sqrt in) err: 0

 in:   2/10001 : 0.0001999800019998 (rat => double)
 sqrt: 9899/700000
0.014141428571429 (rat => double)
0.014141428569978 : (sqrt in) err: 1.45e-012

 in:   1000/1 : 1000 (rat => double)
 sqrt: 702247/22207
31.622776601972 (rat => double)
31.622776601684 : (sqrt in) err: 2.886e-010

 in:   10001/2 : 5000.5 (rat => double)
 sqrt: 700000/9899
70.714213556925 (rat => double)
70.714213564177 : (sqrt in) err: -7.252e-009