round remove redondear number hacia decimals decimales arriba javascript floating-point decimal precision fractions

remove - ¿Existe una forma confiable en JavaScript para obtener el número de decimales de un número arbitrario?



math.round java dos decimales (6)

Es importante tener en cuenta que no estoy buscando una función de redondeo. Estoy buscando una función que devuelva el número de decimales en la representación decimal simplificada de un número arbitrario. Es decir, tenemos lo siguiente:

decimalPlaces(5555.0); //=> 0 decimalPlaces(5555); //=> 0 decimalPlaces(555.5); //=> 1 decimalPlaces(555.50); //=> 1 decimalPlaces(0.0000005); //=> 7 decimalPlaces(5e-7); //=> 7 decimalPlaces(0.00000055); //=> 8 decimalPlaces(5.5e-7); //=> 8

Mi primer instinto fue usar las representaciones de cadena: dividir en ''.'' , luego en ''e-'' , y haz los cálculos, así (el ejemplo es detallado):

function decimalPlaces(number) { var parts = number.toString().split(''.'', 2), integerPart = parts[0], decimalPart = parts[1], exponentPart; if (integerPart.charAt(0) === ''-'') { integerPart = integerPart.substring(1); } if (decimalPart !== undefined) { parts = decimalPart.split(''e-'', 2); decimalPart = parts[0]; } else { parts = integerPart.split(''e-'', 2); integerPart = parts[0]; } exponentPart = parts[1]; if (exponentPart !== undefined) { return integerPart.length + (decimalPart !== undefined ? decimalPart.length : 0) - 1 + parseInt(exponentPart); } else { return decimalPart !== undefined ? decimalPart.length : 0; } }

Para mis ejemplos anteriores, esta función funciona. Sin embargo, no estoy satisfecho hasta que haya probado todos los valores posibles, por lo que eliminé Number.MIN_VALUE .

Number.MIN_VALUE; //=> 5e-324 decimalPlaces(Number.MIN_VALUE); //=> 324 Number.MIN_VALUE * 100; //=> 4.94e-322 decimalPlaces(Number.MIN_VALUE * 100); //=> 324

Esto pareció razonable al principio, pero luego en una toma doble, me di cuenta de que 5e-324 * 10 debería ser 5e-323 . Y luego me di cuenta: estoy lidiando con los efectos de la cuantización de números muy pequeños. No solo se cuantifican los números antes del almacenamiento; Además, algunos números almacenados en binario tienen representaciones decimales irrazonablemente largas, por lo que sus representaciones decimales se están truncando. Esto es desafortunado para mí, porque significa que no puedo obtener su verdadera precisión decimal utilizando sus representaciones de cadena.

Así que vengo a ti, comunidad StackOverflow. ¿Alguien entre ustedes sabe una manera confiable de obtener la precisión pos-decimal verdadera de un número?

El propósito de esta función, si alguien pregunta, es para usarlo en otra función que convierte un flotador en una fracción simplificada (es decir, devuelve el numerador entero relativamente coprime y el denominador natural distinto de cero). La única pieza faltante en esta función externa es una forma confiable de determinar el número de lugares decimales en el flotador para que pueda multiplicarlo por el poder apropiado de 10. Espero que lo esté pensando demasiado.


Actualización 2017

Aquí hay una versión simplificada basada en la respuesta de Edwin. Tiene un conjunto de pruebas y devuelve el número correcto de decimales para los casos de esquina que incluyen NaN, Infinito, notaciones de exponentes y números con representaciones problemáticas de sus fracciones sucesivas, como 0.0029 o 0.0408. Esto cubre la gran mayoría de las aplicaciones financieras, donde 0.0408 con 4 decimales (no 6) es más importante que 3.14e-21 con 23.

function decimalPlaces(n) { function hasFraction(n) { return Math.abs(Math.round(n) - n) > 1e-10; } let count = 0; // multiply by increasing powers of 10 until the fractional part is ~ 0 while (hasFraction(n * (10 ** count)) && isFinite(10 ** count)) count++; return count; } for (const x of [ 0.0028, 0.0029, 0.0408, 0.1584, 4.3573, // corner cases against Edwin''s answer 11.6894, 0, 1.0, 1.00, 0.123, 1e-3, -1e2, -1e-2, -0.1, NaN, 1E500, Infinity, Math.PI, 1/3, 3.14, 2.e-3, 2.e-14, 1e-9, // 9 1e-10, // should be 10, but is below the precision limit -3.14e-13, // 15 3.e-13, // 13 3.e-14, // should be 14, but is below the precision limit 123.12345678901234567890, // 14, the precision limit 5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8, 0.000006, 0.0000007, 0.123, 0.121, 0.1215 ]) console.log(x, ''->'', decimalPlaces(x));

La compensación es que el método está limitado a un máximo de 10 decimales garantizados. Puede devolver más decimales correctamente, pero no confíe en eso. Los números más pequeños que 1e-10 pueden considerarse cero, y la función devolverá 0. Ese valor en particular fue elegido para resolver correctamente el caso de esquina 11.6894, para el cual el método simple de multiplicar por potencias de 10 falla (devuelve 5 en lugar de 4 ).

Sin embargo, este es el caso de la quinta esquina que he descubierto, después de 0.0029, 0.0408, 0.1584 y 4.3573. Después de cada uno, tuve que reducir la precisión en un decimal. No sé si hay otros números con menos de 10 decimales para los cuales esta función puede devolver un número incorrecto de decimales. Para estar en el lado seguro, busque una biblioteca de precisión arbitraria .

Tenga en cuenta que la conversión a la cadena y la división por . Solo es una solución para hasta 7 decimales. String(0.0000007) === "7e-7" . O tal vez incluso menos? La representación de punto flotante no es intuitiva.


Basado en la respuesta gion_13 se me ocurrió esto:

function decimalPlaces(n){ let result= /^-?[0-9]+/.([0-9]+)$/.exec(n); return result === null ? 0 : result[1].length; } for (const x of [ 0, 1.0, 1.00, 0.123, 1e-3, 3.14, 2.e-3, -3.14e-21, 5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8, 0.000006, 0.0000007, 0.123, 0.121, 0.1215 ]) console.log(x, ''->'', decimalPlaces(x));

Corrige el retorno 1 cuando no hay decimales. Por lo que sé, esto funciona sin errores.


Bueno, uso una solución basada en el hecho de que si multiplicas un número de coma flotante por la potencia correcta de 10, obtienes un número entero.

Por ejemplo, si multiplicas 3.14 * 10 ^ 2, obtienes 314 (un número entero). El exponente representa entonces el número de decimales que tiene el número de punto flotante.

Entonces, pensé que si gradualmente multiplico un punto flotante al aumentar los poderes de 10, eventualmente llegarás a la solución.

let decimalPlaces = function () { function isInt(n) { return typeof n === ''number'' && parseFloat(n) == parseInt(n, 10) && !isNaN(n); } return function (n) { const a = Math.abs(n); let c = a, count = 1; while (!isInt(c) && isFinite(c)) { c = a * Math.pow(10, count++); } return count - 1; }; }(); for (const x of [ 0.0028, 0.0029, 0.0408, 0, 1.0, 1.00, 0.123, 1e-3, 3.14, 2.e-3, 2.e-14, -3.14e-21, 5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8, 0.000006, 0.0000007, 0.123, 0.121, 0.1215 ]) console.log(x, ''->'', decimalPlaces(x));


Nota histórica: el hilo de comentarios a continuación puede referirse a la primera y segunda implementación. Cambié el pedido en septiembre de 2017, ya que liderar con una implementación de buggy causó confusión.

Si quieres algo que asigna "0.1e-100" a 101, entonces puedes probar algo como

function decimalPlaces(n) { // Make sure it is a number and use the builtin number -> string. var s = "" + (+n); // Pull out the fraction and the exponent. var match = /(?:/.(/d+))?(?:[eE]([+/-]?/d+))?$/.exec(s); // NaN or Infinity or integer. // We arbitrarily decide that Infinity is integral. if (!match) { return 0; } // Count the number of digits in the fraction and subtract the // exponent to simulate moving the decimal point left by exponent places. // 1.234e+2 has 1 fraction digit and ''234''.length - 2 == 1 // 1.234e-2 has 5 fraction digit and ''234''.length - -2 == 5 return Math.max( 0, // lower limit. (match[1] == ''0'' ? 0 : (match[1] || '''').length) // fraction length - (match[2] || 0)); // exponent }

Según la especificación, cualquier solución basada en el número incorporado -> conversión de cadena solo puede tener una precisión de 21 lugares más allá del exponente.

9.8.1 ToString aplicado al tipo de número

  1. De lo contrario, sea n, k, ys sean enteros tales que k ≥ 1, 10k − 1 ≤ s <10k, el valor del Número para s × 10n − k es m, yk es lo más pequeño posible. Tenga en cuenta que k es el número de dígitos en la representación decimal de s, que s no es divisible por 10, y que el dígito menos significativo de s no está necesariamente determinado de forma única por estos criterios.
  2. Si k ≤ n ≤ 21, devuelva la cadena que consta de los k dígitos de la representación decimal de s (en orden, sin ceros iniciales), seguidos de n − k apariciones del carácter ''0''.
  3. Si 0 <n ≤ 21, devuelva la cadena que consta de los n dígitos más significativos de la representación decimal de s, seguido de un punto decimal ''.'', Seguido de los dígitos k − n restantes de la representación decimal de s.
  4. Si −6 <n ≤ 0, devuelva la Cadena que consta del carácter ''0'', seguido de un punto decimal ''.'', Seguido de −n apariciones del carácter ''0'', seguido de los k dígitos de la representación decimal de s.

Nota histórica: la implementación a continuación es problemática. Lo dejo aquí como contexto para el hilo de comentarios.

Según la definición de Number.prototype.toFixed , parece que lo siguiente debería funcionar, pero debido a la representación IEEE-754 de valores dobles, ciertos números producirán resultados falsos. Por ejemplo, decimalPlaces(0.123) devolverá 20 .

function decimalPlaces(number) { // toFixed produces a fixed representation accurate to 20 decimal places // without an exponent. // The ^-?/d*/. strips off any sign, integer portion, and decimal point // leaving only the decimal fraction. // The 0+$ strips off any trailing zeroes. return ((+number).toFixed(20)).replace(/^-?/d*/.?|0+$/g, '''').length; } // The OP''s examples: console.log(decimalPlaces(5555.0)); // 0 console.log(decimalPlaces(5555)); // 0 console.log(decimalPlaces(555.5)); // 1 console.log(decimalPlaces(555.50)); // 1 console.log(decimalPlaces(0.0000005)); // 7 console.log(decimalPlaces(5e-7)); // 7 console.log(decimalPlaces(0.00000055)); // 8 console.log(decimalPlaces(5e-8)); // 8 console.log(decimalPlaces(0.123)); // 20 (!)


esto funciona para números más pequeños que e-17 :

function decimalPlaces(n){ var a; return (a=(n.toString().charAt(0)==''-''?n-1:n+1).toString().replace(/^-?[0-9]+/.?([0-9]+)$/,''$1'').length)>=1?a:0; }


No solo se cuantifican los números antes del almacenamiento; Además, algunos números almacenados en binario tienen representaciones decimales excesivamente largas, por lo que se están truncando sus representaciones decimales.

JavaScript representa los números utilizando el IEEE-754 doble precisión (64 bits). Según tengo entendido, esto le da una precisión de 53 bits, o de quince a dieciséis dígitos decimales.

Así que para cualquier número con más dígitos solo obtienes una aproximación. Hay algunas bibliotecas alrededor para manejar grandes números con más precisión, incluidas las mencionadas en este hilo .