2010-12-03 58 views
12

Je suis en train de revoir une question (How to test if numeric conversion will change value?) qui, en ce qui me concerne, a été entièrement résolue. Le problème était de détecter lorsqu'une valeur numérique particulière dépassait le type de numéro IEEE-754 de JavaScript. La question précédente utilisait C# et la réponse marquée fonctionnait parfaitement.IEEE-754 Double (virgule flottante 64 bits) vs longue (entier de 64 bits) Revisited

Maintenant, je fais exactement la même tâche mais cette fois en Java et cela ne fonctionne pas. AFAIK, Java utilise IEEE-754 pour son double type de données. Je devrais donc être capable de le lancer d'avant en arrière pour forcer la perte de précision, mais il faut faire des allers-retours. Déconcerté par cela j'ai commencé à pousser plus loin dans Java et maintenant je suis vraiment confus.

Dans C# et Java, les valeurs minimales et maximales pour longtemps sont les mêmes:

long MIN_VALUE = -9223372036854775808L; 
long MAX_VALUE = 9223372036854775807L; 

AFAIK, ces valeurs sont en dehors des nombres représentables dans la norme IEEE-754 en raison des bits fixes réservés à l'exposant et signe.

// this fails in browsers that have stuck with the pure ECMAScript Number format 
var str = Number(-9223372036854775808).toFixed(); 
if ("-9223372036854775808" !== str) { throw new Error("Overflow!"); } 

Cela renvoie false pour (valeur = -9223372036854775808L) en Java:

boolean invalidIEEE754(long value) { 
    try { 
     return ((long)((double)value)) != value; 
    } catch (Exception ex) { 
     return true; 
    } 
} 

Cela renvoie false pour (valeur = -9223372036854775808L) en Java:

boolean invalidIEEE754(long value) { 
    // trying to get closer to the actual representation and 
    // being more explicit about conversions 
    long bits = Double.doubleToLongBits(Long.valueOf(value).doubleValue()); 
    long roundtrip = Double.valueOf(Double.longBitsToDouble(bits)).longValue(); 
    return (value != roundtrip); 
} 

Ce retourne true pour (valeur = -9223372036854775808L) mais est moins précis:

boolean invalidIEEE754(long value) { 
    return (0x0L != (0xFFF0000000000000L & (value < 0L ? -value : value))); 
} 

Pourquoi cela fonctionne-t-il de cette façon? Ai-je manqué quelque chose comme l'optimisation du compilateur, par ex. Est-ce que le compilateur détecte mes conversions et les "corrige" pour moi?

Édition: Ajout d'un cas de test sur demande. Tous les trois de ces tests échouent:

import static org.junit.Assert.*; 
import org.junit.Test; 

public class FooTests { 

    @Test 
    public void ieee754One() { 
     assertTrue(((long)((double)Long.MIN_VALUE)) != Long.MIN_VALUE); 
    } 

    @Test 
    public void ieee754Two() { 
     long bits = Double.doubleToLongBits(Long.valueOf(Long.MIN_VALUE).doubleValue()); 
     long roundtrip = Double.valueOf(Double.longBitsToDouble(bits)).longValue(); 

     assertTrue(Long.MIN_VALUE != roundtrip); 
    } 

    @Test 
    public void ieee754Three() { 
     long bits = Double.doubleToRawLongBits(Long.valueOf(Long.MIN_VALUE).doubleValue()); 
     long roundtrip = Double.valueOf(Double.longBitsToDouble(bits)).longValue(); 

     assertTrue(Long.MIN_VALUE != roundtrip); 
    } 
} 
+0

Les deux méthodes retournent 'false' pour moi. JDK 1.6.0_21. – axtavt

+0

Intéressant! Maintenant, je suis vraiment confus. Le mien fonctionne via JUnit mais devrait aussi être sur JDK 1.6.0_21. – mckamey

+0

Pouvez-vous montrer le test complet? – axtavt

Répondre

6

-9223372036854775808Lest représentable comme un nombre double précision IEEE-754. C'est exactement -2^63, qui a la double représentation -1.0 x 2^63 et l'encodage 0xc3e0000000000000.

Double est capable de représenter des nombres beaucoup, beaucoup plus grand que cela. Cependant, il n'est pas capable de représenter tous les entiers dans la plage des nombres représentables. Par exemple, si vous ajoutez un au nombre, vous obtiendrez -9223372036854775807 = -2^63 + 1, qui est et non, représentable comme une valeur double précision, et ne survivra pas à la conversion aller-retour.

Si vous convertissez -2^63 + 1 en double, l'arrondit à la valeur double représentable la plus proche, à savoir -2^63; la conversion à long préservera cette valeur.

Editer: Sur quelle plateforme avez-vous effectué votre test JavaScript? Dans Safari courant,

"-9223372036854775808" === Number(-9223372036854775808).toFixed() 

est évaluée comme True.

+0

Aussi intéressant. On dirait que je choisis un mauvais cas de test! Pourquoi cela fonctionnerait-il en C#? Cela me rend confus. – mckamey

+0

@McKAMEY: Si cela fonctionne réellement en C#, c'est contraire à la norme IEEE-754. Avez-vous observé cela? –

+0

Ouais c'était mon cas de test et il "passé" :) Ensuite, je l'ai déplacé vers Java et il ne l'était pas. – mckamey