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

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour à tous,

je suis toujours en train de coder une lib mathématique en C++ pour apprendre et pratiquer le langage, mais il se trouve que pour certaines fonctions (ceil, floor, around) j'ai besoin d'extraire la partie décimale du nombre, logiquement j'ai penser à un code comme celui ci :

1
2
3
4
5
6
float extract_decimal(float x)
{
    int   integer_part = static_cast<int>(x);
    float decimal_part = x - integer_part;
    return decimal_part;
}

mais il inclue un cast ce qui n'est pas très mal en soit mais ce serait mieux sans, ainsi est ma question :

Est-il possible d'écrire cette fonction sans cast ?

Je vous remercie d'avance de vos réponses et vous souhaite une bonne journée

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0

Cette réponse a aidé l'auteur du sujet

Pourquoi ce code ne te convient pas ? (en dehors de l'oubli des const, ce qui pourrait peut être améliorer le code généré par le compilateur).

Si c'est pour des raisons de performances, il ne sera pas possible (en première estimation rapide) de faire mieux sans passer par du code assembleur (si le proc supporte des instructions d'arrondis de réels directement)

cf peut etre http://stackoverflow.com/questions/5589383/extract-fractional-part-of-double-efficiently-in-c

Édité par gbdivers

+0 -0
Auteur du sujet

Pourquoi ce code ne te convient pas ? (en dehors de l'oubli des const, ce qui pourrait peut être améliorer le code généré par le compilateur).

gbdivers

je suis sur d'avoir déjà lu qu'il fallait éviter les cast sinon effectivement je rajoute les const et j'implémente ça. Merci de ta réponse :)

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Auteur du sujet

La valeur d'un float peut dépasser largement le maximum possible pour un int, donc cette solution n'est pas véritablement correcte AMHA.

yoch

du coup il vaudrait mieux un long int ?

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Staff

Salut,

du coup il vaudrait mieux un long int ?

LudoBike

Même problème, la capacité d'un long int et même d'un long long int est largement insuffisante. Pour rappel, si le flottant est représenté suivant la norme IEEE 754 en simple précision, le maximum représentable est de l'ordre de $10^{38}$.

+0 -0
Auteur du sujet

Salut,

du coup il vaudrait mieux un long int ?

LudoBike

Même problème, la capacité d'un long int et même d'un long long int est largement insuffisante. Pour rappel, si le flottant est représenté suivant la norme IEEE 754 en simple précision, le maximum représentable est de l'ordre de $10^{38}$.

Taurre

ah merde mince, du coup il y a pas une autre solution ?

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Staff

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.

+0 -0
Auteur du sujet

Si tu as déjà codé une fonction fmod pour ta lib, alors tu peux faire un simple return x - fmod(x, 1).

Olybri

Mais oui je me disais bien que j'avais déjà trouvé une solution (en me brossant les dents), la partie décimale d'un nombre réel est le résultat du modulo de 1

par contre j'ai essayé sur python ça fait un truc bizarre

1
2
3
4
5
6
7
8
>>> x = 5.2
>>> type(x)
<class 'float'>
>>> y = x % 1
>>> type(y)
<class 'float'>
>>> print(y)
0.20000000000000018

juste d'où viennent les 1,8*10^-16 qui se sont rajouté ?

Sinon je me demandais à quoi servais fmod() mais je crois que j'ai trouvé, je ne savais pas que le modulo ne marchais pas avec les float et les double :(

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Staff

Erreur d'arrondi. 5,2 n'est pas représentable de manière exacte ni dans un float, ni dans un double. Teste ça :

1
print(str.format('{:.50}',5.2))

Édité par Aabu

+0 -0
Auteur du sujet

Erreur d'arrondi. $5,2$ n'est pas représentable de manière exacte. Teste ça :

1
print(str.format('{:.50}',5.2))

Aabu

ok d'accord :)

sinon il faut que je trouve comment coder fmod sans cast mais je trouve pas

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Auteur du sujet

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

1
2
3
4
5
6
7
float math::fmod(float numer, float denom)
{
    while (numer > denom)
    numer -= denom;

    return numer;
}

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Auteur du sujet

Bon, ce n'est pas ce qui se fait de plus efficace non plus…

yoch

Je le sais bien mais à défaut d'autre chose c'est bien. (sinon si tu as quelque chose de plus efficace je suis preneur, enfin avec des explications parce que je déteste copier un code que je ne comprends pas)

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0

Cette réponse a aidé l'auteur du sujet

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.)

Édité par Olybri

+0 -0
Staff

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.

Olybri

Cela prendra surtout un temps infini si le nombre représenté est trop important (edit : bon, faut voir en Python vu qu'il utilise GMP derrière). En effet, vu la représentation des flottants, passez un certain stade, seuls les chiffres significatifs sont conservés.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <float.h>
#include <stdio.h>


int
main(void)
{
    printf("%d\n", FLOAT_MAX == FLOAT_MAX - 1);
    return 0;
}
1
1

La même chose vaut en C++.

Édit : @Aabu : tu peux préciser ta pensée, je ne comprends pas bien ce que tu veux dire ?

Édité par Taurre

+0 -0
Staff

Édit : @Aabu : tu peux préciser ta pensée, je ne comprends pas bien ce que tu veux dire ?

Quelle pensée ? Je disais juste que 5,2 n'est pas un flottant, et donc le réel est arrondi lorsqu'il est stocké. On peut le voir en affichant assez de décimales (ici en Python) :

1
2
>>> print(str.format('{:.21}',5.2)) # affiche un nombre avec 21 décimales
5.20000000000000017753

Cela explique que la partie fractionnaire donnée par Python (ou d'autres langages) traîne des décimales inattendues.

Édité par Aabu

+0 -0
Auteur du sujet

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

Ok merci je vais le coder mais juste sa veux dire quoi template<typename T> ?

PS: si vous voulez aller voir, le code est sur github

Édité par LudoBike

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Auteur du sujet

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)

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

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