Extraire la partie décimal d'un float/double sans cast

Le problème exposé dans ce sujet a été résolu.

Du coup j'ai compris pour les template, donc je peux transformer ce code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
int math::abs(int const x)
{
    if (x == 0)
    return 0;
    else if (x > 0)
    return x;
    else
    return -x;
}

float math::abs(float const x)
{
    if (x == 0)
    return 0;
    else if (x > 0)
    return x;
    else
    return -x;
}

en celui là:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
template<typename T>
T math::abs(T const x)
{
    if (x == 0)
    return 0;
    else if (x > 0)
    return x;
    else
    return -x;
}

int math::abs(int const x)
{
    return math::abs<int>(x);
}

float math::abs(float const x)
{
    return math::abs<float>(x);
}

mais du coup je me pose une autre question qui est "est-ce que je peux limiter les types que ma template T peux avoir ?" (je veux pas que l'on essaye d'avoir la valeur absolue d'une string)

+0 -0

Donc à la fin sa fait ça

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template<typename T>
std::enable_if_t<std::is_arithmetic<T>, T>
T math::abs(T const x)
{
    if (x == 0)
    return 0;
    else if (x > 0)
    return x;
    else
    return -x;
}
+0 -0

Les exceptions sont faites pour gérer les cas qui correspondent à des erreurs qui sont dues à ton environnement d'exécution : des erreurs contre lesquelles ni toi, ni le développeur qui utilise ta bibliothèque, ne peuvent se peuvent se prémunir.

Ici, l'erreur est due à un non respect de la pré-condition de la fonction, c'est donc une erreur contre laquelle un utilisateur développeur peut se prémunir : en écrivant son code correctement (non mais). L'idée est plutôt de la traiter à l'aide d'une assertion :

1
2
3
4
int abs(int a){
  assert(a > INT_MIN && "abs: pre-cond: out of range parameter a");
  return (a < 0)? -a : a;
}

Pour une version à template, il faudra aller chercher du côté des fonctions de "numeric_limits".

Un peu de lecture sur la PPC.

@Aabu : je parlais de ce message. ;)

Le truc, c'est que les plus grands flottants sont en fait des entiers (la précision ne permet pas d'avoir de décimales). Du coup, avec un branchement, tu peux éliminer ce cas là : si le flottant est grand, il n'y a rien à convertir. La glibc fait d'ailleurs quelque chose de similaire pour floor.

Aabu

+0 -0

@Aabu : je parlais de ce message. ;) […]

Taurre

Si j'ai bien compris, ça veut simplement dire qu'un float trop grand devient trop imprécis, et qu'à une certaine limite, c'est forcément un nombre entier.

Pour illustrer :

1
2
float n = 1'000'000'000.123;
std::cout << std::fixed << n << std::endl;
1
2
$ clang++ -std=c++14 main.cpp && ./a.out
1000000000.000000

Donc ici, $ floor(n) = n $ .

Les exceptions sont faites pour gérer les cas qui correspondent à des erreurs qui sont dues à ton environnement d'exécution : des erreurs contre lesquelles ni toi, ni le développeur qui utilise ta bibliothèque, ne peuvent se peuvent se prémunir.

Ici, l'erreur est due à un non respect de la pré-condition de la fonction, c'est donc une erreur contre laquelle un utilisateur développeur peut se prémunir : en écrivant son code correctement (non mais). L'idée est plutôt de la traiter à l'aide d'une assertion :

1
2
3
4
int abs(int a){
  assert(a > INT_MIN && "abs: pre-cond: out of range parameter a");
  return (a < 0)? -a : a;
}

Pour une version à template, il faudra aller chercher du côté des fonctions de "numeric_limits".

Un peu de lecture sur la PPC.

Ksass`Peuk

Ok merci pour ces informations.

Edit : j'ai quand même une question, est-ce que ce cas ou -INT_MIN ne loge pas dans INT_MAX existe avec d'autres types (peut-être tous) ?

Reedit : D'après cette page de cppreference cela ne concerne que les entiers donc mon assert est comme ça

1
assert((is_integer<T>) ? x > numeric_limits<T>::min() : true)

Comme je n'ai plus de questions et que le sujet s'écarte fortement du problème d'origine je vais le mettre comme résolu et marqué les réponses qui mon aidées.

Merci à tous de vos réponses j’aurais beaucoup appris sur ce sujet :)

+0 -0

Edit : j'ai quand même une question, est-ce que ce cas ou -INT_MIN ne loge pas dans INT_MAX existe avec d'autres types (peut-être tous) ?

La norme n'offre aucune garantie sur la représentation des entiers signés, et donc sur les relations entre les bornes positives et négatives.

Mais à moins d'être sur une architecture ésotérique, les entiers signés sont représentés en complément à deux, ce qui signifie effectivement que - *_MIN n'est pas représentable.

Attention également aux exemples de cppreference, lorsqu'il est indiqué « possible output » cela signifie que le point en question dépend de l'implémentation.

+0 -0

Je retire ce que j'ai dit, j'ai un problème. Ma lib compile sans problème par contre mon code de test m'envoye des erreurs de référence indéfinie

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ludovic at Tardis in ~workspace/C++/Math-library/test on git:master x [14:35:07]
$ make
[ 50%] Linking CXX executable bin/test_math
CMakeFiles/test_math.dir/src/main.cpp.o: dans la fonction « main »:
main.cpp:(.text+0x16): référence indéfinie vers « std::enable_if<std::is_arithmetic<int>::value, int>::type math::abs<int>(int) »
main.cpp:(.text+0x47): référence indéfinie vers « std::enable_if<std::is_arithmetic<double>::value, double>::type math::abs<double>(double) »
main.cpp:(.text+0x68): référence indéfinie vers « std::enable_if<std::is_arithmetic<int>::value, int>::type math::abs<int>(int) »
../lib/libmath.so: référence indéfinie vers « std::enable_if<std::is_arithmetic<float>::value, float>::type math::abs<float>(float) »
collect2: erreur : ld a retourné 1 code d'état d'exécution
CMakeFiles/test_math.dir/build.make:94 : la recette pour la cible « bin/test_math » a échouée
make[2]: *** [bin/test_math] Erreur 1
CMakeFiles/Makefile2:67 : la recette pour la cible « CMakeFiles/test_math.dir/all » a échouée
make[1]: *** [CMakeFiles/test_math.dir/all] Erreur 2
Makefile:83 : la recette pour la cible « all » a échouée
make: *** [all] Erreur 2

voici mon main pour le test, le reste du code est sur github

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <limits>
#include <libmath/math.h>

using std::cout;
using std::cin;
using std::cerr;
using std::endl;


int main()
{
    int i = std::numeric_limits<int>::min();

    cout << math::abs(-2) << endl;
    cout << math::abs(-2.3434) << endl;
    cout << math::abs(i) << endl;

    return 0;
}
+0 -0

HS: J'ai jeté un coup d’œil à ton GitHub, et il y a un truc que je comprends pas trop : si c'est juste pour t'entraîner, pourquoi t'ennuyer à tout écrire en Anglais ? Car il y a tout de même pas mal de fautes dérangeantes :P (bon après tu me diras que c'est pour entraîner ton Anglais).

Ensuite, je suis de loin pas expert avec Git, mais si c'est un projet perso, tu n'est pas forcément obligé de commiter chaque petit changement (surtout si au final c'est pour se retrouver avec des messages comme "yolo" ou "pardon j'ai sauvegardé après avoir indexé" :D ). Essaie de rassembler les différentes modifications qui ont un lien commun, et si besoin commite avec un message de plusieurs lignes (une ligne courte qui résume, une ligne vide, un pavé qui détail le commit).

D'ailleurs, pense à ajouter l'option -a quand tu commites pour indexer automatiquement tous les fichiers suivis, comme ça tu n'as même plus besoin de faire git add. Et si au pire tu foires un commit, tu peux le "refaire" avec l'option --amend (vu que tu es le seul collaborateur, c'est pas très grave).

Voilou, c'était les conseils du soir sur Git.

je dois être fatigué vu la simplicité de la solution qui réside en une simple boucle

LudoBike

L'idée est bonne, mais teste avec math::fmod(1'234'567'891'234.5678, 1.0) (peut-être qu'il faudra passer par des double) : tu verras que le temps pour soustraire un billion de fois le dénominateur sera probablement trop long.

A mon avis, on pourrait essayer de garder uniquement ce qui est nécessaire pour pouvoir calculer le modulo. Par exemple ici, il suffirait qu'on garde 4.5678. En s'aidant de l'exponentielle en base 10 (j'imagine que tu as déjà une fonction pow), on pourrait imaginer un code comme ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
template<typename T>
T mod(T num, T den)
{
    while(num > den * 10)
    {
        unsigned a{};
        unsigned b{};
        while(num / pow(10, ++a) > 10);
        while(num / pow(10, a) - ++b > 1);
        num -= pow(10, a) * b;
    }

    while(num > den)
        num -= den;

    return num;
}

J’ignore si c'est la manière la plus optimale, mais ça devrait marcher (il faut du moins que pow retourne un double.)

Olybri

Salut,

j'arrive après la bataille mais il me semble que ce que tu proposes ne donnera le bon modulo que pour 1, 2 et 5. Par exemple si tu prends 12 modulo 3 tu obtiendras 2 au lieu de 0.

Pourquoi ne pas utiliser la division euclidienne tout simplement ? En modifiant légèrement ta fonction tu peux obtenir une fonction de division euclidienne et donc ne garder que le reste pour avoir le modulo.

@Guigz12 il y a effectivement une erreur mais je pense que c'est juste un oubli de la part d'Olybri.

En fait ce résultat avec 12 et 3 s'explique au fait que la dernière boucle a cette condition d'arrêt num > den et qu'il manque juste un égal, du coup sur cette exemple comme 12 est multiple de 3 à la dernière itération on à 3 > 3 ce qui est faux et donc arrête la boucle avant de retourner 3. Sinon merci de l'avoir remarqué et signalé. :)

+0 -0

J'avais pas pris en compte la condition d'arrêt de la première boucle… Pour effectivement 12 ça marche.

Du coup c'est plus subtil que ça mais ce n'est quand même pas juste mathématiquement. Contre-exemple : 140 % 7 = 0 mais avec cette fonction on obtient après la premiere boucle num = 40 et donc comme résultat 1.

Pour faire la division euclidienne c'est à peu près la même fonction.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template<typename T>
T mod(T num, T den)
{
    unsigned reste{};
    while(num > 10)
    {
        unsigned a{};
        unsigned b{};
        //calcule le digit le plus à gauche du dividande
        while(num / pow(10, ++a) > 10);
        while(num / pow(10, a) - ++b > 1);

        //mis a jour du reste
        reste = reste * 10 + b

        //suppression du digit le plus à gauche
        num -= pow(10, a) * b;

        //calcul du nouveau reste (on ne calcule pas le quotien mais il suffit de compter le nombre de tours de boucle)
        while(reste > den)
            reste -= den
    }
    return reste;
}

D'après la division euclidienne on retrouve bien le modulo. Je pense que ça répond au problème. Désolé si j'ai fait des erreurs de syntaxe, je n'ai jamais fait de C++.

+0 -0

j'arrive après la bataille mais il me semble que ce que tu proposes ne donnera le bon modulo que pour 1, 2 et 5. Par exemple si tu prends 12 modulo 3 tu obtiendras 2 au lieu de 0.

Guigz12

Pour les lignes 13-14, effectivement, c'est une erreur. En fait il suffirait de modifier l'opérateur de comparaison et avoir num >= den.

Cependant, je n'ai pas assez réfléchi et en fait, les lignes 4-11 sont stupides, car elles ne fonctionnent qu'avec den = 1 ou den = 10 etc… Donc cette fonction-là n'a pas trop d'intérêt (si ce n'est pour être réécrite en fonction floor). Sur le coup, je vois pas comment corriger le truc.

Pour faire la division euclidienne c'est à peu près la même fonction. […]

Guigz12

Je crois que tu as dû oublier un truc. Tu fais des opérations sur num mais après tu ne l'utilises plus. Du coup ça ne fonctionne pas (testé avec 2345 par 6). Je suis pas assez expert en maths pour voir comment coder la chose.

+0 -0

Oui je suis allé un peu vite.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
template<typename T>
T mod(T num, T den)
{
    unsigned reste{};
    while(num >= 10)
    {
        unsigned a{};
        unsigned b{};
        //calcule le digit le plus à gauche du dividande
        while(num / pow(10, ++a) > 10);
        while(num / pow(10, a) - ++b > 1);

        //mis a jour du reste
        reste = reste * 10 + b

        //calcul du nouveau reste (on ne calcule pas le quotien mais il suffit de compter le nombre de tours de boucle)
        while(reste > den)
            reste -= den

        //suppression du digit le plus à gauche
        num -= pow(10, a) * b;
    }
    //mis a jour du reste
    reste = reste * 10 + num

    //calcul du nouveau reste
    while(reste > den)
        reste -= den

    return reste;
}

C'est pas très beau mais vous avez compris l'idée.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte