11544 sujets

JavaScript, DOM et API Web HTML5

Pages :
Modérateur
Bonjour à tous,

Depuis plusieurs minutes déjà, je recherche une fonction Javascript permettant d'arrondir un chiffre en conservant 2 décimals. Le nombre qui m'intéresse est 9.075. Mon objectif est de l'arrondir à 9.08.

C'est alors que l'idée m'est venu de rechercher sur Google. Une vraie partie de plaisir parce que je me disais que ce serait facile à trouver. Il n'y a rien de plus simple, en théorie, que de trouver une fonction pour arrondir. C'est alors que je suis tombé sur plusieurs fonctions développées par des inconnus. J'en ait essayé quelques-unes, mais sans succès. J'ai finalement trouvé toFixed.

Je l'ai essayé, et ca ne fonctionnait pas. Je pensais devenir fou. Smiley fulmine

Cette fonction semblait bel et bien destinée à arrondir. J'ai créé une page test.

Essayez les nombres suivants avec Firefox :

9.005
9.015
9.025
9.035
9.045
9.055
9.065
9.075
9.085
9.095

Vous allez constater certaines irrégularités. Les nombres s'arrondissent bien en général, sauf 9.075, entre autres.

Dans Internet Explorer, tout fonctionne à merveille.

Connaissez-vous une fonction d'arrondissement qui fonctionne bien sous Firefox ? Je ne peux pas me permettre d'utiliser une fonction retournant des résultats incorrects.

Merci et j'attend vos messages avec impatience Smiley bequilles
Modifié par Tony Monast (18 Jan 2007 - 22:57)
Bon alors écoute :

Je n'ai pas forcément bien compris ce que tu cherches car, si tu veux arrondir à deux chiffres après la virgule, pourquoi se fouler à scruter tout le web à la recherche d'un script quand il suffit de faire :

y = ( math.round (100 * x) ) / 100

Si x est le nombre que tu souhaites arrondir.

Tu peux aussi considérer math.ceil et math.floor en fonction du type d'arrondi que tu souhaites.
Hello,

J'allais te proposer d'utiliser
function round2d(nb) {
  return Math.round(nb * 100) /100;
}
Mais en fait, je me suis aperçu, que pour Firefox comme pour IE
alert(9.075 *100)
affiche "907.4999999999999". J'avais déjà entendu dire que JavaScript était peu précis au niveau du calcul décimal, mais je l'observe maintenant par moi-même...

En tout cas, ça ne t'aide pas beaucoup, mais je vais essayer de jeter un coup d'oeil à cette histoire.
Modérateur
Bonjour aCOSwt,

Merci de ta réponse. J'ai mis en place une seconde fonction utilisant ton code. Fais le test que j'ai proposé, et tu verras une irrégularité des résultats (à moins que je sois totalement givré)

9.055 donne correctement 9.06
9.075 donne incorrectement 9.07

Pourquoi 9.075 ne donne pas 9.08 alors que 9.055 donne 9.06 ? Tu me suis ? L'arrondissement s'effectue bien pour certains nombres, tandis que pour d'autres, c'est la folie. J'ai du mal à cerner le problème, mais je suis sûr qu'il y a un problème. Internet Explorer donne les bons résultats quant à lui.

Une explication ?
Bon et bien si personne n'est content, il n'y a qu'à tout faire soi même :

y = 1000 * x + 5 ;
y = math.floor ( y ) / 1000 ;

Ce qui se produit est normal et vient de la manière de coder les flottants.
Il faut donc travailler avec des entiers le plus loin possible.

Si cela ne fonctionne encore pas alors :

y = 10000 * x + 50 ;
y = math.floor ( y ) / 10000 ;

Edit : Attention ! J'ai changé round en floor
Modifié par aCOSwt (18 Jan 2007 - 22:29)
Modérateur
Julien Royer a écrit :
J'avais déjà entendu dire que JavaScript était peu précis au niveau du calcul décimal, mais je l'observe maintenant par moi-même...


Ouais, je crois avoir déjà entendu parler de ca. Il doit bien exister une façon de contourner le problème. C'est pourquoi j'ai tant cherché sur le Web à la recherche de la solution miracle.

À bientôt !
Modérateur
Smiley eek Merci beaucoup aCOSwt ! La dernière solution fonctionne très bien. La seule chose est que 1.85 retourne 1.855, et 100 retourne 100.005, mais ca, c'est un léger détail. Je n'ai qu'à conserver les deux décimals, et ca devrait aller.

T'es génial ! Smiley cligne

Ceci dit, si tu le peux, j'aimerais bien que tu m'explique :

aCOSwt a écrit :

Ce qui se produit est normal et vient de la manière de coder les flottants.


Je suis plutôt étonné que ce soit un comportement normal. Tu peux m'en dire plus ?

Juste en passant, c'est Math avec une lettre majuscule. Je dis ca pour ceux qui voudraient utiliser ce code.

Encore merci, je vais pouvoir amorcer la soirée l'esprit tranquille. Smiley biggrin
Modifié par Tony Monast (18 Jan 2007 - 22:47)
aCOSwt a écrit :

y = 1000 * x + 5 ;
y = math.floor ( y ) / 1000 ;


Je confirme, cela fonctionne TRES bien sous mon FireFox 2.0.0.1

J'ai vaguement compris pourquoi les autres formules ne fonctionnent pas.
Dès que j'ai les idées claires sur ce sujet et que je me sens de pouvoir l'expliquer, je m'y colle. Promis.

En attendant enjoy la formule ci-dessus.
Modérateur
Oui j'enjoy très bien cette formule ! Smiley smile

Je t'invite à envoyer une fonction d'arrondissement, à partir de ton code, pour le sujet des fonctions utiles. Ca pourrait servir à d'autres. En plus, tu pourrais la rendre plus dynamique en passant en paramètre le nombre de décimal à conserver.

Pour ma part, je vais me créer une fonction similaire en fin de semaine.
Bon alors...
Je me risque...

1/ D'abord, ceux qui veulent encore plus marrant que le problème de Tony peuvent s'amuser à calculer 0.1 + 0.2 !

2/ Javascript ne connait en matière d'encodage de nombres que les double flottants codés suivant la norme IEEE-754 sur 64 bits.
TOUS les nombres sont concernés. Même ceux qui, pour nous humains sont des entiers.

3/ Rappel sur le codage IEEE-754 :
Tout nombre N est décomposé comme M * (2 à la puissance E).
M devant évidemment toujours être < 2.
On code la mantisse M du nombre N sur les 52 bits de poids faible
L'exposant E sur les 11 bits suivants et on garde le 64° pour le signe.

Prenons l'exemple de N = 4 : => M = 1 et E = 2
Pour N = 10 : => M = 1.25 et E = 3

Gnarf ! Gnarf !...

Et oui, nos jolis nombres bien entiers en base 10 peuvent devenir d'affreux nombres à virgule ainsi codés...
Et... il se peut même parfois que cette décomposition de N en M et E donne des Mantisses illimitées...

C'est ainsi que notre très joli décimal 0.1, si on cherche à le transcoder, va devenir le très affreux...

Allez...

J'attends...

1.59999999999999999... * 2 à la puissance -4 !

Gnarf ! Gnarf ! Gnarf !

Et oui ! Cela ne "tombe" pas juste !

Et... forcément... c'est tronqué !

Pour ceux qui veulent se faire d'autres démonstrations, voici un super outil :

http://babbage.cs.qc.edu/IEEE-754/Decimal.html

4/ Bon alors voilà déjà en soi une bonne raison pour que les calculs soient faux ! MAIS ceci n'explique pas tout encore :

D'une part, c'est bien la peine, me dira-t-on, d'aller coder sur 64 bits si on ne peut même pas avoir une opération bonne à 3 chiffres après la virgule...
D'autre part si ceci explique que le résultat est faux, ceci n'explique pas que le même calcul fait avec le même codage semble juste sous d'autres implémentations.

C'est là que les affaires se compliquent un peu...

Gnarf ! Gnarf !

5/ Javascript est un très gentil langage. Qui fait tout son possible pour que cela tombe juste quand bien même c'est faux. Car... il sait arrondir...
Il sait arrondir... le 52° bit de la mantisse !
Ce qu'il fait très bien... sauf quand des malfaisants lui coupent l'herbe sous le pieds ou les bits sous le double !

Et oui, il se trouve que certaines implémentations de certains OS sur certaines machines font que dans certains cas (Il ne faut pas m'en demander plus je n'en sais rien) L'OS pilote le FPU pour traiter les flottants en mode Extended et non plus Double !

Or... Extended c'est... 53 bits au lieu des 64 pour le double...

Alors là évidemment si je sais arrondir disons pour faire court 1.5999 à 1.6000, mais que quelqu'un me présente à mon insu 1.599 (le codage extended) au lieu de 1.5999 (le codage double), je comprends évidemment 1.5990 (car je suis cablé en codage double) et je ne trouve pas de pertinence à arrondir 1.5990 à 1.6000

Bon ! Plus je me relis et moins je me trouve clair.
J'espère quand même que vous aurez compris et vous re-recommande le lien ci-dessus. Vous comprendrez mieux par vous même et en plus... c'est... marrant...

Enfin... un peu...

Pas longtemps !
Modifié par aCOSwt (19 Jan 2007 - 02:13)
Modérateur
Merci pour les explications aCOSwt, mais à moins d'être un programmeur fou ou un geek à la puissance 2, difficile de tout saisir les explications. Smiley smash L'effort pour l'expliquer est tout de même louable. Smiley smile

C'est plutôt complexe tout ca. Smiley rolleyes
ATTENTION ! WARNING ! IMPORTANT NOTICE !

Ha Ha !!!

Je vous ai fichu une belle trouille avec mon explication !

Hein ?

Allez ! Avouez !

Maintenant vous flippez tel le rat ! vous n'osez plus additionner 1 et 2 !

Bon ! Et bien le pire c'est que...

... Moi aussi.

------------------

Sérieux sans dec :

J'ai cherché à prouver que la méthode que j'ai proposée quelques posts plus haut pour contourner le problème marchait dans toutes les configurations.

Et bien je n'y suis pas arrivé !
(Je ne suis pas non plus parvenu à l'invalider mais ceci n'est pas suffisant comme preuve !
En plus ma méthode présente le désavantage de limiter le nombre max que l'on peut traiter à 10 à la puissance la précision voulue fois moins que le max codable sur un flottant.
Cela peut présenter un défaut.

Alors je me suis dit !

aCOS ! (C'est comme cela que je me parle à moi)
il n'y a pas deux solutions !
Pour être sûr que cela fonctionne,

IL FAUT TOUT TRAITER EN CHAINE DE CARACTERE !

Donc, je vous propose le code suivant :

ATTENTION ! Paranoïds only ! Merci aux autres de s'abstenir de commentaires quant à l'alambication de ce qui suit, de la montagne qui écrase la souris etc, etc... !



DEPRECATED ! 
Récupérer la release 1 plus bas !

function accurateRoundTo(number,precision){
		
		var	nstr, parts, resint, dotpos, dummy;
		
		parts=new Array();
		nstr=number.toString();
		dotpos=nstr.indexOf(".",0);
		if((dotpos<0)||(nstr.length<(dotpos+precision+2)))
			return(nstr);
		parts=nstr.split(".");
		resint=parts[0]+parts[1].substr(0,precision);
		if(parseInt(parts[1].charAt(precision))>4){
			dummy=parseInt(resint)+1;
			resint=dummy.toString();}
		parts[0]=resint.substr(0,resint.length-precision);
		parts[1]=resint.substr(parts[0].length,precision);
		return(parts[0]+"."+parts[1]);}	


Code livré as is sans garantie d'aucune sorte mais copyright 2006 aCOSwt tous droits réservés sauf pour l'URSS et mon ami Tony Monast !
Modifié par aCOSwt (19 Jan 2007 - 22:29)
aCOSwt a écrit :
Je vous ai fichu une belle trouille avec mon explication !

Hein ?
Oui. Smiley smile
aCOSwt a écrit :
IL FAUT TOUT TRAITER EN CHAINE DE CARACTERE !
Ce n'est pas très réjouissant, mais si l'on veut être certain de son résultat, j'ai en effet l'impression que c'est la seule solution. Smiley ohwell
Julien Royer a écrit :
Ce n'est pas très réjouissant


Et comment !

Si on souhaite des détails techiques quant au où quand comment du 5° paragraphe de mon explication on peut aller voir :

https://bugzilla.mozilla.org/show_bug.cgi?id=264912

Il faut scroller un peu et c'est en anglais et c'est du... niveau processeur...
Modifié par aCOSwt (19 Jan 2007 - 12:49)
Modérateur
Pour ce qui est de la fonction accurateRoundTo, je crois que ce n'est pas encore tout à fait au point. La fonction ne retourne rien si le nombre n'a pas au moins 3 décimals.

De toutes façons, la première proposition me semblait tout à fait adéquate, non ?
Heu...

Je ne comprends pas ton objection.

Je viens de copier coller le code ci-dessus pour le retester au cas où le copier/coller m'aurait joué un tour. Et cela fonctionne comme il faut quel que soit le nombre de décimales.

J'ai peut être oublié de préciser que le paramètre precision à passer correspond au nombre de digits après la virgule sur lequel l'arrondi doit porter :

i.e. : accurateRoundTo(1.28,1) pour obtenir 1.3
ou encore accurateRoundTo(12.3452,3) pour obtenir 12.345

Qui plus est je ne vois pas dans le code de cas où la fonction ne renvoit rien.
Même si tu demandes accurateRoundTo(1.2,10).
Modifié par aCOSwt (19 Jan 2007 - 16:29)
Modérateur
Hmmm... j'étais peut-être givré, j'en sais rien. As-tu essayé accurateRoundTo(100,2) ?

Quel est le résultat ? Est-ce que la fonction retourne bien 100 ou 100.00, peu importe ? Tant que le résultat soit 100 ?

Edit : Eh ben tiens, je viens de tester à nouveau, et ca fonctionne bien. J'ignore quel était le problème. Bref, j'ai rien dis. Je retourne au fond de ma grotte. Smiley cligne
Modifié par Tony Monast (19 Jan 2007 - 17:04)
Modérateur
En espérant ne pas encore me tromper, mais que te donne :


accurateRoundTo(0.825,2)


??

Pour ma part, ca me donne 0.1. Smiley sweatdrop
Modifié par Tony Monast (19 Jan 2007 - 17:17)
Tony Monast a écrit :
En espérant ne pas encore me tromper, mais que te donne :


accurateRoundTo(0.825,2)


??

Pour ma part, ca me donne 0.1. Smiley sweatdrop


Gee !

Là tu as raison.
Je n'avais pas testé cela !

C'est à cause du fait que parseInt("0825") = 0 ! et non 825 !

Je ne m'attendais pas à celle-là.

Bon ! Je corrige et dans la foulée traiterai le cas d'un nombre négatif qui n'était pas pris en compte non plus.
Release 1 d'accurateRoundTo suite à remaque Tony Monast des erreurs sur les nombres compris entre 0 et 1 et traitement des nombres négatifs :




	function accurateRoundTo(number,precision){

		var	nstr, parts, resint, dotpos, dummy, isneg, isnul;

		isneg=0;
		isnul=0;
		parts=new Array();
		if(number<0){
			number=Math.abs(number);
			isneg=1;}		
		nstr=number.toString();
		dotpos=nstr.indexOf(".",0);
		if((dotpos<0)||(nstr.length<(dotpos+precision+2))){
			if(isneg)
				return("-"+nstr);
			else
				return(nstr);}
		parts=nstr.split(".");
		if(parts[0]=="0"){
			parts[0]="10";
			isnul=1;}
		resint=parts[0]+parts[1].substr(0,precision);
		if(parseInt(parts[1].charAt(precision))>4){
			dummy=parseInt(resint)+1;
			resint=dummy.toString();}
		parts[0]=resint.substr(0,resint.length-precision);
		parts[1]=resint.substr(parts[0].length,precision);
		if(isnul)
			parts[0]=parts[0].substr(1,1);
		if(isneg)
			parts[0]="-"+parts[0];
		return(parts[0]+"."+parts[1]);}

Modifié par aCOSwt (19 Jan 2007 - 22:39)
Pages :