在Javascript中验证十进制数-IsNumeric()

2561

在JavaScript中,验证十进制数的最干净、最有效的方法是什么?

额外加分项:

  1. 清晰易懂。解决方案应简洁明了。
  2. 跨平台。

测试用例:

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

272
请注意,在法国,99,999是一个有效的数字,它与英式/美式格式中的99.999是相同的。因此,如果您从输入表单中读取字符串,则99,999可能是真实的。请注意,这是一条翻译,不包括任何额外的解释或内容。 - Re0sless
5
请查看这篇文章和精彩评论 - powtac
82
十进制逗号是整个欧洲和俄罗斯(英国除外)的标准。 - Calmarius
93
jQuery 1.7 引入了 jQuery.isNumeric 工具函数:http://api.jquery.com/jQuery.isNumeric/。 - Ates Goral
26
jQuery.isNumeric 无法通过 OP 的第七个测试用例 (IsNumeric('0x89f') => *false*)。然而,我不确定是否同意这个测试用例。 - Tim Lehner
显示剩余5条评论
53个回答

2995

@Joel的回答非常接近,但在以下情况下会失败:

// 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;

有一段时间,我需要实现一个 `IsNumeric` 函数,以便判断变量是否包含数值,而且不考虑其类型,可能是一个包含数值的字符串(我还需要考虑指数记数法等),也可能是一个 `Number` 对象,可以传递任何类型的参数给该函数,我不能做出任何类型的假设,注意类型强制转换(例如 `+true == 1;` 但 `true` 不应被视为“数字”)。
我认为值得分享这个基于多种函数实现的+30个单元测试的集合,并分享通过我所有测试的那个函数:
```html +30 unit tests ```
function isNumeric(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
}
P.S. 由于强制转换为数字,isNaNisFinite 存在令人困惑的行为。在ES6中,Number.isNaNNumber.isFinite 可以解决这些问题。在使用它们时请谨慎考虑。
更新:这是最新的 jQuery(2.2-stable)如何处理的方式
isNumeric: function(obj) {
    var realStringObj = obj && obj.toString();
    return !jQuery.isArray(obj) && (realStringObj - parseFloat(realStringObj) + 1) >= 0;
}

更新:Angular 4.3

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

jsBench 表明,angulars 的 isNumeric 比 isNaN && isFinite 函数快了 0.51%。有人能确认一下吗?我不能在未注册的情况下分享我的测试... - aProgger
2
isNumeric([1]) 应该返回 false,但是 !isNaN(parseFloat(n)) && isFinite(n); 却返回了 true。在我看来,isNumeric 不应该隐式地解析字符串,而应该要求一个数字。可选的解析参数可以通过尝试解析字符串来放宽这个限制。 - Timothy C. Quinn

356

啊!不要听正则表达式的答案。对于这个问题,正则表达式不仅性能差,而且很容易出现微妙的错误,难以发现。

如果你不能使用isNaN() — 记住:我说的是“如果” — 那么这个方法应该更好:

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

这是它的工作原理:

(input - 0) 表达式强制 JavaScript 对您的输入值进行类型转换;它必须首先被解释为一个数字,以进行减法运算。如果该转换失败,则表达式将导致 NaN(不是数字)。然后将此数值结果与您传递的原始值进行比较。由于左侧现在是数字,因此再次使用类型强制转换。现在,来自两侧的输入已从相同的原始值强制转换为相同类型,您会认为它们应该始终相同(始终为真)。但是,有一条特殊规则,即 NaN 永远不等于 NaN,因此无法转换为数字的值(仅限无法转换为数字的值)将导致 false。

长度检查是针对某些特殊情况下的空字符串。还要注意,在你的0x89f测试中失败了,但是因为在JavaScript中这是一种定义数字字面量的有效方式。 如果你想要捕捉该特定场景,可以添加额外的检查。更好的方法是,如果这是你不使用isNaN()的原因,那么就在isNaN()周围包装自己的函数,可以进行额外的检查。

总之,如果想知道一个值是否能转换为数字,实际上应该尝试将其转换为数字。


我回去做了一些研究,想知道为什么一个空格字符串没有预期的输出,现在我明白了:一个空字符串被强制转换为0而不是NaN。只需在长度检查之前修剪字符串即可处理此情况。

对新代码运行单元测试,它只会在无限和布尔文字上失败,唯一可能出现问题的时候是如果你正在生成代码(真的,谁会输入一个文字并检查它是否为数字?你应该知道),那将是一些奇怪的代码生成。

但是,再次强调,唯一使用这个的原因是如果你必须避免isNaN()。


在TypeScript中无法工作:TS2362:算术操作的左侧必须是“any”、“number”、“bigint”或枚举类型之一。 - Sebastian Barth
在Typescript中,可以使用+input进行强制转换。 - Sebastian Barth
@SebastianBarth 这不是为 TypeScript 编写的,我相信 TypeScript 在这方面提供了更好的开箱即用工具。 - Joel Coehoorn
IsNumeric([1])返回true。 - trusktr

85

这种方式似乎效果很好:

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

在一行中:
const IsNumeric = (num) => /^-{0,1}\d*\.{0,1}\d+$/.test(num);

并测试它:

const IsNumeric = (num) => /^-{0,1}\d*\.{0,1}\d+$/.test(num);
    
    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;
    }

console.log(TestIsNumeric());
.as-console-wrapper { max-height: 100% !important; top: 0; }

我从http://www.codetoad.com/javascript/isnumeric.asp借用了这个正则表达式。解释:

/^ 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使用这个:

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

55

接受的答案未通过您的第七个测试,我猜测这是因为您改变了主意。因此,这是对被接受的答案的回应,我对它有异议。

在一些项目中,我需要验证一些数据,并尽可能确信它是可以用于数学运算的javascript数字值。

jQuery和其他一些javascript库已经包含了这样一个函数,通常称为isNumeric。还有一个在stackoverflow上的帖子已被广泛接受为答案,这与前面提到的库使用的一般例程相同。

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

首先,上面的代码会在参数是长度为1且该单个元素被视为数字类型时返回true。在我看来,如果它是一个数组,那么它就不是数字。

为了解决这个问题,我添加了一个检查来排除数组逻辑。

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

当然,你也可以使用 Array.isArray、jquery 的$.isArray或原型的Object.isArray 而不是 Object.prototype.toString.call(n) !== '[object Array]'
我的第二个问题是负十六进制整数文字字符串(“-0xA” -> -10)没有被视为数字。然而,正十六进制整数文字字符串(“0xA” -> 10)被视为数字。我需要两者都是有效的数字。
然后我修改了逻辑以考虑这一点。
function isNumber(n) {
  return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}

如果您担心每次调用函数时都会创建正则表达式,那么可以在闭包内重写它,就像这样:
var isNumber = (function () {
  var rx = /^-/;
   
  return function (n) {
      return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(rx, ''));
  };
}());

我接着拿了CMS的+30个测试用例,克隆了在jsfiddle上进行测试,并添加了额外的测试用例和上述解决方案。
这可能无法取代被广泛接受/使用的答案,但如果这更符合您从isNumeric函数中期望的结果,那么希望这会有所帮助。 编辑:正如Bergi所指出的,还有其他可能被视为数字的对象,最好是白名单而不是黑名单。考虑到这一点,我会添加以下标准。
我希望我的isNumeric函数仅考虑数字或字符串。
考虑到这一点,最好使用
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(/^-/, ''));
}

测试解决方案

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>


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

这对于0x23类型的数字同样适用。


但对于空字符串也将评估为“true”。 - Sebastian Barth

37

是的,内置的isNaN(object)比任何正则表达式解析都要快得多,因为它是内置且已编译,而不是即时解释。

尽管结果与您想要的有些不同(试一试):

                                              // 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

22

使用函数isNaN。我认为如果您测试!isNaN(yourstringhere),它适用于任何这些情况。


19
自从jQuery 1.7版本以后,你可以使用jQuery.isNumeric()方法来判断是否为数字:
$.isNumeric('-1');      // true
$.isNumeric('-1.5');    // true
$.isNumeric('0');       // true
$.isNumeric('0.42');    // true
$.isNumeric('.42');     // true
$.isNumeric('0x89f');   // true (valid hexa number)
$.isNumeric('99,999');  // false
$.isNumeric('#abcdef'); // false
$.isNumeric('1.2.3');   // false
$.isNumeric('');        // false
$.isNumeric('blah');    // false

请注意,与您所说的不同,0x89f是一个有效的数字(十六进制)


14

可以不使用正则表达式来完成。

function IsNumeric(data){
    return parseFloat(data)==data;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接