web-dev-qa-db-fra.com

Quelle est la meilleure façon de faire des maths à virgule fixe?

J'ai besoin d'accélérer un programme pour la Nintendo DS qui n'a pas de FPU, donc je dois changer les mathématiques à virgule flottante (qui sont émulées et lentes) en virgule fixe.

J'ai commencé par changer les flottants en pouces et chaque fois que je devais les convertir, j'utilisais x >> 8 pour convertir la variable à virgule fixe x en nombre réel et x << 8 pour convertir en virgule fixe. Bientôt, j'ai découvert qu'il était impossible de garder une trace de ce qui devait être converti et j'ai également réalisé qu'il serait difficile de changer la précision des nombres (8 dans ce cas.)

Ma question est, comment dois-je rendre cela plus facile et toujours rapide? Dois-je créer une classe FixedPoint, ou simplement un typedef ou une structure FixedPoint8 avec certaines fonctions/macros pour les convertir, ou autre chose? Dois-je mettre quelque chose dans le nom de la variable pour montrer qu'il est à virgule fixe?

45
Jeremy Ruten

Vous pouvez essayer ma classe de points fixes (Dernière disponible @ https://github.com/eteran/cpp-utilities )

// From: https://github.com/eteran/cpp-utilities/edit/master/Fixed.h
// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math
/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015 Evan Teran
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef FIXED_H_
#define FIXED_H_

#include <ostream>
#include <exception>
#include <cstddef> // for size_t
#include <cstdint>
#include <type_traits>

#include <boost/operators.hpp>

namespace numeric {

template <size_t I, size_t F>
class Fixed;

namespace detail {

// helper templates to make magic with types :)
// these allow us to determine resonable types from
// a desired size, they also let us infer the next largest type
// from a type which is Nice for the division op
template <size_t T>
struct type_from_size {
    static const bool is_specialized = false;
    typedef void      value_type;
};

#if defined(__GNUC__) && defined(__x86_64__)
template <>
struct type_from_size<128> {
    static const bool           is_specialized = true;
    static const size_t         size = 128;
    typedef __int128            value_type;
    typedef unsigned __int128   unsigned_type;
    typedef __int128            signed_type;
    typedef type_from_size<256> next_size;
};
#endif

template <>
struct type_from_size<64> {
    static const bool           is_specialized = true;
    static const size_t         size = 64;
    typedef int64_t             value_type;
    typedef uint64_t            unsigned_type;
    typedef int64_t             signed_type;
    typedef type_from_size<128> next_size;
};

template <>
struct type_from_size<32> {
    static const bool          is_specialized = true;
    static const size_t        size = 32;
    typedef int32_t            value_type;
    typedef uint32_t           unsigned_type;
    typedef int32_t            signed_type;
    typedef type_from_size<64> next_size;
};

template <>
struct type_from_size<16> {
    static const bool          is_specialized = true;
    static const size_t        size = 16;
    typedef int16_t            value_type;
    typedef uint16_t           unsigned_type;
    typedef int16_t            signed_type;
    typedef type_from_size<32> next_size;
};

template <>
struct type_from_size<8> {
    static const bool          is_specialized = true;
    static const size_t        size = 8;
    typedef int8_t             value_type;
    typedef uint8_t            unsigned_type;
    typedef int8_t             signed_type;
    typedef type_from_size<16> next_size;
};

// this is to assist in adding support for non-native base
// types (for adding big-int support), this should be fine
// unless your bit-int class doesn't nicely support casting
template <class B, class N>
B next_to_base(const N& rhs) {
    return static_cast<B>(rhs);
}

struct divide_by_zero : std::exception {
};

template <size_t I, size_t F>
Fixed<I,F> divide(const Fixed<I,F> &numerator, const Fixed<I,F> &denominator, Fixed<I,F> &remainder, typename std::enable_if<type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    typedef typename Fixed<I,F>::next_type next_type;
    typedef typename Fixed<I,F>::base_type base_type;
    static const size_t fractional_bits = Fixed<I,F>::fractional_bits;

    next_type t(numerator.to_raw());
    t <<= fractional_bits;

    Fixed<I,F> quotient;

    quotient  = Fixed<I,F>::from_base(next_to_base<base_type>(t / denominator.to_raw()));
    remainder = Fixed<I,F>::from_base(next_to_base<base_type>(t % denominator.to_raw()));

    return quotient;
}

template <size_t I, size_t F>
Fixed<I,F> divide(Fixed<I,F> numerator, Fixed<I,F> denominator, Fixed<I,F> &remainder, typename std::enable_if<!type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    // NOTE(eteran): division is broken for large types :-(
    // especially when dealing with negative quantities

    typedef typename Fixed<I,F>::base_type     base_type;
    typedef typename Fixed<I,F>::unsigned_type unsigned_type;

    static const int bits = Fixed<I,F>::total_bits;

    if(denominator == 0) {
        throw divide_by_zero();
    } else {

        int sign = 0;

        Fixed<I,F> quotient;

        if(numerator < 0) {
            sign ^= 1;
            numerator = -numerator;
        }

        if(denominator < 0) {
            sign ^= 1;
            denominator = -denominator;
        }

            base_type n      = numerator.to_raw();
            base_type d      = denominator.to_raw();
            base_type x      = 1;
            base_type answer = 0;

            // egyptian division algorithm
            while((n >= d) && (((d >> (bits - 1)) & 1) == 0)) {
                x <<= 1;
                d <<= 1;
            }

            while(x != 0) {
                if(n >= d) {
                    n      -= d;
                    answer += x;
                }

                x >>= 1;
                d >>= 1;
            }

            unsigned_type l1 = n;
            unsigned_type l2 = denominator.to_raw();

            // calculate the lower bits (needs to be unsigned)
            // unfortunately for many fractions this overflows the type still :-/
            const unsigned_type lo = (static_cast<unsigned_type>(n) << F) / denominator.to_raw();

            quotient  = Fixed<I,F>::from_base((answer << F) | lo);
            remainder = n;

        if(sign) {
            quotient = -quotient;
        }

        return quotient;
    }
}

// this is the usual implementation of multiplication
template <size_t I, size_t F>
void multiply(const Fixed<I,F> &lhs, const Fixed<I,F> &rhs, Fixed<I,F> &result, typename std::enable_if<type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    typedef typename Fixed<I,F>::next_type next_type;
    typedef typename Fixed<I,F>::base_type base_type;

    static const size_t fractional_bits = Fixed<I,F>::fractional_bits;

    next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw()));
    t >>= fractional_bits;
    result = Fixed<I,F>::from_base(next_to_base<base_type>(t));
}

// this is the fall back version we use when we don't have a next size
// it is slightly slower, but is more robust since it doesn't
// require and upgraded type
template <size_t I, size_t F>
void multiply(const Fixed<I,F> &lhs, const Fixed<I,F> &rhs, Fixed<I,F> &result, typename std::enable_if<!type_from_size<I+F>::next_size::is_specialized>::type* = 0) {

    typedef typename Fixed<I,F>::base_type base_type;

    static const size_t fractional_bits = Fixed<I,F>::fractional_bits;
    static const size_t integer_mask    = Fixed<I,F>::integer_mask;
    static const size_t fractional_mask = Fixed<I,F>::fractional_mask;

    // more costly but doesn't need a larger type
    const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits;
    const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits;
    const base_type a_lo = (lhs.to_raw() & fractional_mask);
    const base_type b_lo = (rhs.to_raw() & fractional_mask);

    const base_type x1 = a_hi * b_hi;
    const base_type x2 = a_hi * b_lo;
    const base_type x3 = a_lo * b_hi;
    const base_type x4 = a_lo * b_lo;

    result = Fixed<I,F>::from_base((x1 << fractional_bits) + (x3 + x2) + (x4 >> fractional_bits));

}
}

/*
 * inheriting from boost::operators enables us to be a drop in replacement for base types
 * without having to specify all the different versions of operators manually
 */
template <size_t I, size_t F>
class Fixed : boost::operators<Fixed<I,F>> {
    static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes");

public:
    static const size_t fractional_bits = F;
    static const size_t integer_bits    = I;
    static const size_t total_bits      = I + F;

    typedef detail::type_from_size<total_bits>             base_type_info;

    typedef typename base_type_info::value_type            base_type;
    typedef typename base_type_info::next_size::value_type next_type;
    typedef typename base_type_info::unsigned_type         unsigned_type;

public:
    static const size_t base_size          = base_type_info::size;
    static const base_type fractional_mask = ~((~base_type(0)) << fractional_bits);
    static const base_type integer_mask    = ~fractional_mask;

public:
    static const base_type one = base_type(1) << fractional_bits;

public: // constructors
    Fixed() : data_(0) {
    }

    Fixed(long n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(unsigned long n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(int n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(unsigned int n) : data_(base_type(n) << fractional_bits) {
        // TODO(eteran): assert in range!
    }

    Fixed(float n) : data_(static_cast<base_type>(n * one)) {
        // TODO(eteran): assert in range!
    }

    Fixed(double n) : data_(static_cast<base_type>(n * one))  {
        // TODO(eteran): assert in range!
    }

    Fixed(const Fixed &o) : data_(o.data_) {
    }

    Fixed& operator=(const Fixed &o) {
        data_ = o.data_;
        return *this;
    }

private:
    // this makes it simpler to create a fixed point object from
    // a native type without scaling
    // use "Fixed::from_base" in order to perform this.
    struct NoScale {};

    Fixed(base_type n, const NoScale &) : data_(n) {
    }

public:
    static Fixed from_base(base_type n) {
        return Fixed(n, NoScale());
    }

public: // comparison operators
    bool operator==(const Fixed &o) const {
        return data_ == o.data_;
    }

    bool operator<(const Fixed &o) const {
        return data_ < o.data_;
    }

public: // unary operators
    bool operator!() const {
        return !data_;
    }

    Fixed operator~() const {
        Fixed t(*this);
        t.data_ = ~t.data_;
        return t;
    }

    Fixed operator-() const {
        Fixed t(*this);
        t.data_ = -t.data_;
        return t;
    }

    Fixed operator+() const {
        return *this;
    }

    Fixed& operator++() {
        data_ += one;
        return *this;
    }

    Fixed& operator--() {
        data_ -= one;
        return *this;
    }

public: // basic math operators
    Fixed& operator+=(const Fixed &n) {
        data_ += n.data_;
        return *this;
    }

    Fixed& operator-=(const Fixed &n) {
        data_ -= n.data_;
        return *this;
    }

    Fixed& operator&=(const Fixed &n) {
        data_ &= n.data_;
        return *this;
    }

    Fixed& operator|=(const Fixed &n) {
        data_ |= n.data_;
        return *this;
    }

    Fixed& operator^=(const Fixed &n) {
        data_ ^= n.data_;
        return *this;
    }

    Fixed& operator*=(const Fixed &n) {
        detail::multiply(*this, n, *this);
        return *this;
    }

    Fixed& operator/=(const Fixed &n) {
        Fixed temp;
        *this = detail::divide(*this, n, temp);
        return *this;
    }

    Fixed& operator>>=(const Fixed &n) {
        data_ >>= n.to_int();
        return *this;
    }

    Fixed& operator<<=(const Fixed &n) {
        data_ <<= n.to_int();
        return *this;
    }

public: // conversion to basic types
    int to_int() const {
        return (data_ & integer_mask) >> fractional_bits;
    }

    unsigned int to_uint() const {
        return (data_ & integer_mask) >> fractional_bits;
    }

    float to_float() const {
        return static_cast<float>(data_) / Fixed::one;
    }

    double to_double() const        {
        return static_cast<double>(data_) / Fixed::one;
    }

    base_type to_raw() const {
        return data_;
    }

public:
    void swap(Fixed &rhs) {
        using std::swap;
        swap(data_, rhs.data_);
    }

public:
    base_type data_;
};

// if we have the same fractional portion, but differing integer portions, we trivially upgrade the smaller type
template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator+(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l + r;
}

template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator-(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l - r;
}

template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator*(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l * r;
}

template <size_t I1, size_t I2, size_t F>
typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator/(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {

    typedef typename std::conditional<
        I1 >= I2,
        Fixed<I1,F>,
        Fixed<I2,F>
    >::type T;

    const T l = T::from_base(lhs.to_raw());
    const T r = T::from_base(rhs.to_raw());
    return l / r;
}

template <size_t I, size_t F>
std::ostream &operator<<(std::ostream &os, const Fixed<I,F> &f) {
    os << f.to_double();
    return os;
}

template <size_t I, size_t F>
const size_t Fixed<I,F>::fractional_bits;

template <size_t I, size_t F>
const size_t Fixed<I,F>::integer_bits;

template <size_t I, size_t F>
const size_t Fixed<I,F>::total_bits;

}

#endif

Il est conçu pour remplacer quasiment les flotteurs/doubles et a une précision au choix. Il utilise boost pour ajouter toutes les surcharges d'opérateurs mathématiques nécessaires, vous en aurez donc également besoin (je pense que c'est juste une dépendance d'en-tête, pas une dépendance de bibliothèque).

BTW, l'usage courant pourrait ressembler à ceci:

using namespace numeric;
typedef Fixed<16, 16> fixed;
fixed f;

La seule vraie règle est que le nombre doit correspondre à une taille native de votre système telle que 8, 16, 32, 64.

47
Evan Teran

Dans les implémentations C++ modernes, il n'y aura pas de pénalité de performance pour l'utilisation d'abstractions simples et allégées, telles que des classes concrètes. Le calcul en virgule fixe est précisément l'endroit où l'utilisation d'une classe correctement conçue vous évitera de nombreux bugs.

Par conséquent, vous devez écrire une classe FixedPoint8 . Testez-le et déboguez-le soigneusement. Si vous devez vous convaincre de ses performances par rapport à l'utilisation de nombres entiers simples, mesurez-les.

Il vous évitera bien des ennuis en déplaçant la complexité du calcul en virgule fixe vers un seul endroit.

Si vous le souhaitez, vous pouvez augmenter encore l'utilité de votre classe en en faisant un modèle et en remplaçant l'ancien FixedPoint8 avec, disons, typedef FixedPoint<short, 8> FixedPoint8; Mais sur votre architecture cible, ce n'est probablement pas nécessaire, évitez donc la complexité des modèles au début.

Il existe probablement une bonne classe de points fixes quelque part sur Internet - je commencerais à chercher dans les bibliothèques Boost .

31
Antti Kissaniemi

Votre code à virgule flottante utilise-t-il réellement le point décimal? Si c'est le cas:

Vous devez d'abord lire l'article de Randy Yates sur l'Intro to Fixed Point Math: http://www.digitalsignallabs.com/fp.pdf

Ensuite, vous devez faire un "profilage" sur votre code à virgule flottante pour déterminer la plage appropriée de valeurs à virgule fixe requises aux points "critiques" de votre code, par exemple U (5,3) = 5 bits à gauche, 3 bits à droite, non signé.

À ce stade, vous pouvez appliquer les règles arithmétiques du document mentionné ci-dessus; les règles précisent comment interpréter les bits qui résultent d'opérations arithmétiques. Vous pouvez écrire des macros ou des fonctions pour effectuer les opérations.

Il est pratique de conserver la version en virgule flottante afin de comparer les résultats en virgule flottante et en virgule fixe.

9
ryu

La modification des représentations à virgule fixe est communément appelée "mise à l'échelle".

Si vous pouvez le faire avec une classe sans pénalité de performance, alors c'est la voie à suivre. Cela dépend fortement du compilateur et de la façon dont il s'aligne. S'il y a une pénalité de performance en utilisant des classes, vous avez besoin d'une approche de style C plus traditionnelle. L'approche OOP vous donnera une sécurité de type imposée par le compilateur que l'implémentation traditionnelle ne fait qu'approcher.

@cibyr a une bonne implémentation OOP. Maintenant pour la plus traditionnelle.

Pour garder une trace des variables mises à l'échelle, vous devez utiliser une convention cohérente. Faites une notation à la fin de chaque nom de variable pour indiquer si la valeur est mise à l'échelle ou non, et écrivez les macros SCALE () et UNSCALE () qui se développent en x >> 8 et x << 8.

#define SCALE(x) (x>>8)
#define UNSCALE(x) (x<<8)

xPositionUnscaled = UNSCALE(10);
xPositionScaled = SCALE(xPositionUnscaled);

Utiliser autant de notation peut sembler un travail supplémentaire, mais remarquez comment vous pouvez voir en un coup d'œil que n'importe quelle ligne est correcte sans regarder les autres lignes. Par exemple:

xPositionScaled = SCALE(xPositionScaled);

est évidemment faux, par inspection.

Ceci est une variation de l'idée des applications hongroises qui Joel mentionne dans ce post .

6
Bart

Je n'utiliserais pas du tout de virgule flottante sur un CPU sans matériel spécial pour le gérer. Mon conseil est de traiter TOUS les nombres comme des entiers à l'échelle d'un facteur spécifique. Par exemple, toutes les valeurs monétaires sont en cents sous forme d'entiers plutôt qu'en dollars sous forme de flottants. Par exemple, 0,72 est représenté par l'entier 72.

L'addition et la soustraction sont alors une opération entière très simple telle que (0,72 + 1 devient 72 + 100 devient 172 devient 1,72).

La multiplication est légèrement plus complexe car elle nécessite une multiplication entière suivie d'une échelle de retour telle que (0,72 * 2 devient 72 * 200 devient 14400 devient 144 (scaleback) devient 1,44).

Cela peut nécessiter des fonctions spéciales pour effectuer des mathématiques plus complexes (sinus, cosinus, etc.), mais même celles-ci peuvent être accélérées à l'aide de tables de recherche. Exemple: puisque vous utilisez une représentation à 2 fixes, il n'y a que 100 valeurs dans la plage (0,0,1] (0-99) et sin/cos se répètent en dehors de cette plage, vous n'avez donc besoin que d'une table de recherche de 100 entiers.

À la vôtre, Pax.

6
paxdiablo

Lorsque j'ai rencontré pour la première fois des nombres à virgule fixe, j'ai trouvé l'article de Joe Lemieux, Fixed-point Math in C , très utile, et il suggère une façon de représenter les valeurs à virgule fixe.

Je n'ai pas fini par utiliser sa représentation syndicale pour les nombres à virgule fixe. J'ai surtout de l'expérience avec le point fixe en C, donc je n'ai pas eu la possibilité d'utiliser une classe non plus. Pour la plupart cependant, je pense que définir votre nombre de bits de fraction dans une macro et utiliser des noms de variables descriptives rend cela assez facile à travailler. De plus, j'ai trouvé qu'il est préférable d'avoir des macros ou des fonctions pour la multiplication et surtout la division, ou vous obtenez rapidement du code illisible.

Par exemple, avec 24,8 valeurs:

 #include "stdio.h"

/* Declarations for fixed point stuff */

typedef int int_fixed;

#define FRACT_BITS 8
#define FIXED_POINT_ONE (1 << FRACT_BITS)
#define MAKE_INT_FIXED(x) ((x) << FRACT_BITS)
#define MAKE_FLOAT_FIXED(x) ((int_fixed)((x) * FIXED_POINT_ONE))
#define MAKE_FIXED_INT(x) ((x) >> FRACT_BITS)
#define MAKE_FIXED_FLOAT(x) (((float)(x)) / FIXED_POINT_ONE)

#define FIXED_MULT(x, y) ((x)*(y) >> FRACT_BITS)
#define FIXED_DIV(x, y) (((x)<<FRACT_BITS) / (y))

/* tests */
int main()
{
    int_fixed fixed_x = MAKE_FLOAT_FIXED( 4.5f );
    int_fixed fixed_y = MAKE_INT_FIXED( 2 );

    int_fixed fixed_result = FIXED_MULT( fixed_x, fixed_y );
    printf( "%.1f\n", MAKE_FIXED_FLOAT( fixed_result ) );

    fixed_result = FIXED_DIV( fixed_result, fixed_y );
    printf( "%.1f\n", MAKE_FIXED_FLOAT( fixed_result ) );

    return 0;
}

Qui écrit

 9,0 
 4,5 

Notez qu'il y a toutes sortes de problèmes de dépassement d'entier avec ces macros, je voulais juste garder les macros simples. Ceci est juste un exemple rapide et sale de la façon dont j'ai fait cela en C. En C++, vous pourriez faire quelque chose de beaucoup plus propre en utilisant la surcharge de l'opérateur. En fait, vous pourriez facilement rendre ce code C beaucoup plus joli aussi ...

Je suppose que c'est une façon de dire de longue haleine: je pense que c'est OK d'utiliser une approche typedef et macro. Tant que vous savez clairement quelles variables contiennent des valeurs à virgule fixe, ce n'est pas trop difficile à maintenir, mais ce ne sera probablement pas aussi joli qu'une classe C++.

Si j'étais à votre place, j'essaierais d'obtenir des chiffres de profilage pour montrer où se trouvent les goulots d'étranglement. S'il y en a relativement peu, optez pour un typedef et des macros. Si vous décidez que vous avez besoin d'un remplacement global de tous les flottants avec des mathématiques à virgule fixe, alors vous serez probablement mieux avec une classe.

5
ryan_s

La version originale de Tricks of the Game Programming Gurus contient un chapitre entier sur l'implémentation des mathématiques à virgule fixe.

4
Ana Betts

Quelle que soit la façon dont vous décidez d'aller (je pencherais pour un typedef et certaines macros CPP pour la conversion), vous devrez faire attention à effectuer des conversions dans les deux sens avec une certaine discipline.

Vous constaterez peut-être que vous n'avez jamais besoin de faire des conversions. Imaginez simplement que tout dans le système entier est x256.

1
jfm3
template <int precision = 8> class FixedPoint {
private:
    int val_;
public:
    inline FixedPoint(int val) : val_ (val << precision) {};
    inline operator int() { return val_ >> precision; }
    // Other operators...
};
1
cibyr