Skip to content

File precise_real.hpp

File List > backends > cxx > include > zmbt > decor > precise_real.hpp

Go to the documentation of this file

#ifndef ZMBT_DECORATOR_PRECISE_REAL_HPP_
#define ZMBT_DECORATOR_PRECISE_REAL_HPP_

#include <cstdlib>
#include <cmath>

#include "zmbt/core.hpp"
#include "zmbt/reflect.hpp"


namespace zmbt {

namespace decor {
struct precision_loss_error : public std::runtime_error {
    using std::runtime_error::runtime_error;
};


namespace detail {



template<class T>
T strto (boost::json::string_view, boost::json::string_view::pointer*);

template<>
inline float strto<float>(boost::json::string_view str, boost::json::string_view::pointer* end)
{
    return std::strtof(str.cbegin(), end);
}

template<>
inline double strto<double>(boost::json::string_view str, boost::json::string_view::pointer* end)
{
    return std::strtod(str.cbegin(), end);
}
template<>
inline long double strto<long double>(boost::json::string_view str, boost::json::string_view::pointer* end)
{
    return std::strtold(str.cbegin(), end);
}

} // namespace detail


template <class VT>
struct precise
{
    // TODO: document the decoration API
    using decorated_type = VT;

    static_assert(std::is_floating_point<decorated_type>::value, "template paremeter is not a floating number type");


    ~precise() = default;

    precise() : value_ {0} {}
    precise(precise const&) = default;
    precise(precise &&) = default;
    precise& operator=(precise const&) = default;
    precise& operator=(precise &&) = default;


  private:

    template <class T>
    static decorated_type validate(T init_value)
    {
        decorated_type value = static_cast<decorated_type>(init_value);
        if (std::isnormal(init_value) and (T {value} !=  init_value)) {
            throw precision_loss_error("precision loss when creating zmbt::decor::precise<T>");
        }
        return value;
    }

    template <class T>
    static decorated_type validate_str_as(boost::json::string_view str)
    {
        char* end {nullptr};

        auto const value_as_t = detail::strto<T>(str, &end);

        if (end == str.cbegin() ) {
            throw std::invalid_argument("zmbt::decor::precise<T> string parsing error");
        }

        // test reverse
        if (std::isnormal(value_as_t) and (value_as_t != detail::strto<decorated_type>(str, nullptr))) {
            throw precision_loss_error("precision loss when creating zmbt::decor::precise<T>");
        }

        return validate(value_as_t);
    }


    static decorated_type validate_str(boost::json::string_view str)
    {
        // hex notation shall be precise
        if (0 == (std::strncmp(str.cbegin(), "0x", 2) & std::strncmp(str.cbegin(), "0X", 2)))
        {
            return validate_str_as<long double>(str);
        }
        else if (auto const N = str.size())
        {
            switch (str[N-1])
            {
                case 'f':
                case 'F':
                    return validate_str_as<float>(str);
                    break;
                case 'l':
                case 'L':
                    return validate_str_as<long double>(str);
                    break;
                default:
                    return validate_str_as<double>(str);
                    break;
            }
        }
        else {
            throw std::invalid_argument("zmbt::decor::precise<T> string parsing error");
            return {};
        }
    }

  public:

    explicit precise(decorated_type v) : value_{v} {}

    template<class T, class = std::enable_if_t<std::is_arithmetic<T>::value>>
    precise(T const v) : value_{validate(v)}
    {
    }

    template <class T , class = std::enable_if_t<std::is_arithmetic<T>::value>>
    precise& operator=(T const v)
    {
        value_ = validate(v);
        return *this;
    }


    template<class T>
    precise(precise<T> other) : value_{validate(other.value())}
    {
    }

    template <class T>
    precise& operator=(precise<T> other)
    {
        value_ = validate(other.value());
        return *this;
    }

    precise(boost::json::string_view str) : value_{validate_str(str)}
    {
    }

    precise& operator=(boost::json::string_view str)
    {
        value_ = validate_str(str);
        return *this;
    }

    decorated_type value() const
    {
        return value_;
    }

    bool operator<(precise other) const
    {
        return value() < other.value();
    }

    bool operator<(boost::json::string_view str) const
    {
        return value() < validate_str(str);
    }

    bool operator==(precise other) const
    {
        return value() == other.value();
    }

    bool operator==(boost::json::string_view str) const
    {
        return value() == validate_str(str);
    }


    operator decorated_type() const
    {
        return value();
    }

    std::string stringify() const
    {
        char buff[256] {};
        std::snprintf(buff, 256, "%a", value());
        return std::string(buff);
    }

  private:
    decorated_type value_;
};

template <class T>
constexpr zmbt::type_tag<precise<T>> Precise;

} // namespace decor

namespace reflect {

template <class T>
struct custom_serialization<decor::precise<T>> {


    static boost::json::value json_from(decor::precise<T> const t)
    {
        boost::json::value v;
        v.emplace_string() = t.stringify();
        return v;
    }

    static decor::precise<T>
    dejsonize(boost::json::value const& v)
    {
        decor::precise<T> result;
        switch (v.kind()) {
            case boost::json::kind::string:
                result = v.get_string().data();
                break;
            case boost::json::kind::double_:
                result = v.get_double();
                break;
            case boost::json::kind::int64:
                result = v.get_int64();
                break;
            case boost::json::kind::uint64:
                result = v.get_uint64();
                break;
            default:
                throw std::invalid_argument("zmbt::decor::precise<T> conversion failure");
                break;
        }
        return result;
    }

};


} // namespace reflect
} // namespace zmbt

#endif  // ZMBT_MAPPING_PRECISE_REAL_HPP_