Brunni
19/04/2006, 23h07
Hello tout le monde ^^
Voici un tuto qui vous permettra de créer et manipuler des nombres à virgule fixe très simplement en C++, comme si c'étaient des float, mais avec une énorme amélioration de vitesse:
void main() {
fixed i = 0.5;
i += 0.75;
printf("%i, %f", (int)i, (float)i); // => 1, 1.25
}
Dans ce tuto, j'explique comment arriver à ce résultat, mais vous pouvez très bien télécharger le fichier .h tout prêt à la fin du post, il suffit de l'inclure et à vous les fixed! :hp:
Tout d'abord, on va devoir travailler en C++, car le C ne permet pas de faire ça (si vous voulez faire du C, regardez un autre tuto sur les fixed point ;)).
Bien heureusement, le C est compatible C++ (mais pas l'inverse) et en général un programme C compilera en C++ sans souci. Il suffit de renommer les fichiers .c en .cpp pour spécifier que c'est du C++.
Note: Les performances d'un même programme peuvent varier suivant qu'il est compilé en C ou en C++.
Pour créer ce nouveau type de données, nous allons profiter des fonctionnalités propres au C++ que sont les classes et la surcharge d'opérateurs. Tout d'abord, une classe, c'est simplement une évolution des structures en C: elle est designée pour contenir plusieurs paramètres, tel qu'un objet dans un jeu:
class OBJET {
int x, y; //Positions
IMAGE *img; //Sprite de l'objet
};
En plus de ça, les classes sont chargées d'effectuer des tâches, contrairement aux structures en C. On peut par exemple définir une fonction (qu'on appelle méthode lorsqu'elle est contenue dans une classe) dans la classe OBJET qui serait chargée de dessiner l'objet:
class OBJET {
int x, y; //Positions
IMAGE *img; //Sprite de l'objet
void affiche()
{
printf("je suis un objet aux positions %i, %i", x, y);
}
};
Vous pouvez ensuite l'utiliser simplement, comme une structure en C:
void main()
{
OBJET piece;
piece.x = 4;
piece.y = 10;
piece.affiche();
}
Une classe peut également être considérée comme un type de données: on peut définir ce qui se passe lorsqu'on accède à sa valeur (ici 'piece'), mais également définir ce qui se passe si on l'additionne avec quelque chose par exemple (piece = piece + 1). C'est de là que naît notre classe 'fixed', qui représente un nombre à virgule fixe. Cette classe contient simplement une valeur, notre nombre en virgule fixe, c'est-à-dire multiplié par 256 (j'ai choisi 256 comme ça, ça aurait pu être n'importe quoi d'autre. Ici: 256 = 1, 512 = 2, 128 = 0.5, ...):
class fixed {
int value;
};
On pourrait dores et déjà l'utiliser comme ceci (oubliez ça si vous n'y connaissez rien en fixed point):
void main() {
fixed i;
i.value = 3 * 256;
i.value *= 0.5 * 256;
printf("%f", (float)i.value / 256); // => 1.5
}
Mais ça n'est pas très beau. On remarque toutefois que systématiquement, on doit remplacer un "i = 3" naturel par un 'i.value = 3 * 256', beaucoup moins pratique. Et si on pouvait le faire directement? Grâce à la surcharge d'opérateurs du C++, c'est possible. On peut définir plus généralement que 'fixed = x' devient 'fixed.value = x * 256'.
C'est également possible pour toutes les autres opérations, comme l'addition, où on peut dire que 'fixed + x' est équivalent à 'fixed.value + x * 256'. Sans oublier les autres types, par exemple la valeur entière: '(int)fixed = fixed.value / 256'.
Pour le code:
fixed& operator = (fixed& f, int n) {
f.value = n * 256;
return f;
}
C'est déjà moins joyeux tout ça, ci-dessus on définit ce que l'assignement (opérateur '=') d'une valeur entière (int) à un type fixed va réaliser. N'oubliez pas que 'value' représente le membre correspondant dans la classe, c'est à dire le nombre en fixed point lui même.
Finalement, suite à un assignement, on doit toujours retourner la valeur de l'assignement lui même, c'est une spécificité du C/C++: (i = 2) * 3 => 6. En l'occurence, la valeur de retour c'est notre classe 'fixed' passée en paramètre, nommée 'f'.
Le '&' pour les paramètres est une spécificité du C++, il définit les paramètres passés en référence, comme par exemple:
int add2(int& x) {
x = x + 2;
}
void main() {
int x = 1;
add2(x);
printf("%i", x); // => 3
}
Sans le &, la valeur de 'x' n'aurait pas été conservée en dehors de la fonction 'add2', et le résultat aurait été 1.
Finalement, il va falloir écrire du code pour tous les opérateurs vers tous les types supportés (en l'occurence, ma classe gère les int, float et double, et tout ce qui peut être converti vers l'un de ces types, tel que 'short'). Si jamais vous avez besoin de plus d'informations, regardez mon code, car le bout que j'ai présenté ici n'est pas optimisé du tout, c'est juste pour expliquer ;)
Ensuite, si vous incluez mon fichier .h, vous pouvez modifier le #define VIRGULE au début pour y mettre le nombre de bits que vous voulez après la virgule. En général, 12 est une bonne valeur pour les positions de vos personnages, car elle offre une précision à 0.00024 près, tout en laissant 20 bits pour la partie entière (valeurs entre +/- 500'000), donc aucun problème pour les grandes maps :)
Et l'utilisation se fait comme montré en haut de ce post, exactement comme des floats quoi ^^
Question performances, c'est quand même légèrement plus lent que des fixed faits à la main (enfin ça dépend du compilo, parce que théoriquement ça devrait être pareil s'il fait bien son boulot), et j'ai pas de DS donc je peux pas tester.
Ce tuto s'applique également à la GBA et à la PSP :)
:busted_bl Dernière précision: ce n'est pas un tuto sur le C++, j'ai juste expliqué pour qu'on comprenne le principe, en plus c'est un très mauvais exemple. Si vous voulez vous mettre au C++, regardez les tutos correspondants :)
Téléchargement: http://oslib.palib.info/oth/fixed_tuto.zip
Benchmarks (temps écoulé, plus bas = mieux):
Int (C): 807
Int (C++): 809
Fixed: 933
Float: 1174
Double: 22000 (et augmente selon la taille du nombre)
On a donc un bon compromis entre la simplicité et la vitesse :) Notez que ces tests ont été effectués sur une PSP, qui a une FPU, c'est donc normal que les float soient si rapides. Sur GBA/DS, les float devraient avoir la même vitesse que les double (c'est-à-dire vraiment, mais alors vraiment lent).
Voilà, si vous avez des questions, n'hésitez pas ^^
Voici un tuto qui vous permettra de créer et manipuler des nombres à virgule fixe très simplement en C++, comme si c'étaient des float, mais avec une énorme amélioration de vitesse:
void main() {
fixed i = 0.5;
i += 0.75;
printf("%i, %f", (int)i, (float)i); // => 1, 1.25
}
Dans ce tuto, j'explique comment arriver à ce résultat, mais vous pouvez très bien télécharger le fichier .h tout prêt à la fin du post, il suffit de l'inclure et à vous les fixed! :hp:
Tout d'abord, on va devoir travailler en C++, car le C ne permet pas de faire ça (si vous voulez faire du C, regardez un autre tuto sur les fixed point ;)).
Bien heureusement, le C est compatible C++ (mais pas l'inverse) et en général un programme C compilera en C++ sans souci. Il suffit de renommer les fichiers .c en .cpp pour spécifier que c'est du C++.
Note: Les performances d'un même programme peuvent varier suivant qu'il est compilé en C ou en C++.
Pour créer ce nouveau type de données, nous allons profiter des fonctionnalités propres au C++ que sont les classes et la surcharge d'opérateurs. Tout d'abord, une classe, c'est simplement une évolution des structures en C: elle est designée pour contenir plusieurs paramètres, tel qu'un objet dans un jeu:
class OBJET {
int x, y; //Positions
IMAGE *img; //Sprite de l'objet
};
En plus de ça, les classes sont chargées d'effectuer des tâches, contrairement aux structures en C. On peut par exemple définir une fonction (qu'on appelle méthode lorsqu'elle est contenue dans une classe) dans la classe OBJET qui serait chargée de dessiner l'objet:
class OBJET {
int x, y; //Positions
IMAGE *img; //Sprite de l'objet
void affiche()
{
printf("je suis un objet aux positions %i, %i", x, y);
}
};
Vous pouvez ensuite l'utiliser simplement, comme une structure en C:
void main()
{
OBJET piece;
piece.x = 4;
piece.y = 10;
piece.affiche();
}
Une classe peut également être considérée comme un type de données: on peut définir ce qui se passe lorsqu'on accède à sa valeur (ici 'piece'), mais également définir ce qui se passe si on l'additionne avec quelque chose par exemple (piece = piece + 1). C'est de là que naît notre classe 'fixed', qui représente un nombre à virgule fixe. Cette classe contient simplement une valeur, notre nombre en virgule fixe, c'est-à-dire multiplié par 256 (j'ai choisi 256 comme ça, ça aurait pu être n'importe quoi d'autre. Ici: 256 = 1, 512 = 2, 128 = 0.5, ...):
class fixed {
int value;
};
On pourrait dores et déjà l'utiliser comme ceci (oubliez ça si vous n'y connaissez rien en fixed point):
void main() {
fixed i;
i.value = 3 * 256;
i.value *= 0.5 * 256;
printf("%f", (float)i.value / 256); // => 1.5
}
Mais ça n'est pas très beau. On remarque toutefois que systématiquement, on doit remplacer un "i = 3" naturel par un 'i.value = 3 * 256', beaucoup moins pratique. Et si on pouvait le faire directement? Grâce à la surcharge d'opérateurs du C++, c'est possible. On peut définir plus généralement que 'fixed = x' devient 'fixed.value = x * 256'.
C'est également possible pour toutes les autres opérations, comme l'addition, où on peut dire que 'fixed + x' est équivalent à 'fixed.value + x * 256'. Sans oublier les autres types, par exemple la valeur entière: '(int)fixed = fixed.value / 256'.
Pour le code:
fixed& operator = (fixed& f, int n) {
f.value = n * 256;
return f;
}
C'est déjà moins joyeux tout ça, ci-dessus on définit ce que l'assignement (opérateur '=') d'une valeur entière (int) à un type fixed va réaliser. N'oubliez pas que 'value' représente le membre correspondant dans la classe, c'est à dire le nombre en fixed point lui même.
Finalement, suite à un assignement, on doit toujours retourner la valeur de l'assignement lui même, c'est une spécificité du C/C++: (i = 2) * 3 => 6. En l'occurence, la valeur de retour c'est notre classe 'fixed' passée en paramètre, nommée 'f'.
Le '&' pour les paramètres est une spécificité du C++, il définit les paramètres passés en référence, comme par exemple:
int add2(int& x) {
x = x + 2;
}
void main() {
int x = 1;
add2(x);
printf("%i", x); // => 3
}
Sans le &, la valeur de 'x' n'aurait pas été conservée en dehors de la fonction 'add2', et le résultat aurait été 1.
Finalement, il va falloir écrire du code pour tous les opérateurs vers tous les types supportés (en l'occurence, ma classe gère les int, float et double, et tout ce qui peut être converti vers l'un de ces types, tel que 'short'). Si jamais vous avez besoin de plus d'informations, regardez mon code, car le bout que j'ai présenté ici n'est pas optimisé du tout, c'est juste pour expliquer ;)
Ensuite, si vous incluez mon fichier .h, vous pouvez modifier le #define VIRGULE au début pour y mettre le nombre de bits que vous voulez après la virgule. En général, 12 est une bonne valeur pour les positions de vos personnages, car elle offre une précision à 0.00024 près, tout en laissant 20 bits pour la partie entière (valeurs entre +/- 500'000), donc aucun problème pour les grandes maps :)
Et l'utilisation se fait comme montré en haut de ce post, exactement comme des floats quoi ^^
Question performances, c'est quand même légèrement plus lent que des fixed faits à la main (enfin ça dépend du compilo, parce que théoriquement ça devrait être pareil s'il fait bien son boulot), et j'ai pas de DS donc je peux pas tester.
Ce tuto s'applique également à la GBA et à la PSP :)
:busted_bl Dernière précision: ce n'est pas un tuto sur le C++, j'ai juste expliqué pour qu'on comprenne le principe, en plus c'est un très mauvais exemple. Si vous voulez vous mettre au C++, regardez les tutos correspondants :)
Téléchargement: http://oslib.palib.info/oth/fixed_tuto.zip
Benchmarks (temps écoulé, plus bas = mieux):
Int (C): 807
Int (C++): 809
Fixed: 933
Float: 1174
Double: 22000 (et augmente selon la taille du nombre)
On a donc un bon compromis entre la simplicité et la vitesse :) Notez que ces tests ont été effectués sur une PSP, qui a une FPU, c'est donc normal que les float soient si rapides. Sur GBA/DS, les float devraient avoir la même vitesse que les double (c'est-à-dire vraiment, mais alors vraiment lent).
Voilà, si vous avez des questions, n'hésitez pas ^^