OVH Cloud OVH Cloud

[Calcul] Problème étrange

69 réponses
Avatar
Rincevent
Bonjour à tous,
g un gros problème dans mon programme C++.
Voilà g 3 entier a,b et c
Je souhaite calculer la partie entière de (a/b)*c
J'utilise pour cela la fonction floor() de <cmath>
Or deux problèmes se présentent :

1) Le compilateur renvoie 0 lorsque je lui demande de calculer (a/b)*c (et
ce, alors que les valeurs de a,b,c ne devraient pas donner un tel résultat)

2) Le compilateur m'insulte lorsque j'essaie d'appliquer la fonction floor()
à (a/b)*c (motif : l'argument n'est pas un double...)


- Je suppose qu'il doit exister une fonction pour convertir des rapports
d'entier en double, non ?
- Et inversement pour passer d'un double codant un entier --> en type int ?
- floor() attend un double comme argument... Soit mais quel est le type duy
résultat ? un double ? un int ?
- comment résoudre cet épineux problème de façon élégante ??? ;-)

Quelqu'un peut-t-il m'aider ?
Merci d'avance !

Rincevent

10 réponses

1 2 3 4 5
Avatar
kanze
Pierre Maurette wrote in message
news:...

[...]
N est une constante, apparemment, ainsi que P pendant le
calcul. Votre problème esr ainsi simplifié :

int resultat = (k*N)/P; //c'est tout
ou même
int resultat = (k<<8)/P;


Jamais. Il veut multiplier, non décaler.


Surtout que je déclare resultat en int (et non unsigned int). Laisser
faire le compilateur, en déclarant toutefois N const si c'est le cas.

Question subsidiaire: (je me situe pour l'exemple dans une
architecture où la multiplication serait très pénalisante par rapport
au décalage). Si N est constant dans une boucle, faut-il faire
confiance au compilateur ou est-il préférable de l'orienter par un
cast ou autre méthode ? Si oui, laquelle (en C et en C++) ?


Je ne sais pas exactement ce que tu veux dire par « constant dans une
boucle ». S'il ne s'agit pas d'une constante au moment de la
compilation, il n'y a pas grand chose que ni toi ni le compilateur peut
faire. Et si N est une constante au moment de la compilation, c'est
égale où il est déclaré -- pour le compilateur, une constante est une
constante.

Indépendamment de tout ça : on écrit logiquement ce qu'on veut : on
n'utilise pas les decalages pour multiplier, et on ne se sert pas non
plus des multiplications ou des divisions pour décaler (quand, par
exemple, je sors un mot 8 bits à la fois). Ensuite, si vraiment il y a
un problème de vitesse, et le profiler dit bien que c'est là où ça se
coince, évidemment, on fait ce qu'il faut. Mais avant, rien. Ou au
moins, pas à ce niveau. Si j'ai à trier plusieurs millions d'objets, et
pour je ne sais pas quelle raison, je ne peux pas me servir de
std::sort, je n'attends pas le profiler pour me dire qu'un tri par
insertion n'est pas suffisamment rapide. Mais pour tout ce qui n'est pas
algorithmique, jamais d'optimisations sans que le profiler ne le dise.

--
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
kanze
Samuel Krempp wrote in message
news:<40b2f354$0$12747$...

[...]
j'imaginais mal le cas de l'appel de floor pour un int, qui n'est pas
très naturel 8-/


Ralentir le programme ? Sinon, tu le surcharge sur int pour faire
quelque chose de tout à fait différente -- l'obfuscation:-).

d'ailleurs comme dit dans un autre msg, la meileure solution ici est
de rester en int tout du long, mais en faisant la division euclidienne
comme il faut :
int n = (a*b) / c;


Tout à fait. Maintenant qu'il nous a donné une idée sur l'ordre de
grandeurs avec lequel il travaille, et qu'on est assez sûr qu'il n'y
aurait pas de débordement.

En passant, ces surcharges cassent du code existant pré-norme,
puisqu'avant la norme, quelque chose comme exp(3) était
légal. L'intérêt du surcharge est évident (à mon avis, en tout cas),
mais ils auraient peut-être dû ajouter des surcharges sur int, etc.,
pour ce genre de cas.


enfin, pour floor cette surcharge n'est pas un problème, vu que c'est
assez inutile de passer un nbre entier à la fonction. Sinon la norme
n'a pas du juger utile d'ajouter une surcharge sur int qui
n'apporterait rien d'autre que la compatibilité avec le code
pré-norme.


En ce qui concerne floor et ceil, je suis d'accord que ça n'apporte
rien. Mais quelque chose comme :
double ln2 = log( 2 ) ;
n'est pas tout à fait sans intérêt. Moi, même là, j'écrirais 2.0, mais
j'en connais pour qui ce n'est pas le cas. C'est du code qui a marché,
et qui ne marche plus.

Enfin, ce n'est pas la fin du monde ; le changement n'est pas
silencieux, et la correction est trivialle.

--
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
kanze
Pierre Maurette wrote in message
news:...
drkm typa:

Pierre Maurette writes:
[...]

Si vous faites :
int resultat = (a*c)/b;
vous traitez exactement le problème tel qu'il est posé, c'est à
dire comme un problème d'entiers.


Et qu'advient-il lorsque

a == std::numeric_limits< int >::max() ;


Oui. J'avais fait des hypothèses sur le problème qui n'étaient pas
mentionnées dans le message original (mais que le second message de
Rincevent rendent valables). En fait, le message original est un peu
étrange, je ne vois ce que vient y faire le floor() puisque a, b et c
me semblent devoir être tous positifs.


La fonction floor() intervient dès qu'on peut avoir une valeur
fractionnelle. On peut avoir une valeur fractionnelle dès qu'il y a des
divisions virgule flottant.

? En fait, je suis en train de me demander s'il n'y a pas conversion
du résultat de « a * c » en un long int dans ce cas, mais je ne pense
pas ; et ce ne ferait que déplacer le problème. Tandis que

int result = static_cast< int >(
static_cast< double >( a ) / b * c
) ;
gère correctement le problème.


Je trouve dommage de passer par des double si le contexte rend cette
précaution inutile, et je mettrais ma main à couper que c'est ici le
cas.


C'est effectivement le cas, maintenant qu'il a éclairé le contexte. Mais
on ne savait pas avant.

Ça dépend de l'étendue des valeurs admissibles pour a, b et c, mais
également de la fréquence du calcul.


La fréquence du calcul ? Si les valeurs intermédiaire exigent un double,
il faut y passer, même si les calculs sont très fréquents. Si on peut
s'en passer, il vaut mieux, même si le calcul ne se fait qu'une seule
fois, pour la raison que tu as cité ci-dessous.

Il est clair que si le calcul est fait une seule fois (avant une
boucle par exemple), il est inutile de trop se casser la tête. Surtout
si l'on ne sait rien sur a, b et c, auquel cas il faudrait faire
quelques tests pour choisir entre (a/b)*c, (c/b)*a et (a*c)/b, pas
nécessairement plus lent que la passage par double, mais un peu plus
prise de tête, moins lisible, etc...


Ça dépend de la machine. Sur ma Sparc, des multiplications et des
divisions double sont nettement plus rapides que des multiplications et
des divisions entières. Je crois que c'est même souvent le cas.

Attention également, le passage par double donne un résultat qui peut
être différent (de celui exact donné par des int) si a*c est divisible
par b.


Et voilà la vraie raison pourquoi il vaut mieux éviter le double si
possible.

D'une façon plus générale, il me semble voir souvent des erreurs dues
à une utilisation abusive d'opérations "réelles" sur une problématique
d'entiers. Ce qui n'en disqualifie pas non plus l'usage modéré.


Il faut absolument éviter les doubles si on pense qu'ils représentent
des réelles:-). Le calcul flottant est plein de pièges.

Pour ce qui est de la promotion en long int, je n'ai trouvé que
4.5/1. Je ne suis pas certain qu'il s'agit du bon paragraphe, mais
si c'est le cas, cette promotion en long int n'existe pas (ce que je
pense).


Il me semble également qu'il ne doit pas y avoir de promotion int ->
long int.


Il n'y a pas de promotion, mais il peut y avoir, quand le contexte le
veut, une converion implicite. Ici, il n'y a rien dans le contexte qui
le veut.

Mais je travaille (c'est une erreur) avec en tête des int et des long
int équivalents.
Si ce n'est pas le cas: int result = static_cast<int>(static_cast<long
int>(a)/b*c);


La conversion explicite avant l'affectation n'est pas nécessaire.

--
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
Alain Naigeon
a écrit dans le message news:

Pierre Maurette wrote in message
news:...
"Rincevent" typa:

5) Pour la particules appartenant à la k-ème classe, sa couleur est
donc (canoniquement) donnée par (k/P)*N (k/P)*N n'est pas forcément
un entier, on prends donc la partie entière pour être sur...


Tous vos entiers sont positifs, déclarez-les donc unsigned.


Non. Gabi l'explique mieux que moi, mais en gros, l'abstraction
implémentée par les unsigned n'est pas un ensemble des nombres naturels.


Pour les classes on conseille de s'inspirer de "l'univers du problème" ;
serait-ce une tare de faire de même pour d'autres types, encore
plus basiques ? Quand une variable représente un entier qui n'a
aucun sens autre que positif, cela me révulse absolument qu'il
ne soit pas unsigned. Je veux bien regarder la machine, toutes
considérations conceptuelles égales par ailleurs. Mais jamais je
ne pourrait m'habituer à violer un concept pour des raisons
bassement techniques. Si j'avais cette tendance, je ferais encore
du C, sans ++.

--

Français *==> "Musique renaissance" <==* English
midi - facsimiles - ligatures - mensuration
http://anaigeon.free.fr | http://www.medieval.org/emfaq/anaigeon/
Alain Naigeon - - Strasbourg, France



Avatar
Alain Naigeon
Hélas,

"Alain Naigeon" a écrit

Mais jamais je ne pourrait


Alain Naigeon, au coin.

--

Français *==> "Musique renaissance" <==* English
midi - facsimiles - ligatures - mensuration
http://anaigeon.free.fr | http://www.medieval.org/emfaq/anaigeon/
Alain Naigeon - - Strasbourg, France

Avatar
Samuel Krempp
le Tuesday 25 May 2004 23:38, écrivit :
Pour les classes on conseille de s'inspirer de "l'univers du problème" ;
serait-ce une tare de faire de même pour d'autres types, encore
plus basiques ? Quand une variable représente un entier qui n'a
aucun sens autre que positif, cela me révulse absolument qu'il
ne soit pas unsigned.


en cherchant un exple, il me vient :
for(unsigned int n = 25; n>= 0; --n) ;
c'est pour moi une illustration assez simple du fait que les unsigned int
sont mal adaptés pour modéliser un sous ensemble des nombres entiers
positifs.

Dans le monde des unsigned int, "être positif" n'a pas de sens.
Et c'est vraiment dans la quintessence du type : le concept auquel
correspond le mieux les types entiers unsigned n'est pas pas celui des
nombres entiers positifs,
mais celui de Z/nZ (i.e. arithmétique modulo n -avec n puissance de 2
précisémént) dont le caractère cyclique s'oppose profondément à toute
relation d'ordre compatible avec l'addition.


Ils ont beau s'appeler unsigned, ça ne veut pas forcément dire qu'ils sont
là pour correspondre au concept de nombres positifs. (d'ailleurs, au pied
de la lettre, c'est finalement moins traître qu'on ne le pense : 'sans
signe', c'est bien en accord avec le fait qu'ils n'ont pas de concept de
signe).


Techniquement, le fait qu'un unsigned int peut prendre des valeurs qui ne
sont pas représentables par une valeurs positive d'un int fait d'ailleurs
vite déchanter celui qui pensait utiliser un unsigned dès qu'il a une
valeur positive :
Alors qu'à la base, on considère notre variable comme appartenant à un
sous-ensemble de nbres entiers, dès qu'on la mélange à des entiers on doit
soit promouvoir les entiers en unsigned (à priori contraire au sens
arithmétique voulu), ou retransformer ces nbres en entiers (au prix de
vérifications bien ennuyeuses).

Alors que si on utilise des nbres entiers partout, tout se passe mieux (et
si on y réfléchit, c'est bien normal).

bref, pour mettre en code un concept de nbre entier positif, le mieux est
d'associer un type entier à la contrainte "être positif".
e.g. utiliser un int en association avec des (pré/post/)conditions de
positivité (non-negativeness dirait un anglophone, puisque "positive" en
anglais signifie "positif strict")

résumé de mon opinion : ne pas confondre "unsigned int" et "non-negative
int".

(Si je voulais vraiment un type "non-negative int", j'imaginerais une classe
encapsulant int, et qui teste à chaque occasion la positivité du nbre..
Mais en fait je pense que c'est surtout le message de l'auteur qui compte,
et celà peut aussi bien passer en dehors du nom de type : dans la
description de la fonction / variable)

--
Sam

Avatar
Loïc Joly
Samuel Krempp wrote:

Dans le monde des unsigned int, "être positif" n'a pas de sens.
Et c'est vraiment dans la quintessence du type : le concept auquel
correspond le mieux les types entiers unsigned n'est pas pas celui des
nombres entiers positifs,
mais celui de Z/nZ (i.e. arithmétique modulo n -avec n puissance de 2
précisémént) dont le caractère cyclique s'oppose profondément à toute
relation d'ordre compatible avec l'addition.


En quoi int est-il préférable ? On a aussi les phénomènes de cyclicité,
mais en plus, ils ne sont pas spécifiés par la norme.

--
Loïc

Avatar
kanze
"Michel Michaud" wrote in message
news:<0_Jsc.27867$...
Dans news:,
Pierre Maurette wrote in message
news:...
Mais bon, personnellement, je trouve regrettable l'utilisation de /
pour la division entière, j'aime bien le couple div mod du Pascal.


Tout à fait d'accord. Mais C/C++ ne sont pas seuls dans leur choix.


Tiens vous êtes bizarres : au contraire, j'ai toujours trouvé que les
langages où les opérations classiques (+-*/) sont dans le type des
données SAUF la division étaient plutôt bizarres. Il me semble que,
la plupart du temps, si on a des entiers pour les calculs, on veut un
entier comme résultat et la division réelle ne fait qu'apporter des
conversions inutiles.


Le problème, c'est ce qu'on entend par « division ». Si tu démandes à un
non-informaticien quelle est la valeur de 1/3, il va te dire un
tiers. Et non 0. Dans la mésure où l'opérateur ne donne pas 1/3, ou
quelque chose de proche, pour 1/3, je ne trouve pas que l'opérateur /
est bien choisi.

Ça ne veut pas dire que je suis contre la division entière, comme elle
est définie. Seulement le choix de l'opérateur. Si je concevais un
langage, sans tenir compte de l'historique, je ne définirais pas
l'opérateur / pour les entiers. Et je ne supporterais pas non plus des
conversions implicites : une expression du genre i / j, où i et j sont
des entiers, serait simplement illégal.

Demandez à quelqu'un qui fait du VB s'il fait attention d'utiliser la
division entière quand il en a besoin (au moins en Pascal, il me
semble qu'il fallait convertir explicitement le résultat de la
division pour le ravoir en entier).


En Pascal, effectivement, l'opérateur / était défini comme une
application de (int,int)->float. Et il n'y avait pas de conversion
implicite float vers int.

Je comprends que le programmeur peut faire des erreurs s'il ne fait
pas attention à l'ordre des opérations en calcul entier, mais il faut
apprendre car il y a aussi des problèmes avec l'ordre des opérations
dans les calculs réels, même si la plupart des programmeurs ne
semblent pas s'en préoccuper.


Tout à fait. Je trouve que l'utilisation d'un opérateur autre que / aide
à se rappeler que l'opération n'est pas celui auquel on pourrait
s'attendre.

--
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
kanze
"Michel Michaud" wrote in message
news:<EJJsc.27786$...
Dans news:,
Non. Gabi l'explique mieux que moi, mais en gros, l'abstraction
implémentée par les unsigned n'est pas un ensemble des nombres
naturels. Le type naturel pour les calculs entiers en C/C++, c'est
int. On n'utilise d'autres types que dans les cas exceptionnels. On
n'utilise donc unsigned que dans les cas où on a besoin d'un de ces
caractèristiques particuiliers :

- Des opérations bit-à-bit ne donne pas de résultats « bizarre »,
même si on manipule le bit de poids fort. (C'est surtout important
pour les décalages à droits.)

- On a besoin d'un arithmétique modulo, par exemple dans le
calcul d'un code d'hachage.

- On a besoin de ce bit supplémentaire. Pour représenter ces
niveaux de gris, par exemple, je verrais bien un unsigned
char, parce que signé, il lui faudrait un short.


C'est à peu près ce que je pense aussi, mais les nombreuses
discussions (sur clc++m par exemple) montrent bien que ce n'est pas
complètement noir ou blanc. En particulier, le fait que la
bibliothèque standard regorge de unsigned (pour aucune des raisons
citées plus haut) indique qu'au moins quelques grands experts ont,
pendant un certain temps, pensé autrement.


Ou que la bibliothèque n'est pas le produit des grands experts:-).

Sérieusement, au moins dans la partie STL (et c'est bien là où on trouve
l'introduction des size_t au bout de bras), c'est bien la troisième
motivation ci-dessus qui a joué, au moins en partie. N'oublie pas que
Stepanov a beaucoup développé sur un PC, à un époque où les PC étaient
des machines à 16 bits (mais que les pointeurs avec 32 bits). Vouloir un
vector<char> avec 40000 caractères (quand on peut avoir jusqu'à 640 Ko
de mémoire totale) n'est pas si étrange que ça.

C'est possible que cette motivation joue toujours. Personnellement, je
ne peux pas imaginer vouloir un vector<char> avec plus de 2 milliards
d'éléments, mais je ne connais pas toutes les applications
possibles. (Il y a quand même une différence. J'aurais au maximum un
vector<char> de cette taille, puisqu'il occupera plus de la moitié de la
mémoire. Et je ne pourrais pas le créer au coup de push_back. Tandis que
40 mille char dans un mémoire de 640 Ko, il y en a de la place pour
plusieurs, et il se crée facilement avec des push_back -- j'aurais bien
la place d'avoir les deux copies nécessaires parfois.)

Ceci dit, je crois que c'est un « erreur » qui est du même
niveau que les conversions implicites à perte de valeur :


Pas du tout. Les conversions implicites à perte de valeur est une erreur
de conception pûre et simple. Stroustrup n'avait pas le choix, à cause
de la compatibilité C. En fait, je crois que l'« erreur » vient du fait
que C dérivait au départ de B, qui lui n'avait pas de types du tout (et
donc, pas de conversions). Mais le fait qu'il y a une excuse ne veut pas
dire que ce n'est pas une erreur.

on
sent bien que ces « experts » mettent les compilateurs en mode
« pas d'avertissement pour ces petits détails » alors qu'au
contraire plusieurs personnes, dont moi, aiment que les
programmes passent sans avertissement, même au niveau le plus
élevé...


Moi aussi, je préfère ne pas avoir des avertissements. Mais parfois, il
y a des compilateurs qui en émettent des vraiments bêtes.

--
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
kanze
"Alain Naigeon" wrote in message
news:<40b3bfa9$0$12753$...
a écrit dans le message news:

Pierre Maurette wrote in message
news:...
"Rincevent" typa:

5) Pour la particules appartenant à la k-ème classe, sa couleur
est donc (canoniquement) donnée par (k/P)*N (k/P)*N n'est pas
forcément un entier, on prends donc la partie entière pour être
sur...


Tous vos entiers sont positifs, déclarez-les donc unsigned.


Non. Gabi l'explique mieux que moi, mais en gros, l'abstraction
implémentée par les unsigned n'est pas un ensemble des nombres
naturels.


Pour les classes on conseille de s'inspirer de "l'univers du problème"
; serait-ce une tare de faire de même pour d'autres types, encore plus
basiques ? Quand une variable représente un entier qui n'a aucun sens
autre que positif, cela me révulse absolument qu'il ne soit pas
unsigned.


Tu veux dire que ça te révulse qu'il puisse représenter les valeurs en
dehors du domaine. Moi aussi. Par exemple, si je veux un type qui
représente un dé, je ne veux pas non plus qu'il puisse représenter un 0,
ou un 100.

Malheureusement, C/C++ n'a pas des types à sous-étendu (subrange
types). On peut les implémenter au moyens des classes, mais le résultat
est assez lourd. Alors, en général, on s'en passe. (Peut-être à tort, je
ne sais pas. Un template Subrange< typename T, T, T > ne doit pas poser
de grands problèmes.)

Je veux bien regarder la machine, toutes considérations conceptuelles
égales par ailleurs. Mais jamais je ne pourrait m'habituer à violer un
concept pour des raisons bassement techniques.


Je comprends pas trop le rapport avec unsigned. En C++, c'est un type
spécifique pour modèler des nombres modulo N ou, éventuellement, un
ensemble de bits. Son nom ne lui convient pas, je suis d'accord, mais
c'est comme ça.

S'il y avait un type Cardinal en C++, je m'en servirais volentiers. Mais
il n'y en a pas. S'il y avait des types à sous-étendues, je m'en
servirais encore plus volentiers, mais il n'y en a pas non plus. Dans
l'ensemble, pour les valeurs numérique, on a int et double, choisir un
autre type (au moins d'implémenter un template Subrange) pour une valeur
numérique, c'est effectivement regarder la machine pour préter attention
à des aspects « bassement techniques ».

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




1 2 3 4 5