validering - Valider desimaltall i JavaScript - IsNumeric()
Hva er den reneste og mest effektive måten å validere desimaltall på i JavaScript?
Bonuspoeng for:
- Klarhet. Løsningen bør være ren og enkel.
- Tverrplattform.
Testtilfeller:
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
Svar #1
For en tid siden måtte jeg implementere en IsNumeric-funksjon for å finne ut om en variabel inneholdt en numerisk verdi, uavhengig av typen. Det kunne være en String som inneholdt en numerisk verdi (jeg måtte også ta hensyn til eksponentiell notasjon, osv.), et Number-objekt, praktisk talt hva som helst kunne sendes til den funksjonen. Jeg kunne ikke gjøre noen antagelser om typen, og måtte være oppmerksom på typekoersjon (f.eks. +true == 1; men true burde ikke anses som "numerisk").
Jeg synes det er verdt å dele dette settet med +30 enhetstester laget for mange funksjonsimplementasjoner, og også dele den som består alle mine tester:
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
Merk: Dette returnerer true for alle numeriske verdier, ikke bare desimaltall (dvs. base ti) som spesifisert i spørsmålet. Spesielt regnes '0x89f' som numerisk.
P.S. isNaN & isFinite har en forvirrende oppførsel på grunn av tvungen konvertering til tall. I ES6 vil Number.isNaN & Number.isFinite løse disse problemene. Husk det når du bruker dem.
Oppdatering: Slik gjør jQuery det nå (2.2-stabil):
isNumeric: function(obj) {
var realStringObj = obj && obj.toString();
return !jQuery.isArray(obj) && (realStringObj - parseFloat(realStringObj) + 1) >= 0;
}
Oppdatering : Angular 4.3:
export function isNumeric(value: any): boolean {
return !isNaN(value - parseFloat(value));
}
Svar #2
Arrrgh! Ikke lytt til svarene med regulære uttrykk. RegEx er klønete for dette, og jeg snakker ikke bare om ytelse. Det er så lett å lage subtile, umulige å oppdage feil med regulæruttrykket ditt.
Hvis du ikke kan bruke isNaN() — og husk: Jeg sa «HVIS» — bør dette fungere mye bedre:
function IsNumeric(input)
{
return (input - 0) == input && (''+input).trim().length > 0;
}
Slik fungerer det:
Uttrykket (input - 0) tvinger JavaScript til å utføre typeomforming på verdien din; den må først tolkes som et tall for subtraksjonsoperasjonen. Hvis den konverteringen til et tall mislykkes, vil uttrykket resultere i NaN (Not a Number). Dette numeriske resultatet blir deretter sammenlignet med den opprinnelige verdien du sendte inn. Siden venstresiden nå er numerisk, brukes typeomforming igjen. Nå som input fra begge sider ble omformet til samme type fra samme opprinnelige verdi, skulle man tro at de alltid burde være like (alltid sant). Men det finnes en spesiell regel som sier at NaN aldri er lik NaN, og derfor vil en verdi som ikke kan konverteres til et tall (og bare verdier som ikke kan konverteres til tall) resultere i false.
Kontrollen av lengden er for et spesialtilfelle som involverer tomme strenger. Merk også at den feiler på testen din 0x89f, men det er fordi for JavaScript det er en gyldig måte å definere et tallliteral på. Hvis du vil fange det spesifikke scenariet, kan du legge til en ekstra sjekk. Enda bedre, hvis det er grunnen til at du ikke bruker isNaN(), kan du bare lage en funksjon rundt isNaN() som også kan utføre den ekstra sjekken.
Oppsummert, hvis du vil vite om en verdi kan konverteres til et tall, prøv faktisk å konvertere den til et tall.
Jeg gikk tilbake og gjorde litt research for hvorfor en whitespace-streng ikke hadde forventet output, og jeg tror jeg forstår det nå: en tom streng blir tvunget til 0 i stedet for NaN. Å bare trimme strengen før lengdesjekken vil håndtere dette tilfellet.
Kjører enhetstestene mot den nye koden, og den feiler bare på uendelighet og boolske literaler, og den eneste gangen det burde være et problem er hvis du genererer kode (virkelig, hvem ville skrevet inn en literal og sjekket om det er numerisk? Du burde vite), og det ville være en merkelig kode å generere.
Men, igjen, den eneste grunnen til å bruke dette noen gang er hvis du av en eller annen grunn må unngå isNaN().
Svar #3
Denne metoden ser ut til å fungere bra:
function IsNumeric(input){
var RE = /^-{0,1}\d*\.{0,1}\d+$/;
return (RE.test(input));
}
I én linje:
const IsNumeric = (num) => /^-{0,1}\d*\.{0,1}\d+$/.test(num);
Og for å teste det:
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; }Jeg lånte den regexen fra http://www.codetoad.com/javascript/isnumeric.asp. Forklaring:
/^ 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
Svar #4
Yahoo! UI bruker dette:
isNumber: function(o) {
return typeof o === 'number' && isFinite(o);
}
Svar #5
Det aksepterte svaret feilet din test #7, og jeg antar det er fordi du ombestemte deg. Så dette er et svar på det aksepterte svaret, som jeg hadde problemer med.
Under noen prosjekter har jeg trengt å validere enkelte data og være så sikker som mulig på at det er en javascript numerisk verdi som kan brukes i matematiske operasjoner.
jQuery og noen andre JavaScript-biblioteker inkluderer allerede en slik funksjon, vanligvis kalt isNumeric. Det finnes også et innlegg på StackOverflow som har blitt bredt akseptert som svaret, den samme generelle rutinen som de nevnte bibliotekene bruker.
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
Først vil koden ovenfor returnere true hvis argumentet var et array av lengde 1, og det eneste elementet var av en type som anses som numerisk etter logikken ovenfor. Etter min mening, hvis det er et array, er det ikke numerisk.
For å lindre dette problemet, la jeg til en sjekk for å utelate matriser fra logikken
function isNumber(n) {
return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n);
}
Selvfølgelig kan du også bruke Array.isArray, jQuery $.isArray eller prototype Object.isArray i stedet for Object.prototype.toString.call(n) !== '[object Array]'
Mitt andre problem var at negative heksadesimale heltallslitteraler ("-0xA" -> -10) ikke ble regnet som numeriske. Positive heksadesimale heltallslitteraler ("0xA" -> 10) ble derimot behandlet som numeriske. Jeg trengte at begge skulle være gyldige numeriske.
Jeg endret deretter logikken for å ta dette i betraktning.
function isNumber(n) {
return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}
Hvis du er bekymret for opprettelsen av regexen hver gang funksjonen kalles, kan du omskrive den innenfor en closure, noe sånt som dette
var isNumber = (function () {
var rx = /^-/;
return function (n) {
return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(rx, ''));
};
}());
Jeg tok deretter CMSs +30 testtilfeller og klonet testingen på jsfiddle og la til mine ekstra testtilfeller og min ovenfor beskrevne løsning.
Det kan hende det ikke erstatter det allment aksepterte/brukt svaret, men hvis dette er mer av det du forventer som resultat fra din isNumeric-funksjon, håper jeg dette vil være til hjelp.
REDIGER: Som påpekt av Bergi, finnes det andre mulige objekter som kan anses som numeriske, og det ville være bedre å lage en hviteliste enn en svarteliste. Med dette i tankene, vil jeg legge til kriteriene.
Jeg vil at min isNumeric-funksjon kun skal vurdere tall eller strenger
Med dette i tankene, ville det være bedre å bruke
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(/^-/, ''));
}
Test løsningene
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>Svar #6
function IsNumeric(num) {
return (num >=0 || num < 0);
}
Dette fungerer også for 0x23-type tall.
Svar #7
Ja, den innebygde isNaN(object) vil være mye raskere enn enhver regex-parsing, fordi den er innebygd og kompilert, i stedet for å tolkes på farten.
Selv om resultatene er noe forskjellige fra det du leter etter (prøv det):
// 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
Svar #8
Bruk funksjonen isNaN. Jeg tror at hvis du tester med !isNaN(dinstedingher), fungerer det fint for noen av disse situasjonene.
Svar #9
Siden jQuery 1.7, kan du bruke 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
Bare merk at i motsetning til det du sa, er 0x89f et gyldig tall (heksadesimal)
Svar #10
Det kan gjøres uten RegExp som
function IsNumeric(data){
return parseFloat(data)==data;
}
