Contenu | Rechercher | Menus

Annonce

Ubuntu 16.04 LTS
Commandez vos DVD et clés USB Ubuntu-fr !

Pour en savoir un peu plus sur l'équipe du forum.

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

#1 Le 07/12/2017, à 00:24

Destroyers

[C++...+] Méta-programmation arithmétique avec des flottants

Bonjour à toi qui passe par là.
Je suis novice en métaprogrammation, quoique j'ai de bonnes bases avec les types_traits. Néanmoins je requière de l'aide.


Version courte : J'ai besoin de connaître le modulo de deux doubles au compile-time. Comment faire ?

version longue : Je code une librairie de gestion d'unités physiques inspirée de std::chrono::duration. Cependant, j'ai des difficultés à représenter certaines sous-unités avec std::ratio. Par exemple std::ratio<1, 1> dans une classe Mass va représenter le Kilogramme, et les valeurs qui peuvent être prises par std::ratio (des intmax_t, limitées à 10^18) m’empêchent de représenter l'unité de masse atomique (qui nécessite un ratio avec un dénominateur valant   ~ 10^35).

Donc j'ai décidé de coder ma propre classe ratio prenant des doubles, ce type pouvant aller jusqu'à 10^310.

template<double const& _Num, double const& _Den = 1>
class ratio{};

Or, je veux tout de même que_ Num et _Den soient des entiers. Je place donc un static_assert qui teste cette condition.
Pour se faire, il suffit de tester si :     _Num % 1 = 0
Or, vous savez comme moi que l'opérateur % ne fonctionne pas avec des flottants. std::remainder ou std::modulus ne sont pas des solutions car ce sont des fonctions appelées au run-tume, or je cherche a optimiser un maximum...

La bonne solution pour moi était celle-ci :

template <double const& X, double const& Y, bool>
class quotient_impl;


template <double const& X, double const& Y>
class quotient_impl<X, Y, false>
{
  static constexpr double newY = X - Y;
public:
  static constexpr double value = quotient_impl<X, newY, X < Y >::value;
};


template <double const& X, double const& Y>
class quotient_impl<X, Y, true>
{
public:
  static constexpr double value = Y;
};


template <double const& X, double const& Y>
class quotient
{
public:
  static constexpr double value = quotient_impl<X, Y, X < Y >::value;
};


template <double const& X, double const& Y>
class modulo
{
public:
  static constexpr double value = X - (Y * quotient<X, Y>::value);
};

Voici le main que je fournis pour le test :

#include <iostream>

static constexpr double num = 10;
static constexpr double den = 3;

int main()
{
    //je test quotient et non molulo, donc c'est pas modulo qui pose problème
    std::cout << quotient<num, den>::value <<'\n';
    return 0;
}

Et l'erreur est sans appel :

fatal error: template instantiation depth exceeds maximum of 900
 static constexpr double newY = X - Y;
                         ^~~~

C'est comme si la condition X<Y n'était jamais vraie, Alors qu'elle devrait bien le devenir au bout de 3 "itérations" (récursions).
Merci de m'éclairer smile

PS : newY est nécessaire. En effet je ne peux pas faire directement quotient_impl<X, X - Y, X < Y > car X - Y est une rvalue et un template double non-type ne peux prendre que des expressions constantes (donc un static constexpr double... soit une lvalue).

Dernière modification par Destroyers (Le 07/12/2017, à 00:29)

Hors ligne

#2 Le 07/12/2017, à 10:28

Destroyers

Re : [C++...+] Méta-programmation arithmétique avec des flottants

Très bien, j'étais simplement dans les choux, j'avais simplement mal fait les calculs, désolé d'avoir posté un topic inutile.

Malgré tout, voici la solution pour ceux que ça intéresse :

template <double const& X, double const& Y, bool>
class modulo_impl;


template <double const& X, double const& Y>
class modulo_impl<X, Y, false>
{
  static constexpr double newX = X < Y ? X : X - Y;
public:
  static constexpr double value = modulo_impl<newX, Y, X < Y >::value;
};


template <double const& X, double const& Y>
class modulo_impl<X, Y, true>
{
public:
  static constexpr double value = X;
};


template <double const& X, double const& Y>
class modulo
{
public:
  static constexpr double value = modulo_impl<X, Y, X < Y >::value;
};

Ce n'est pas Y qu'il faut modifier à chaque récursion, c'est X. En plus de fonctionner, cela donne directement le modulo, donc plus besoin de passer par le calcul du quotient.

Je me pose néanmoins toujours une petite question, est ce que la condition :

static constexpr double newX = X < Y ? X : X - Y;

se résout bien au compile-time ?

Voila, si vous avez malgré tout des suggestions ou des optimisations à apporter, je suis preneur smile





EDIT : Cette manière de faire ne fonctionne que si X et Y sont positifs. Vu que je n'ai pas besoin de gérer les nombres négatifs vu l'utilisation que je veux en faire, j'ai ajouté des static_assert

template <double const& X, double const& Y>
class modulo
{
public:
  static_assert(X >= 0 && Y >= 0 , "modulo doesn't manage negative numbers.");
  static_assert(Y > 0 , "denominator cannot be zero.");

  static constexpr double value = modulo_impl<X, Y, X < Y >::value;
};

EDIT 2 : Si le numérateur du modulo est 900 fois plus grand que le dénominateur, il y a trop de récursion à réaliser et le compilateur provoque une erreur (depth exeeds maximum of 900). J'utilise alors une option de compilation me permettant d'augmenter cette limite, mais même avec cette option on ne peut pas monter à plus de 65535 récursions... Or j'ai besoin de 10^35 récursions... Je pense donc qu'il faut que je lâche la métaprogrammation par template et que je teste la métaprogrammation par expression constante... cela dit, j'ai peur que tout ne se fasse pas au compile-time avec cette seconde méthode.


EDIT 3 : En fait, la métaprogrammation à base d’expression constante a le même souci, il y a la même limitation de récursion qu'avec les templates. Sauf que cela ne se voit pas, cela ne provoque pas d'erreur de compilation car si le compilateur ne peut pas résoudre la récursion, alors il considère l'expression constante comme une simple fonction normale qui sera évaluée au run-time. Enfin bref, je pensais que la métaprogrammation était quelque chose de fabuleux, mais elle possède tout de même ses limites (de grosses limites) : elle ne fonctionne que pour des "petits" nombres et n'est pas très adaptée pour les calculs de flottants. Je vais donc faire une fonction inline pour calculer mon modulo et... espérer que le compilateur accepte de l'inliner (j'ai besoin d'utiliser std::remainder() qui n'est pas inline mais qui retourne un constexpr... j’espère que ça va donc marcher).

Dernière modification par Destroyers (Le 07/12/2017, à 17:37)

Hors ligne