Comment traiter un struct avec deux courts métrages non signés comme si elle était un entier non signé? (En C)
-
26-09-2019 - |
Question
I a créé une structure pour représenter un nombre positif à virgule fixe. Je veux les nombres dans les deux côtés du point décimal se composent de 2 octets.
typedef struct Fixed_t {
unsigned short floor; //left side of the decimal point
unsigned short fraction; //right side of the decimal point
} Fixed;
Maintenant, je veux ajouter deux nombres à virgule fixe, Fixed x
et Fixed y
. Pour ce faire, je les traite comme des entiers et ajouter.
(Fixed) ( (int)x + (int)y );
Mais comme mon visual studio 2010 compilateur dit, je ne peux pas convertir entre Fixed
et int
.
Quelle est la bonne façon de le faire?
EDIT: Je ne suis pas engagé à la mise en œuvre de {short floor, short fraction}
du fixe.
La solution
magie:
typedef union Fixed {
uint16_t w[2];
uint32_t d;
} Fixed;
#define Floor w[((Fixed){1}).d==1]
#define Fraction w[((Fixed){1}).d!=1]
Points clés:
- J'utiliser des types entiers de taille fixe de sorte que vous n'êtes pas en fonction de
short
étant de 16 bits etint
étant 32 bits. - Les macros pour
Floor
etFraction
(majuscule pour éviter de cliquetis avec fonctionfloor()
) l'accès des deux parties d'une manière indépendante de endian, commefoo.Floor
etfoo.Fraction
.
Modifier la demande à l'OP, une explication des macros:
unions sont un moyen de déclarer un objet constitué de plusieurs types différents se chevauchent. Ici, nous avons uint16_t w[2];
uint32_t d;
qui se chevauchent, ce qui permet d'accéder à la valeur 2 unités 16 bits ou 1 unité 32 bits.
(Fixed){1}
est littéral composé , et pourrait être écrit plus verbeux que (Fixed){{1,0}}
. Son premier élément (uint16_t w[2];
) s'initialisé avec {1,0}
. L'expression ((Fixed){1}).d
évalue ensuite l'entier de 32 bits dont la première moitié de 16 bits est de 1 et dont la seconde moitié 16-bit est égal à 0. Sur un petit-boutiste système, cette valeur est 1, alors ((Fixed){1}).d==1
évalue à 1 (vrai) et ((Fixed){1}).d!=1
est évaluée à 0 (faux). Sur un système big-endian, ce sera l'inverse.
Ainsi, sur un système de petit-boutiste, Floor
est w[1]
et Fraction
est w[0]
. Sur un système big-endian, Floor
est w[0]
et Fraction
est w[1]
. De toute façon, vous finissez par le stockage / l'accès à la moitié correcte de la valeur 32 bits pour la endian-ness de votre plate-forme.
En théorie, un système hypothétique pourrait utiliser une représentation complètement différente pour 16 bits et les valeurs 32 bits (par exemple entrelaçant les bits des deux moitiés), brisant ces macros. Dans la pratique, cela ne va pas se produire. : -)
Autres conseils
Vous pourriez essayer un hack méchant, mais il y a un problème ici avec endian-ness. Quoi que vous fassiez pour convertir, comment est le compilateur censé savoir que vous voulez floor
être la plus grande partie du résultat, et fraction
la partie moins importante? Toute solution qui repose sur la mémoire de réinterprétant va travailler pour une endian-ness, mais pas un autre.
Vous devez soit:
(1) définissent la conversion explicitement. En supposant que short
est de 16 bits:
unsigned int val = (x.floor << 16) + x.fraction;
(2) le changement Fixed
de sorte qu'il a un membre de int
au lieu de deux courts métrages, puis decompose en cas de besoin, plutôt que composition lorsque cela est nécessaire.
Si vous voulez plus d'être rapide, puis (2) est la chose à faire. Si vous avez un type 64 bits, vous pouvez aussi faire la multiplication sans se décomposer. unsigned int result = (((uint64_t)x) * y) >> 16
Le hack méchant, en passant, serait ceci:
unsigned int val;
assert(sizeof(Fixed) == sizeof(unsigned int)) // could be a static test
assert(2 * sizeof(unsigned short) == sizeof(unsigned int)) // could be a static test
memcpy(&val, &x, sizeof(unsigned int));
qui fonctionnerait sur un système big-endian, où fixe n'a pas de remplissage (et les types entiers ont pas de bits de remplissage). Sur un système little-endian vous auriez besoin les membres de Fixé dans l'autre ordre, ce qui est pourquoi il est méchant. Parfois coulée par memcpy est la bonne chose à faire (dans ce cas, il est un « truc » plutôt que d'un « hack méchant »). Ce n'est pas une de ces fois.
Si vous devez vous pouvez utiliser une union, mais méfiez-vous des questions endian. Vous trouverez peut-être l'arithmétique ne fonctionne pas et certainement pas est portable.
typedef struct Fixed_t {
union {
struct { unsigned short floor; unsigned short fraction };
unsigned int whole;
};
} Fixed;
qui est plus probable (je pense) au travail big-endian (lequel Windows / Intel est pas).
Ce n'est pas possible portably, comme le compilateur ne garantit pas un Fixed
utilisera la même quantité d'espace comme int
. La bonne façon de définir une Fixed add(Fixed a, Fixed b)
fonction.
Il suffit d'ajouter les morceaux séparément. Vous devez connaître la valeur de la fraction qui signifie « 1 » - ici, je vous appelle que FRAC_MAX
:
// c = a + b
void fixed_add( Fixed* a, Fixed* b, Fixed* c){
unsigned short carry = 0;
if((int)(a->floor) + (int)(b->floor) > FRAC_MAX){
carry = 1;
c->fraction = a->floor + b->floor - FRAC_MAX;
}
c->floor = a->floor + b->floor + carry;
}
Par ailleurs, si vous configurez simplement le point fixe comme étant à 2 octets limite que vous pouvez faire quelque chose comme:
void fixed_add( Fixed* a, Fixed *b, Fixed *c){
int ia = a->floor << 16 + a->fraction;
int ib = b->floor << 16 + b->fraction;
int ic = ia + ib;
c->floor = ic >> 16;
c->fraction = ic - c->floor;
}
Essayez ceci:
typedef union {
struct Fixed_t {
unsigned short floor; //left side of the decimal point
unsigned short fraction; //right side of the decimal point
} Fixed;
int Fixed_int;
}
Si votre compilateur puts les deux courts de sur 4 octets, vous pouvez utiliser memcpy pour copier votre int dans votre struct, mais comme dit dans une autre réponse, ce n'est pas portable ... et assez laid.
Avez-vous vraiment ajouter séparément chaque champ dans une méthode distincte? Est-ce que vous voulez garder l'entier pour des raisons de performance?
// add two Fixed
Fixed operator+( Fixed a, Fixed b )
{
...
}
//add Fixed and int
Fixed operator+( Fixed a, int b )
{
...
}
Vous pouvez lancer tout type adressable à un autre en utilisant:
*(newtype *)&var