Pregunta Validar números decimales en JavaScript - IsNumeric ()


¿Cuál es la forma más limpia y efectiva de validar números decimales en JavaScript?

Puntos de bonificación por:

  1. Claridad. La solución debe ser limpia y simple.
  2. Multiplataforma.

Casos de prueba:

01. IsNumeric('-1')      => true
02. IsNumeric('-1.5')    => true
03. IsNumeric('0')       => true
04. IsNumeric('0.42')    => true
05. IsNumeric('.42')     => true
06. IsNumeric('99,999')  => false
07. IsNumeric('0x89f')   => false
08. IsNumeric('#abcdef') => false
09. IsNumeric('1.2.3')   => false
10. IsNumeric('')        => false
11. IsNumeric('blah')    => false

2148


origen


Respuestas:


@ La respuesta de Joel está bastante cerca, pero fallará en los siguientes casos:

// Whitespace strings:
IsNumeric(' ')    == true;
IsNumeric('\t\t') == true;
IsNumeric('\n\r') == true;

// Number literals:
IsNumeric(-1)  == false;
IsNumeric(0)   == false;
IsNumeric(1.1) == false;
IsNumeric(8e5) == false;

Hace algún tiempo tuve que implementar un IsNumeric función, para averiguar si una variable contenía un valor numérico, independientemente de su tipo, podría ser un String que contiene un valor numérico (tuve que considerar también la notación exponencial, etc.), una Number objeto, prácticamente cualquier cosa podría pasarse a esa función, no pude hacer ninguna suposición de tipo, ocupándome de la coerción de tipo (ej. +true == 1; pero true no debe ser considerado como "numeric")

Creo que vale la pena compartir este conjunto de +30 pruebas unitarias hecho para numerosas implementaciones de funciones, y también comparto el que pasa todas mis pruebas:

function isNumeric(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
}

PD  isNaN & isFinite tener un comportamiento confuso debido a la conversión forzada al número. En ES6, Number.isNaN & Number.isFinite solucionaría estos problemas Tenlo en cuenta cuando los uses.


Actualizar : Así es como jQuery lo hace ahora (2.2-estable):

isNumeric: function(obj) {
    var realStringObj = obj && obj.toString();
    return !jQuery.isArray(obj) && (realStringObj - parseFloat(realStringObj) + 1) >= 0;
}

Actualizar : Angular 4.3:

export function isNumeric(value: any): boolean {
    return !isNaN(value - parseFloat(value));
}

2756



Arrrgh! No escuche las respuestas de expresiones regulares. RegEx es asqueroso por esto, y no estoy hablando solo de rendimiento. Es tan fácil cometer errores sutiles e imposibles de detectar con su expresión regular.

Si no puedes usar isNaN(), esto debería funcionar mucho mejor:

function IsNumeric(input)
{
    return (input - 0) == input && (''+input).trim().length > 0;
}

Así es como funciona:

los (input - 0) expresión fuerza a JavaScript para que haga una coerción en el valor de entrada; primero debe interpretarse como un número para la operación de resta. Si esa conversión a un número falla, la expresión dará como resultado NaN. Esta numérico el resultado se compara con el valor original que ingresó. Dado que el lado izquierdo ahora es numérico, se vuelve a utilizar la coerción de tipo. Ahora que la entrada de ambos lados fue forzada al mismo tipo desde el mismo valor original, uno pensaría que siempre deberían ser iguales (siempre verdaderos). Sin embargo, hay una regla especial que dice NaN nunca es igual a NaN, por lo que un valor que no se puede convertir en un número (y solo valores que no se pueden convertir en números) dará como resultado un valor falso.

El control de la longitud es para un caso especial que involucra cadenas vacías. También tenga en cuenta que se cae en su prueba 0x89f, pero eso se debe a que en muchos entornos esa es una buena manera de definir un número literal. Si desea ver ese escenario específico, puede agregar un cheque adicional. Aún mejor, si esa es su razón para no usar isNaN() luego solo envuelve tu propia función isNaN() eso también puede hacer el control adicional.

En resumen, si desea saber si un valor se puede convertir a un número, intente convertirlo a un número.


Volví e hice algunas investigaciones para por qué una cadena de espacio en blanco no tenía el resultado esperado, y creo que ahora lo entiendo: una cadena vacía se fuerza a 0 más bien que NaN. Simplemente arreglando la cuerda antes de la verificación de longitud manejará este caso.

Ejecutar las pruebas unitarias contra el nuevo código y solo falla en los literales infinito y booleano, y el único momento que debería ser un problema es si está generando código (realmente, ¿quién escribiría un literal y verificaría si es numérico? Debieras saber), y ese sería un código extraño para generar.

Pero otra vez, la única razón para usar esto es si por alguna razón debe evitar isNaN ().


314



De esta manera parece funcionar bien:

function IsNumeric(input){
    var RE = /^-{0,1}\d*\.{0,1}\d+$/;
    return (RE.test(input));
}

Y para probarlo:

// alert(TestIsNumeric());

function TestIsNumeric(){
    var results = ''
    results += (IsNumeric('-1')?"Pass":"Fail") + ": IsNumeric('-1') => true\n";
    results += (IsNumeric('-1.5')?"Pass":"Fail") + ": IsNumeric('-1.5') => true\n";
    results += (IsNumeric('0')?"Pass":"Fail") + ": IsNumeric('0') => true\n";
    results += (IsNumeric('0.42')?"Pass":"Fail") + ": IsNumeric('0.42') => true\n";
    results += (IsNumeric('.42')?"Pass":"Fail") + ": IsNumeric('.42') => true\n";
    results += (!IsNumeric('99,999')?"Pass":"Fail") + ": IsNumeric('99,999') => false\n";
    results += (!IsNumeric('0x89f')?"Pass":"Fail") + ": IsNumeric('0x89f') => false\n";
    results += (!IsNumeric('#abcdef')?"Pass":"Fail") + ": IsNumeric('#abcdef') => false\n";
    results += (!IsNumeric('1.2.3')?"Pass":"Fail") + ": IsNumeric('1.2.3') => false\n";
    results += (!IsNumeric('')?"Pass":"Fail") + ": IsNumeric('') => false\n";
    results += (!IsNumeric('blah')?"Pass":"Fail") + ": IsNumeric('blah') => false\n";

    return results;
}

Tomé prestada esa expresión regular de http://www.codetoad.com/javascript/isnumeric.asp. Explicación:

/^ match beginning of string
-{0,1} optional negative sign
\d* optional digits
\.{0,1} optional decimal point
\d+ at least one digit
$/ match end of string

58



Yahoo! UI usa esto:

isNumber: function(o) {
    return typeof o === 'number' && isFinite(o);
}

45



function IsNumeric(num) {
     return (num >=0 || num < 0);
}

Esto también funciona para números de tipo 0x23.


44



La respuesta aceptada no pasó su prueba # 7 y supongo que es porque cambiaste de idea. Entonces esta es una respuesta a la respuesta aceptada, con la que tuve problemas.

Durante algunos proyectos, he necesitado validar algunos datos y ser lo más seguro posible de que es un valor numérico de JavaScript que se puede usar en operaciones matemáticas.

jQuery, y algunas otras bibliotecas de JavaScript ya incluyen dicha función, generalmente llamada isNumeric. También hay una publicar en stackoverflow eso ha sido ampliamente aceptado como la respuesta, la misma rutina general que están utilizando las bibliotecas antes mencionadas.

function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

Primero, el código anterior devolvería verdadero si el argumento era una matriz de longitud 1, y ese único elemento era de un tipo considerado como numérico por la lógica anterior. En mi opinión, si se trata de una matriz, entonces no es numérica.

Para aliviar este problema, agregué un cheque para descontar matrices de la lógica

function isNumber(n) {
  return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n);
}

Por supuesto, también podrías usar Array.isArray, jquery $.isArray o prototipo Object.isArray en lugar de Object.prototype.toString.call(n) !== '[object Array]'

Mi segundo problema fue que las cadenas literales de entero hexadecimal negativo ("-0xA" -> -10) no se contaban como numéricas. Sin embargo, las cadenas literales de entero hexadecimal positivo ("0xA" -> 10) se trataron como numéricas. Necesitaba que ambos fueran numéricos válidos.

Luego modifiqué la lógica para tener esto en cuenta.

function isNumber(n) {
  return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}

Si le preocupa la creación de la expresión regular cada vez que se llama a la función, entonces puede volver a escribirla dentro de un cierre, algo como esto

var isNumber = (function () {
  var rx = /^-/;

  return function (n) {
      return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(rx, ''));
  };
}());

Luego tomé CMSs +30 casos de prueba y clonado el probando en jsfiddle agregué mis casos de prueba adicionales y mi solución descrita anteriormente.

Puede que no reemplace la respuesta ampliamente aceptada / utilizada, pero si esto es más de lo que espera como resultado de su función isNumeric, con suerte, esto será de alguna ayuda.

EDITAR: Como se señala por Bergi, hay otros objetos posibles que podrían considerarse numéricos y sería mejor incluir en la lista blanca que en la lista negra. Con esto en mente, agregaría a los criterios.

Quiero que mi función isNumeric considere solo números o cadenas

Con esto en mente, sería mejor usar

function isNumber(n) {
  return (Object.prototype.toString.call(n) === '[object Number]' || Object.prototype.toString.call(n) === '[object String]') &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}

Prueba las soluciones

var testHelper = function() {

  var testSuite = function() {
    test("Integer Literals", function() {
      ok(isNumber("-10"), "Negative integer string");
      ok(isNumber("0"), "Zero string");
      ok(isNumber("5"), "Positive integer string");
      ok(isNumber(-16), "Negative integer number");
      ok(isNumber(0), "Zero integer number");
      ok(isNumber(32), "Positive integer number");
      ok(isNumber("040"), "Octal integer literal string");
      ok(isNumber(0144), "Octal integer literal");
      ok(isNumber("-040"), "Negative Octal integer literal string");
      ok(isNumber(-0144), "Negative Octal integer literal");
      ok(isNumber("0xFF"), "Hexadecimal integer literal string");
      ok(isNumber(0xFFF), "Hexadecimal integer literal");
      ok(isNumber("-0xFF"), "Negative Hexadecimal integer literal string");
      ok(isNumber(-0xFFF), "Negative Hexadecimal integer literal");
    });

    test("Foating-Point Literals", function() {
      ok(isNumber("-1.6"), "Negative floating point string");
      ok(isNumber("4.536"), "Positive floating point string");
      ok(isNumber(-2.6), "Negative floating point number");
      ok(isNumber(3.1415), "Positive floating point number");
      ok(isNumber(8e5), "Exponential notation");
      ok(isNumber("123e-2"), "Exponential notation string");
    });

    test("Non-Numeric values", function() {
      equals(isNumber(""), false, "Empty string");
      equals(isNumber("        "), false, "Whitespace characters string");
      equals(isNumber("\t\t"), false, "Tab characters string");
      equals(isNumber("abcdefghijklm1234567890"), false, "Alphanumeric character string");
      equals(isNumber("xabcdefx"), false, "Non-numeric character string");
      equals(isNumber(true), false, "Boolean true literal");
      equals(isNumber(false), false, "Boolean false literal");
      equals(isNumber("bcfed5.2"), false, "Number with preceding non-numeric characters");
      equals(isNumber("7.2acdgs"), false, "Number with trailling non-numeric characters");
      equals(isNumber(undefined), false, "Undefined value");
      equals(isNumber(null), false, "Null value");
      equals(isNumber(NaN), false, "NaN value");
      equals(isNumber(Infinity), false, "Infinity primitive");
      equals(isNumber(Number.POSITIVE_INFINITY), false, "Positive Infinity");
      equals(isNumber(Number.NEGATIVE_INFINITY), false, "Negative Infinity");
      equals(isNumber(new Date(2009, 1, 1)), false, "Date object");
      equals(isNumber(new Object()), false, "Empty object");
      equals(isNumber(function() {}), false, "Instance of a function");
      equals(isNumber([]), false, "Empty Array");
      equals(isNumber(["-10"]), false, "Array Negative integer string");
      equals(isNumber(["0"]), false, "Array Zero string");
      equals(isNumber(["5"]), false, "Array Positive integer string");
      equals(isNumber([-16]), false, "Array Negative integer number");
      equals(isNumber([0]), false, "Array Zero integer number");
      equals(isNumber([32]), false, "Array Positive integer number");
      equals(isNumber(["040"]), false, "Array Octal integer literal string");
      equals(isNumber([0144]), false, "Array Octal integer literal");
      equals(isNumber(["-040"]), false, "Array Negative Octal integer literal string");
      equals(isNumber([-0144]), false, "Array Negative Octal integer literal");
      equals(isNumber(["0xFF"]), false, "Array Hexadecimal integer literal string");
      equals(isNumber([0xFFF]), false, "Array Hexadecimal integer literal");
      equals(isNumber(["-0xFF"]), false, "Array Negative Hexadecimal integer literal string");
      equals(isNumber([-0xFFF]), false, "Array Negative Hexadecimal integer literal");
      equals(isNumber([1, 2]), false, "Array with more than 1 Positive interger number");
      equals(isNumber([-1, -2]), false, "Array with more than 1 Negative interger number");
    });
  }

  var functionsToTest = [

    function(n) {
      return !isNaN(parseFloat(n)) && isFinite(n);
    },

    function(n) {
      return !isNaN(n) && !isNaN(parseFloat(n));
    },

    function(n) {
      return !isNaN((n));
    },

    function(n) {
      return !isNaN(parseFloat(n));
    },

    function(n) {
      return typeof(n) != "boolean" && !isNaN(n);
    },

    function(n) {
      return parseFloat(n) === Number(n);
    },

    function(n) {
      return parseInt(n) === Number(n);
    },

    function(n) {
      return !isNaN(Number(String(n)));
    },

    function(n) {
      return !isNaN(+('' + n));
    },

    function(n) {
      return (+n) == n;
    },

    function(n) {
      return n && /^-?\d+(\.\d+)?$/.test(n + '');
    },

    function(n) {
      return isFinite(Number(String(n)));
    },

    function(n) {
      return isFinite(String(n));
    },

    function(n) {
      return !isNaN(n) && !isNaN(parseFloat(n)) && isFinite(n);
    },

    function(n) {
      return parseFloat(n) == n;
    },

    function(n) {
      return (n - 0) == n && n.length > 0;
    },

    function(n) {
      return typeof n === 'number' && isFinite(n);
    },

    function(n) {
      return !Array.isArray(n) && !isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
    }

  ];


  // Examines the functionsToTest array, extracts the return statement of each function
  // and fills the toTest select element.
  var fillToTestSelect = function() {
    for (var i = 0; i < functionsToTest.length; i++) {
      var f = functionsToTest[i].toString();
      var option = /[\s\S]*return ([\s\S]*);/.exec(f)[1];
      $("#toTest").append('<option value="' + i + '">' + (i + 1) + '. ' + option + '</option>');
    }
  }

  var performTest = function(functionNumber) {
    reset(); // Reset previous test
    $("#tests").html(""); //Clean test results
    isNumber = functionsToTest[functionNumber]; // Override the isNumber global function with the one to test
    testSuite(); // Run the test

    // Get test results
    var totalFail = 0;
    var totalPass = 0;
    $("b.fail").each(function() {
      totalFail += Number($(this).html());
    });
    $("b.pass").each(function() {
      totalPass += Number($(this).html());
    });
    $("#testresult").html(totalFail + " of " + (totalFail + totalPass) + " test failed.");

    $("#banner").attr("class", "").addClass(totalFail > 0 ? "fail" : "pass");
  }

  return {
    performTest: performTest,
    fillToTestSelect: fillToTestSelect,
    testSuite: testSuite
  };
}();


$(document).ready(function() {
  testHelper.fillToTestSelect();
  testHelper.performTest(0);

  $("#toTest").change(function() {
    testHelper.performTest($(this).children(":selected").val());
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="https://rawgit.com/Xotic750/testrunner-old/master/testrunner.js" type="text/javascript"></script>
<link href="https://rawgit.com/Xotic750/testrunner-old/master/testrunner.css" rel="stylesheet" type="text/css">
<h1>isNumber Test Cases</h1>

<h2 id="banner" class="pass"></h2>

<h2 id="userAgent">Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11</h2>

<div id="currentFunction"></div>

<div id="selectFunction">
  <label for="toTest" style="font-weight:bold; font-size:Large;">Select function to test:</label>
  <select id="toTest" name="toTest">
  </select>
</div>

<div id="testCode"></div>

<ol id="tests">
  <li class="pass">
    <strong>Integer Literals <b style="color:black;">(0, 10, 10)</b></strong>

    <ol style="display: none;">
      <li class="pass">Negative integer string</li>

      <li class="pass">Zero string</li>

      <li class="pass">Positive integer string</li>

      <li class="pass">Negative integer number</li>

      <li class="pass">Zero integer number</li>

      <li class="pass">Positive integer number</li>

      <li class="pass">Octal integer literal string</li>

      <li class="pass">Octal integer literal</li>

      <li class="pass">Hexadecimal integer literal string</li>

      <li class="pass">Hexadecimal integer literal</li>
    </ol>
  </li>

  <li class="pass">
    <strong>Foating-Point Literals <b style="color:black;">(0, 6, 6)</b></strong>

    <ol style="display: none;">
      <li class="pass">Negative floating point string</li>

      <li class="pass">Positive floating point string</li>

      <li class="pass">Negative floating point number</li>

      <li class="pass">Positive floating point number</li>

      <li class="pass">Exponential notation</li>

      <li class="pass">Exponential notation string</li>
    </ol>
  </li>

  <li class="pass">
    <strong>Non-Numeric values <b style="color:black;">(0, 18, 18)</b></strong>

    <ol style="display: none;">
      <li class="pass">Empty string: false</li>

      <li class="pass">Whitespace characters string: false</li>

      <li class="pass">Tab characters string: false</li>

      <li class="pass">Alphanumeric character string: false</li>

      <li class="pass">Non-numeric character string: false</li>

      <li class="pass">Boolean true literal: false</li>

      <li class="pass">Boolean false literal: false</li>

      <li class="pass">Number with preceding non-numeric characters: false</li>

      <li class="pass">Number with trailling non-numeric characters: false</li>

      <li class="pass">Undefined value: false</li>

      <li class="pass">Null value: false</li>

      <li class="pass">NaN value: false</li>

      <li class="pass">Infinity primitive: false</li>

      <li class="pass">Positive Infinity: false</li>

      <li class="pass">Negative Infinity: false</li>

      <li class="pass">Date object: false</li>

      <li class="pass">Empty object: false</li>

      <li class="pass">Instance of a function: false</li>
    </ol>
  </li>
</ol>

<div id="main">
  This page contains tests for a set of isNumber functions. To see them, take a look at the source.
</div>

<div>
  <p class="result">Tests completed in 0 milliseconds.
    <br>0 tests of 0 failed.</p>
</div>


37



Sí, el built-in isNaN(object) será mucho más rápido que cualquier análisis regex, porque está integrado y compilado, en lugar de interpretarse sobre la marcha.

Aunque los resultados son algo diferentes de lo que estás buscando (intentalo)

                                              // IS NUMERIC
document.write(!isNaN('-1') + "<br />");      // true
document.write(!isNaN('-1.5') + "<br />");    // true
document.write(!isNaN('0') + "<br />");       // true
document.write(!isNaN('0.42') + "<br />");    // true
document.write(!isNaN('.42') + "<br />");     // true
document.write(!isNaN('99,999') + "<br />");  // false
document.write(!isNaN('0x89f') + "<br />");   // true
document.write(!isNaN('#abcdef') + "<br />"); // false
document.write(!isNaN('1.2.3') + "<br />");   // false
document.write(!isNaN('') + "<br />");        // true
document.write(!isNaN('blah') + "<br />");    // false

30