javascript
  • floating-point
  • floating-accuracy
  • ieee-754
  • 2009-09-04 10 views 38 likes 
    38

    Ver este código:erróneamente redondeadas en Javascript

    <html> 
    <head> 
    
    <script src="http://www.json.org/json2.js" type="text/javascript"></script> 
    
    <script type="text/javascript"> 
    
        var jsonString = '{"id":714341252076979033,"type":"FUZZY"}'; 
        var jsonParsed = JSON.parse(jsonString); 
        console.log(jsonString, jsonParsed); 
    
    
    </script> 
    </head> 
    <body> 
    </body> 
    </html> 
    

    Cuando veo a mi consola en Firefox 3.5, el valor de jsonParsed es:

    Object id=714341252076979100 type=FUZZY 
    

    es decir el número se redondea. Intentó valores diferentes, el mismo resultado (número redondeado).

    También no obtengo sus reglas de redondeo. 714341252076979136 se redondea a 714341252076979200, mientras que 714341252076979135 se redondea a 714341252076979100.

    EDIT: vea el primer comentario a continuación. Aparentemente, esto no se trata de JSON, sino de manejar el número de Javascript. Pero la pregunta sigue siendo:

    ¿Por qué sucede esto?

    +0

    Gracias a todos por sus rápidas respuestas útiles, me gustaría poder marcar las 3 respuestas oficiales. – Jaanus

    Respuesta

    47

    Lo que está viendo aquí es en realidad el efecto de dos redondeos. Los números en ECMAScript son representados internamente en coma flotante de doble precisión. Cuando id se establece en 714341252076979033 (0x9e9d9958274c359 en hexadecimal), en realidad se le asigna el valor de precisión doble representable más cercano, que es 714341252076979072 (0x9e9d9958274c380). Cuando imprime el valor, se redondea a 15 dígitos decimales significativos, lo que da 14341252076979100.

    7

    No es causado por este analizador json. Intente ingresar al 714341252076979033 en la consola de fbug. Verá el mismo 714341252076979100.

    Ver esta entrada del blog para más detalles: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too

    +6

    Gracias por vincular mi artículo, pero solo explica la mitad del problema: la IMPRESIÓN del valor redondeado internamente. Incluso si javascript le permite imprimir todo, todavía estaría mal, sería el valor de precisión doble representable más cercano, como explican otros a continuación. –

    40

    usted está desbordando la capacidad del tipo de número de JavaScript, ver §8.5 of the spec para más detalles. Esas identificaciones necesitarán ser cadenas.

    punto flotante de precisión doble IEEE-754 (el tipo de número que usa JavaScript) no puede representar con precisión todos los números (por supuesto). Famoso, 0.1 + 0.2 == 0.3 es falso. Eso puede afectar números enteros al igual que afecta a números fraccionarios; comienza una vez que obtiene por encima de 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER).

    Más allá de Number.MAX_SAFE_INTEGER + 1 (9007199254740992), el formato de coma flotante IEEE-754 ya no puede representar todos los números enteros consecutivos. 9007199254740991 + 1 es 9007199254740992, pero 9007199254740992 + 1 es también9007199254740992 porque 9007199254740993 no se pueden representar en el formato. El próximo que puede ser es 9007199254740994. Entonces 9007199254740995 no puede ser, pero 9007199254740996 puede.

    El motivo es que nos hemos quedado sin bits, por lo que ya no tenemos un bit 1; el bit de orden más bajo ahora representa múltiplos de 2. Eventualmente, si seguimos adelante, perdemos ese bit y solo trabajamos en múltiplos de 4. Y así sucesivamente.

    Sus valores son y por encima de ese umbral, por lo que se redondean al valor representable más cercano.


    Si eres curioso acerca de los bits, esto es lo que sucede: Un número binario de coma flotante de doble precisión IEEE-754 tiene un bit de signo, 11 bits de exponente (que define la escala global de la serie , como un poder de 2 [porque este es un formato binario]), y 52 bits de significado (pero el formato es tan inteligente que obtiene 53 bits de precisión de esos 52 bits). Cómo se usa el exponente es complicado (described here), pero en muy términos vagos, si agregamos uno al exponente, el valor del significado se duplica, ya que el exponente se usa para potencias de 2 (de nuevo, caveat there, no es directo, hay inteligencia allí).

    Así que vamos a ver el valor 9007199254740991 (aka, Number.MAX_SAFE_INTEGER):

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110011 1111111111111111111111111111111111111111111111111111 
                    = 9007199254740991 (Number.MAX_SAFE_INTEGER) 
    

    Ese valor del exponente, 10000110011, quiere decir que cada vez que añadimos una a la mantisa, el número representado sube por 1 (el número 1, perdimos la capacidad de representar números fraccionarios mucho antes).

    Pero ahora que significand está lleno. Para pasar ese número, tenemos que aumentar el exponente, lo que significa que si agregamos uno al significado, el valor del número representado aumenta en 2, no en 1 (porque el exponente se aplica a 2, la base de este número de punto flotante binario):

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110100 0000000000000000000000000000000000000000000000000000 
                    = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1) 
    

    Bueno, eso está bien, porque es 9007199254740991 + 19007199254740992 de todos modos. ¡Pero! No podemos representar 9007199254740993. Nos hemos quedado sin partes. Si añadimos a sólo 1 a la mantisa, que añade 2 al valor:

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110100 0000000000000000000000000000000000000000000000000001 
                    = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3) 
    

    El formato no puede representar números impares más a medida que aumenta el valor, el exponente es demasiado grande.

    Finalmente, nos quedamos sin bits significativos y tenemos que aumentar el exponente, por lo que solo podemos representar múltiplos de 4. Entonces múltiplos de 8. Luego múltiplos de 16. Y así sucesivamente.

    +4

    Me gusta esta respuesta porque realmente te dice cómo RESOLVER el problema. – jsh

    2

    El problema es que su número requiere una mayor precisión que JavaScript.

    ¿Se puede enviar el número como una cadena? Separado en dos partes?

    4

    JavaScript utiliza doble precisión valores de punto flotante, es decir, un total precisión de 53 bits, pero se necesita

    ceil(lb 714341252076979033) = 60 
    

    bits para representar con exactitud el valor.

    El número exactamente representable más cercano es 714341252076979072 (escribir el número original en binario, reemplace los últimos 7 dígitos con 0 y redondear al alza debido a que el dígito más alto fue reemplazado 1).

    Obtendrá 714341252076979100 en lugar de este número porque ToString() según lo descrito por ECMA-262, §9.8.1 funciona con potencias de diez y en 53 bits de precisión todos estos números son iguales.

    1

    JavaScript solo puede manejar números enteros exactos de hasta aproximadamente 9000 millones de millones (eso es 9 con 15 ceros). Más alto que eso y obtienes basura. Para solucionar esto, use cadenas para mantener los números. Si necesita hacer cálculos matemáticos con estos números, escriba sus propias funciones o vea si puede encontrar una biblioteca para ellas: sugiero la primera ya que no me gustan las bibliotecas que he visto. Para comenzar, vea dos de mis funciones en another answer.

    Cuestiones relacionadas