OVH Cloud OVH Cloud

Range des entiers signés

7 réponses
Avatar
Sylvain Togni
Bonjour,

Les non-signés, la norme garanti [0, 2^n - 1]. Qu'en est-il des signés,
a-t-on [-2^(n - 1), 2^(n - 1) - 1] de garanti ?

--
Sylvain Togni

7 réponses

Avatar
kanze
Sylvain Togni wrote:

Les non-signés, la norme garanti [0, 2^n - 1].


Pas si tu insistes que n soit le nombre de bits. Il peut y avoir
des bits de padding, et autant que je sache, une implémentation
sur un hardware qui ne supporte pas de non-signer (les anciens
Unisys séries A ?) peut le simuler en masquant le bit de signe.

Qu'en est-il des signés,
a-t-on [-2^(n - 1), 2^(n - 1) - 1] de garanti ?


Non. On a même des implémentations actuelles où ce n'est pas le
cas. Comment en ferais-tu si le hardware est complément à 1 (le
cas des Unisys 2200 actuels). Ou magnitude signée (le cas des
anciens Unisys séries A).

Aussi, la norme permet à ce que certaines valeurs
« trappent ». La norme C est beaucoup plus précise à cet
égard, et dit explicitement que si la représentation est
complément à 2 ou magnitude signée, une représentation avec le
bit de signe à 1 et tous les autres bits à zéro peut trapper.

Dans la pratique, évidemment, au moins de rencontrer un Unisys
2200, ou de se rétrouver sur des architectures fortes anciennes
(mais alors, il n'y a probablement pas de compilateur C++), les
entiers signés sont complément à 2, et tu auras l'intervale
auquel tu t'attends.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Sylvain Togni

Les non-signés, la norme garanti [0, 2^n - 1].


Pas si tu insistes que n soit le nombre de bits. Il peut y avoir
des bits de padding, et autant que je sache, une implémentation
sur un hardware qui ne supporte pas de non-signer (les anciens
Unisys séries A ?) peut le simuler en masquant le bit de signe.


Donc 2^(sizeof(unsigned)*CHAR_BIT) - 1 peut être supérieur à
UINT_MAX. Mais quand la norme spécifie que unsigned int
fait au moins 16 bits, parle-t-elle de sizeof ou de UINT_MAX ?

Qu'en est-il des signés,
a-t-on [-2^(n - 1), 2^(n - 1) - 1] de garanti ?


Non. On a même des implémentations actuelles où ce n'est pas le
cas. Comment en ferais-tu si le hardware est complément à 1 (le
cas des Unisys 2200 actuels). Ou magnitude signée (le cas des
anciens Unisys séries A).

Aussi, la norme permet à ce que certaines valeurs
« trappent ». La norme C est beaucoup plus précise à cet
égard, et dit explicitement que si la représentation est
complément à 2 ou magnitude signée, une représentation avec le
bit de signe à 1 et tous les autres bits à zéro peut trapper.

Dans la pratique, évidemment, au moins de rencontrer un Unisys
2200, ou de se rétrouver sur des architectures fortes anciennes
(mais alors, il n'y a probablement pas de compilateur C++), les
entiers signés sont complément à 2, et tu auras l'intervale
auquel tu t'attends.


Hmm, je vais me limiter aux architectures courantes, alors.
C'est pour l'écriture d'un flux binaire :

void
BinaryStream::writeUInt8(unsigned x)
{
assert(x <= 0xFF);
myIsOk = myIsOk && myBuffer->sputc(x)
!= std::streambuf::traits_type::eof();
}

void
BinaryStream::writeUInt16(unsigned x)
{
assert(x <= 0xFFFFU);
writeUInt8((x ) & 0xFF);
writeUInt8((x >> 8) & 0xFF);
}

void
BinaryStream::writeInt8(int x)
{
assert(-0x80 <= x && x <= 0x7F);
writeUInt8(x < 0 ? 0xFF + (x + 1) : x);
}

void
BinaryStream::writeInt16(int x)
{
assert(-0x8000 <= x && x <= 0x7FFF);
writeUInt16(x < 0 ? 0xFFFFU + (x + 1) : x);
}

...

--
Sylvain Togni


Avatar
kanze
Sylvain Togni wrote:

Les non-signés, la norme garanti [0, 2^n - 1].


Pas si tu insistes que n soit le nombre de bits. Il peut y
avoir des bits de padding, et autant que je sache, une
implémentation sur un hardware qui ne supporte pas de
non-signer (les anciens Unisys séries A ?) peut le simuler
en masquant le bit de signe.


Donc 2^(sizeof(unsigned)*CHAR_BIT) - 1 peut être supérieur à
UINT_MAX.


Et l'a été, au moins sur une architecture.

Mais quand la norme spécifie que unsigned int fait
au moins 16 bits, parle-t-elle de sizeof ou de UINT_MAX ?


La norme ne dit pas que unsigned int fait au mois 16 bits. Ce
que la norme (C -- la norme C++ ici en fait référence) dit,
c'est que UINT_MAX doit être au moins 65535. (Évidemment, il dit
aussi qu'il faut que la représentation soit en base 2. Ce qui
fait avec moins de 16 bits, on n'y arrive pas.)

Qu'en est-il des signés,
a-t-on [-2^(n - 1), 2^(n - 1) - 1] de garanti ?


Non. On a même des implémentations actuelles où ce n'est pas
le cas. Comment en ferais-tu si le hardware est complément à
1 (le cas des Unisys 2200 actuels). Ou magnitude signée (le
cas des anciens Unisys séries A).

Aussi, la norme permet à ce que certaines valeurs « trappent
». La norme C est beaucoup plus précise à cet égard, et dit
explicitement que si la représentation est complément à 2 ou
magnitude signée, une représentation avec le bit de signe à
1 et tous les autres bits à zéro peut trapper.

Dans la pratique, évidemment, au moins de rencontrer un
Unisys 2200, ou de se rétrouver sur des architectures fortes
anciennes (mais alors, il n'y a probablement pas de
compilateur C++), les entiers signés sont complément à 2, et
tu auras l'intervale auquel tu t'attends.


Hmm, je vais me limiter aux architectures courantes, alors.


Ça rend la vie nettement plus facile, et la probabilité qu'on
a à supporter les autres est en général très faible, sinon nul.
Je fais comme toi ; pourquoi faire du travail pour rien ?

C'est un peu plus délicat en ce qui concerne les flottants,
parce qu'il y a encore une architecture assez répandue qui n'est
pas IEEE (et aussi dans le cas d'une architecture IEEE, il peut
y avoir des subtilités dans la conversion).

C'est pour l'écriture d'un flux binaire :

void
BinaryStream::writeUInt8(unsigned x)
{
assert(x <= 0xFF);
myIsOk = myIsOk && myBuffer->sputc(x)
!= std::streambuf::traits_type::eof();
}

void
BinaryStream::writeUInt16(unsigned x)
{
assert(x <= 0xFFFFU);
writeUInt8((x ) & 0xFF);
writeUInt8((x >> 8) & 0xFF);
}

void
BinaryStream::writeInt8(int x)
{
assert(-0x80 <= x && x <= 0x7F);
writeUInt8(x < 0 ? 0xFF + (x + 1) : x);
}

void
BinaryStream::writeInt16(int x)
{
assert(-0x8000 <= x && x <= 0x7FFF);
writeUInt16(x < 0 ? 0xFFFFU + (x + 1) : x);
}


C'est à peu près ce que je fais aussi.

Note bien que ce que tu as écrit là est prèsque 100% portable ;
la seule chose qui n'est pas garantie par la norme, c'est la
comparaison d'un int avec -0x8000. (Sur une machine 16 bit,
0x8000 a le type unsigned. Ce qui fait que ton x sera converti
en unsigned avant la comparaison.)

Sinon, les règles veulent bien que les décalages, etc., ignore
les bits de rembourage (s'il y en a), et que la conversion de
signed en unsigned donne les mêmes bits qu'aurait eu le signed
en représentation complément à deux. (C-à-d que sur une
architecture complément à deux, la conversion signed -> unsigned
serait en fait un no-op, au niveau des bits, mais que sur une
autre architecture, les bits ne serait pas les mêmes après la
conversion.)

C'est à la lecture où le problème se pose (et où on fait des
économies en supposant complément à deux, sans vérification).
Parce que la conversion unsigned -> signed est définie par
l'implémentation (et il n'y a aucun exigeance que si tu fais
signed -> unsigned -> signed, tu te rétrouves avec la valeur du
départ). Donc, une implémentation du genre :

int
BinaryStream::readInt16()
{
return readUInt16() ;
}

ne marche que sur une machine complément à 2 avec des int
d'exactement 16 bits. Sur des architectures courantes, si tu
utilises les int16_t et uint16_t de C99, c'est garantie.
D'ailleurs, en C99, si tu utilises ces types, et l'architecture
n'est pas complément à 2, ou n'a pas de type entier avec
exactement 16 bits, tu auras une erreur de compilation. C'est
donc la solution optimale ; tu ne fais pas de travail
supplémentaire, et si tu tombes sur une architecture où il
l'aurait fallu, tu as une erreur de compilation.

Le seul hic, c'est que les implémentations de C99 sont
rarissime.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
Olivier Miakinen

[...]
assert(-0x8000 <= x && x <= 0x7FFF);


Note bien que ce que tu as écrit là est prèsque 100% portable ;
la seule chose qui n'est pas garantie par la norme, c'est la
comparaison d'un int avec -0x8000. (Sur une machine 16 bit,
0x8000 a le type unsigned. Ce qui fait que ton x sera converti
en unsigned avant la comparaison.)


J'ai eu un problème similaire avec -2147483648 sur une machine 32 bits.
Si je me rappelle bien, j'ai dû le remplacer par (-2147483647 - 1) pour
éviter un warning à la compilation.


Avatar
Sylvain Togni

assert(-0x8000 <= x && x <= 0x7FFF);
Note bien que ce que tu as écrit là est prèsque 100% portable ;

la seule chose qui n'est pas garantie par la norme, c'est la
comparaison d'un int avec -0x8000. (Sur une machine 16 bit,
0x8000 a le type unsigned. Ce qui fait que ton x sera converti
en unsigned avant la comparaison.)


J'ai eu un problème similaire avec -2147483648 sur une machine 32 bits.
Si je me rappelle bien, j'ai dû le remplacer par (-2147483647 - 1) pour
éviter un warning à la compilation.


C'est bizarre, je pensais que -0x8000 et -2147483648 étaient des
constantes entières signées (négatives), alors que si je comprend
bien c'est des constantes entières non signées positives (0x8000
et 2147483648) auxquelles on applique l'opérateur - ?

--
Sylvain Togni



Avatar
Olivier Miakinen

C'est bizarre, je pensais que -0x8000 et -2147483648 étaient des
constantes entières signées (négatives),


C'est ce que je croyais avant d'obtenir ce warning de compilation.

alors que si je comprend
bien c'est des constantes entières non signées positives (0x8000
et 2147483648) auxquelles on applique l'opérateur - ?


En effet.

Avatar
James Kanze
Sylvain Togni wrote:

assert(-0x8000 <= x && x <= 0x7FFF);





Note bien que ce que tu as écrit là est prèsque 100% portable ; la
seule chose qui n'est pas garantie par la norme, c'est la
comparaison d'un int avec -0x8000. (Sur une machine 16 bit, 0x8000 a
le type unsigned. Ce qui fait que ton x sera converti en unsigned
avant la comparaison.)




J'ai eu un problème similaire avec -2147483648 sur une machine 32
bits. Si je me rappelle bien, j'ai dû le remplacer par (-2147483647
- 1) pour éviter un warning à la compilation.



C'est bizarre, je pensais que -0x8000 et -2147483648 étaient des
constantes entières signées (négatives), alors que si je comprend bien
c'est des constantes entières non signées positives (0x8000 et
2147483648) auxquelles on applique l'opérateur - ?


Il n'y a pas de littéraux négatifs en C++. D'une côté, c'est
effectivement un peu étonnant dans ce cas ci. De l'autre...
est-ce que ça te plairait que « -123 » et « - 123 » ait une
signification différente ?

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34