#include <ratio>
#include <type_traits>
#include <limits>
#include <stdexcept>

template< typename FLOAT, std::intmax_t NUM, std::intmax_t DENOM = 1 >
struct fp_as_ratio : std::ratio<NUM,DENOM>
{
    static_assert( DENOM != 0, "infinity/NaN!" ) ;
    using std::ratio<NUM,DENOM>::num ;
    using std::ratio<NUM,DENOM>::den ;
    static constexpr FLOAT value = num / FLOAT(den) ;
};

template< typename FLOAT, std::intmax_t LOWER_NUM, std::intmax_t LOWER_DEN,
                          std::intmax_t UPPER_NUM, std::intmax_t UPPER_DEN >
struct range_checked_fp
{
    static_assert( std::is_floating_point<FLOAT>::value, "not a floating point type!" ) ;

    using limits = std::numeric_limits<FLOAT> ;
    using fp_low_value = fp_as_ratio<FLOAT,LOWER_NUM,LOWER_DEN> ;
    using fp_high_value = fp_as_ratio<FLOAT,UPPER_NUM,UPPER_DEN> ;

    static constexpr FLOAT low_value = fp_low_value::value ;
    static constexpr FLOAT high_value = fp_high_value::value ;
    static constexpr FLOAT epsilon = limits::epsilon() ; // modify as required

    constexpr range_checked_fp() = default ;
    range_checked_fp( FLOAT f ) : value(f)
    { if( !in_range(f) ) throw std::out_of_range( "floating point value is out of range!" ) ; }

    constexpr operator FLOAT() const { return value ; }

    private:
        FLOAT value = fp_low_value::value  ;

        // adjust for epsilon?
        static constexpr bool in_range( FLOAT v ) { return v >= low_value && v < high_value ; }
} ;

using percentage = range_checked_fp< float, 0, 1, 100, 1 > ; // 0 = 100
using probability = range_checked_fp< double, 0, 1, 1, 1 > ; // 0 - 1

#include <iostream>

int main()
{
    percentage p = 52.3 ;
    std::cout << p << '\n' ;
}
