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

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

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

+0 -0

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

+0 -0

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

+0 -0

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

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 ?

+0 -0

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.

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 :(

+0 -0

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;
}
+0 -0

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)

+0 -0

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

+0 -0

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 ?

+0 -0

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

+0 -0

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

+0 -0

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