Utilisation du OU au niveau du bit de 0 pour plancher un nombre

193

Un de mes collègues est tombé sur une méthode pour plancher les nombres flottants en utilisant un bit ou:

var a = 13.6 | 0; //a == 13

Nous en parlions et nous nous posions quelques questions.

  • Comment ça marche? Notre théorie était que l'utilisation d'un tel opérateur convertit le nombre en entier, supprimant ainsi la partie fractionnaire
  • Y a-t-il des avantages à faire Math.floor? C'est peut-être un peu plus rapide? (jeu de mots non prévu)
  • Y a-t-il des inconvénients? Peut-être que cela ne fonctionne pas dans certains cas? La clarté est une évidence, car nous avons dû le comprendre, et bien, j'écris cette question.

Merci.

Alex Turpin
la source
6
Inconvénient: cela ne fonctionne que jusqu'à 2 ^ 31−1 soit environ 2 milliards (10 ^ 9). La valeur numérique maximale est d'environ 10 ^ 308 btw.
Šime Vidas
12
Exemple: prend la valeur 3000000000.1 | 0-1294967296. Donc, cette méthode ne peut pas être appliquée pour les calculs d'argent (en particulier dans les cas où vous multipliez par 100 pour éviter les nombres décimaux).
Šime Vidas
13
@ Les flotteurs ŠimeVidas ne devraient pas non plus être utilisés dans les calculs d'argent
George Reith
20
Ce n'est pas un revêtement de sol, il est tronqué (arrondi vers 0).
Bartłomiej Zalewski
3
@sequence essayez de taper 0.1 + 0.2 == 0.3dans une console JavaScript. Si votre langue le prend en charge, vous devez utiliser un type décimal. Sinon, stockez les cents à la place.
Alex Turpin

Réponses:

161

Comment ça marche? Notre théorie était que l'utilisation d'un tel opérateur convertit le nombre en entier, supprimant ainsi la partie fractionnaire

Toutes les opérations au niveau du bit, à l'exception du décalage à droite non signé >>>, fonctionnent sur des entiers 32 bits signés. Ainsi, l'utilisation d'opérations au niveau du bit convertira un flottant en entier.

Y a-t-il des avantages par rapport à Math.floor? C'est peut-être un peu plus rapide? (jeu de mots non prévu)

http://jsperf.com/or-vs-floor/2 semble légèrement plus rapide

Y a-t-il des inconvénients? Peut-être que cela ne fonctionne pas dans certains cas? La clarté est évidente, car nous avons dû le comprendre, et bien, j'écris cette question.

  • Ne passera pas jsLint.
  • Entiers signés 32 bits uniquement
  • Comportement comparatif étrange:, Math.floor(NaN) === NaNtandis que(NaN | 0) === 0
Joe
la source
9
@harold en effet, parce qu'il ne arrondit pas, ne fait que tronquer.
Alex Turpin
5
Un autre inconvénient possible est que Math.floor(NaN) === NaN, tandis que (NaN | 0) === 0. Cette différence peut être importante dans certaines applications.
Ted Hopp
4
Votre jsperf fournit des informations de performance pour les boucles vides sur chrome en raison du mouvement de code invariant de boucle. Un test de performance légèrement meilleur serait: jsperf.com/floor-performance/2
Sam Giles
4
Ceci est une partie standard de asm.js(où je l'ai appris pour la première fois). C'est plus rapide si pour aucune autre raison car il n'appelle pas une fonction sur l' Mathobjet, une fonction qui pourrait à tout moment être remplacée comme dans Math.floor = function(...).
gman
3
(value | 0) === valuepourrait être utilisé pour vérifier qu'une valeur est en fait un entier et seulement un entier (comme dans le code source Elm @ dwayne-crooks linked). Et foo = foo | 0pourrait être utilisé pour contraindre n'importe quelle valeur à un entier (où les nombres de 32 bits sont tronqués et tous les non-nombres deviennent 0).
David Michael Gregg
36

Il s'agit de la troncature par opposition au revêtement de sol. La réponse de Howard est en quelque sorte correcte; Mais j'ajouterais queMath.floor fait exactement ce qu'il est censé faire en ce qui concerne les nombres négatifs. Mathématiquement, c'est ce qu'est un plancher.

Dans le cas que vous avez décrit ci-dessus, le programmeur était plus intéressé par la troncature ou la suppression complète de la décimale. Cependant, la syntaxe qu'ils ont utilisée masque en quelque sorte le fait qu'ils convertissent le flottant en un int.

Tchad La Guardia
la source
7
C'est la bonne réponse, la réponse acceptée ne l'est pas. Ajoutez à cela qui Math.floor(8589934591.1)produit le résultat attendu, 8589934591.1 | 0 NE FAIT PAS .
Salman A
21

Dans ECMAScript 6, l'équivalent de |0est Math.trunc , un peu je devrais dire:

Renvoie la partie intégrale d'un nombre en supprimant tous les chiffres fractionnaires. Il tronque simplement le point et les chiffres derrière lui, peu importe si l'argument est un nombre positif ou négatif.

Math.trunc(13.37)   // 13
Math.trunc(42.84)   // 42
Math.trunc(0.123)   //  0
Math.trunc(-0.123)  // -0
Math.trunc("-1.123")// -1
Math.trunc(NaN)     // NaN
Math.trunc("foo")   // NaN
Math.trunc()        // NaN
zangw
la source
6
Sauf le fait que Math.trunc()travailler avec un nombre supérieur ou égal à 2 ^ 31 et | 0ne le fait pas
Nolyurn
10

Votre premier point est correct. Le nombre est converti en un entier et ainsi tous les chiffres décimaux sont supprimés. Veuillez noter que cela Math.floorarrondit à l'entier suivant vers moins l'infini et donne donc un résultat différent lorsqu'il est appliqué à des nombres négatifs.

Howard
la source
5

Javascript représente Numbersous forme de nombres flottants 64 bits Double Precision .

Math.floor fonctionne dans cet esprit.

Les opérations au niveau du bit fonctionnent en 32 bits signé entiers . Les entiers signés 32 bits utilisent le premier bit comme signifiant négatif et les 31 autres bits sont le nombre. Pour cette raison, le nombre minimal et maximal autorisé des nombres signés 32 bits sont respectivement -2 147 483 648 et 2147483647 (0x7FFFFFFFF).

Donc, lorsque vous faites | 0, vous faites essentiellement& 0xFFFFFFFF . Cela signifie que tout nombre représenté par 0x80000000 (2147483648) ou supérieur sera renvoyé sous la forme d'un nombre négatif.

Par exemple:

 // Safe
 (2147483647.5918 & 0xFFFFFFFF) ===  2147483647
 (2147483647      & 0xFFFFFFFF) ===  2147483647
 (200.59082098    & 0xFFFFFFFF) ===  200
 (0X7FFFFFFF      & 0xFFFFFFFF) ===  0X7FFFFFFF

 // Unsafe
 (2147483648      & 0xFFFFFFFF) === -2147483648
 (-2147483649     & 0xFFFFFFFF) ===  2147483647
 (0x80000000      & 0xFFFFFFFF) === -2147483648
 (3000000000.5    & 0xFFFFFFFF) === -1294967296

Aussi. Les opérations au niveau du bit ne "plancher" pas. Ils tronquent , ce qui revient à dire qu'ils arrondissent le plus près 0. Une fois que vous êtes passé aux nombres négatifs, Math.floorarrondit vers le bas tandis que le bit commence à arrondir vers le haut .

Comme je l'ai déjà dit, il Math.floorest plus sûr car il fonctionne avec des nombres flottants 64 bits. Le bit est plus rapide , oui, mais limité à une portée signée 32 bits.

Résumer:

  • Bitwise fonctionne de la même manière si vous travaillez à partir de 0 to 2147483647 .
  • Au niveau du bit, 1 chiffre est désactivé si vous travaillez à partir de -2147483647 to 0 .
  • Bitwise est complètement différent pour les nombres inférieurs -2147483648et supérieurs à 2147483647.

Si vous voulez vraiment améliorer les performances et utiliser les deux:

function floor(n) {
    if (n >= 0 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    if (n > -0x80000000 && n < 0) {
      return (n - 1) & 0xFFFFFFFF;
    }
    return Math.floor(n);
}

Juste pour ajouter des Math.trunctravaux comme des opérations au niveau du bit. Vous pouvez donc faire ceci:

function trunc(n) {
    if (n > -0x80000000 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    return Math.trunc(n);
}
Mèche courte
la source
5
  • Les spécifications indiquent qu'il est converti en entier:

    Soit lnum ToInt32 (lval).

  • Performance: cela a déjà été testé chez jsperf .

Remarque: lien mort vers la spécification supprimé

pimvdb
la source