In het moderne JavaScript zijn er twee soorten getallen:
-
Reguliere getallen in JavaScript worden opgeslagen in het 64-bits formaat IEEE-754, ook wel bekend als “double precision floating point numbers”. Dit zijn de getallen die we het meest gebruiken, en we zullen het er in dit hoofdstuk over hebben.
-
BigInt getallen, om gehele getallen van willekeurige lengte weer te geven. Ze zijn soms nodig, omdat een gewoon getal niet groter kan zijn dan
253
of kleiner dan-253
. Omdat bigints op enkele speciale gebieden worden gebruikt, wijden we ze aan een speciaal hoofdstuk BigInt.
Dus hier gaan we het hebben over reguliere getallen. Laten we onze kennis ervan uitbreiden.
Meer manieren om een getal te schrijven
Stel je voor dat we 1 miljard moeten schrijven. De voor de hand liggende manier is:
let billion = 1000000000;
We kunnen ook underscore _
als scheidingsteken gebruiken:
let billion = 1_000_000_000;
Hier speelt de underscore_
de rol van de “syntactische suiker”, het maakt het getal leesbaarder. De JavaScript engine negeert gewoon_
tussen de cijfers, dus het is precies hetzelfde miljard als hierboven.
In het echte leven proberen we echter te vermijden lange reeksen nullen te schrijven. Daar zijn we te lui voor. We zullen proberen iets te schrijven als "1bn"
voor een miljard of "7.3bn"
voor 7 miljard 300 miljoen. Hetzelfde geldt voor de meeste grote getallen.
In JavaScript kunnen we een getal inkorten door er de letter "e"
aan toe te voegen en het aantal nullen op te geven:
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroesalert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)
In andere woorden,e
vermenigvuldigt het getal met1
met het gegeven aantal nullen.
1e3 = 1 * 1000 // e3 means *10001.23e6 = 1.23 * 1000000 // e6 means *1000000
Nu gaan we iets heel kleins schrijven. Zeg, 1 microseconde (een miljoenste van een seconde):
let ms = 0.000001;
Net als eerder kan het gebruik van "e"
helpen. Als we de nullen niet expliciet willen schrijven, kunnen we hetzelfde zeggen als:
let ms = 1e-6; // six zeroes to the left from 1
Als we de nullen tellen in 0.000001
, dan zijn dat er 6. Dus is het natuurlijk 1e-6
.
Met andere woorden, een negatief getal na "e"
betekent een deling door 1 met het gegeven aantal nullen:
// -3 divides by 1 with 3 zeroes1e-3 = 1 / 1000 (=0.001)// -6 divides by 1 with 6 zeroes1.23e-6 = 1.23 / 1000000 (=0.00000123)
Hex, binaire en octale getallen
Hexadecimale getallen worden in JavaScript veel gebruikt om kleuren weer te geven, karakters te coderen, en voor vele andere dingen. Er bestaat dus een kortere manier om ze te schrijven: 0x
en dan het getal.
Bijvoorbeeld:
alert( 0xff ); // 255alert( 0xFF ); // 255 (the same, case doesn't matter)
Binaire en octale getallenstelsels worden zelden gebruikt, maar worden ook ondersteund met behulp van de 0b
en 0o
prefixen:
let a; // binary form of 255let b = 0o377; // octal form of 255alert( a == b ); // true, the same number 255 at both sides
Er zijn slechts 3 cijfersystemen met dergelijke ondersteuning. Voor andere getallenstelsels moeten we de functie parseInt
gebruiken (die we later in dit hoofdstuk zullen zien).
toString(base)
De methode num.toString(base)
geeft een string-weergave van num
in het getallenstelsel met de gegeven base
.
Voorbeeld:
let num = 255;alert( num.toString(16) ); // ffalert( num.toString(2) ); // 11111111
De base
kan variëren van 2
tot 36
. Standaard is dit 10
.
Common use cases hiervoor zijn:
-
base=16 wordt gebruikt voor hex-kleuren, tekencoderingen etc, cijfers kunnen
0..9
ofA..F
zijn. -
base=2 is vooral bedoeld voor het debuggen van bitwise-bewerkingen, cijfers kunnen zijn
0
of1
. -
basis=36 is het maximum, cijfers kunnen
0..9
ofA..Z
zijn. Het hele latijnse alfabet wordt gebruikt om een getal weer te geven. Een grappig, maar nuttig geval voor36
is wanneer we een lange numerieke identificator in iets korter moeten veranderen, bijvoorbeeld om een korte url te maken. We kunnen het eenvoudigweg in het numerieke systeem weergeven met basis36
:alert( 123456..toString(36) ); // 2n9c
Twee puntjes om een methode aan te roepenLet op dat twee puntjes in
123456..toString(36)
geen typfout is. Als we een methode direct op een getal willen aanroepen, zoalstoString
in het voorbeeld hierboven, dan moeten we er twee puntjes..
achter zetten.Als we een enkel puntje hadden geplaatst:
123456.toString(36)
, dan zou er een fout zijn, omdat de syntaxis van JavaScript het decimale deel na de eerste punt impliceert. En als we nog een punt plaatsen, dan weet JavaScript dat het decimale deel leeg is en gaat nu de methode.Ook zou je
(123456).toString(36)
kunnen schrijven.Afronding
Eén van de meest gebruikte bewerkingen bij het werken met getallen is afronding.
Er zijn verschillende ingebouwde functies voor afronding:
Math.floor
Afronding naar beneden:3.1
wordt3
, en-1.1
wordt-2
Math.ceil
Rondt af naar boven:3.1
wordt4
, en-1.1
wordt-1
Math.round
Rondt af naar het dichtstbijzijnde gehele getal:3.1
wordt3
3.6
wordt4
, het middelste geval:3.5
rondt ook af naar boven tot4
Math.trunc
(niet ondersteund door Internet Explorer) Verwijdert alles achter de komma zonder afronding:3.1
wordt3
-1.1
wordt-1
.Hier is de tabel om de verschillen tussen hen samen te vatten:
Math.floor
Math.ceil
Math.round
Math.trunc
3.1
3
4
3
3
3.6
3
4
4
3
-1.1
-2
-1
-1
-1
-1.6
-2
-1
-2
-1
Deze functies behandelen alle mogelijke manieren om met het decimale deel van een getal om te gaan. Maar wat als we het getal willen afronden op
n-th
cijfer na de decimaal?We hebben bijvoorbeeld
1.2345
en willen het afronden op 2 cijfers, waardoor we alleen1.23
krijgen.Er zijn twee manieren om dat te doen:
-
Multiply-and-divide.
Om het getal bijvoorbeeld af te ronden op het 2e cijfer achter de komma, kunnen we het getal vermenigvuldigen met
100
(of een grotere macht van 10), de afrondingsfunctie aanroepen en dan terug delen.let num = 1.23456;alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
-
De methode toFixed(n) rondt het getal af op
n
cijfers na de punt en geeft een string-weergave van het resultaat.let num = 12.34;alert( num.toFixed(1) ); // "12.3"
-
Dit rondt naar boven of beneden af op de dichtstbijzijnde waarde, vergelijkbaar met Math.round
:
let num = 12.36;alert( num.toFixed(1) ); // "12.4"
Let op dat het resultaat van toFixed
een string is. Als het decimale deel korter is dan vereist, worden er nullen aan het eind toegevoegd:
let num = 12.34;alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits
We kunnen het omzetten in een getal met behulp van de unary plus of een Number()
oproep: +num.toFixed(5)
.
Onnauwkeurige berekeningen
Intern wordt een getal weergegeven in het 64-bits formaat IEEE-754, dus er zijn precies 64 bits om een getal op te slaan: 52 daarvan worden gebruikt om de cijfers op te slaan, 11 daarvan slaan de positie van de decimale punt op (ze zijn nul voor gehele getallen), en 1 bit is voor het teken.
Als een getal te groot is, zou het de 64-bits opslagruimte overlopen, wat mogelijk een oneindigheid oplevert:
alert( 1e500 ); // Infinity
Wat misschien iets minder voor de hand ligt, maar wel vaak voorkomt, is het verlies van precisie.
Bedenk deze (vals!) test:
alert( 0.1 + 0.2 == 0.3 ); // false
Dat is juist, als we controleren of de som van 0.1
en 0.2
0.3
is, krijgen we false
.
Schrikbarend! Wat is het dan als het niet 0.3
is?
alert( 0.1 + 0.2 ); // 0.30000000000000004
Ouch! Er zijn hier meer gevolgen dan een onjuiste vergelijking. Stel je voor dat je een e-shopping site maakt en de bezoeker legt $0.10
en $0.20
goederen in zijn winkelwagen. Het totaal van de bestelling zal $0.30000000000000004
zijn. Dat zou niemand verbazen.
Maar waarom gebeurt dit?
Een getal wordt in het geheugen opgeslagen in zijn binaire vorm, een opeenvolging van bits – enen en nullen. Maar breuken zoals 0.1
0.2
die er in het decimale getallensysteem eenvoudig uitzien, zijn in feite oneindige breuken in hun binaire vorm.
Met andere woorden, wat is 0.1
? Het is één gedeeld door tien 1/10
, een tiende. In het decimale getallenstelsel zijn dergelijke getallen gemakkelijk weer te geven. Vergelijk het met eenderde: 1/3
. Het wordt een eindeloze breuk 0.33333(3)
.
Deling door machten 10
werkt dus gegarandeerd goed in het decimale stelsel, maar deling door 3
werkt dat niet. Om dezelfde reden werkt in het binaire getallenstelsel deling door machten van 2
gegarandeerd, maar 1/10
wordt een eindeloze binaire breuk.
Er is gewoon geen manier om precies 0.1 of precies 0.2 op te slaan met het binaire systeem, net zoals er geen manier is om een derde op te slaan als een decimale breuk.
Het numerieke formaat IEEE-754 lost dit op door af te ronden naar het dichtstbijzijnde mogelijke getal. Door deze afrondingsregels zien we dat “kleine precisieverlies” normaal gesproken niet, maar het bestaat wel.
We kunnen dit in actie zien:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
En als we twee getallen optellen, tellen hun “precisieverliezen” bij elkaar op.
Daarom is 0.1 + 0.2
niet precies 0.3
.
Hetzelfde probleem bestaat in veel andere programmeertalen.
PHP, Java, C, Perl, Ruby geven precies hetzelfde resultaat, omdat ze op hetzelfde numerieke formaat zijn gebaseerd.
Kunnen we het probleem omzeilen? Zeker, de meest betrouwbare methode is om het resultaat af te ronden met behulp van een methode toFixed(n):
let sum = 0.1 + 0.2;alert( sum.toFixed(2) ); // 0.30
Merk op dat toFixed
altijd een string teruggeeft. Het zorgt ervoor dat het 2 cijfers achter de komma heeft. Dat is eigenlijk handig als we een e-shopping hebben en $0.30
moeten tonen. Voor andere gevallen kunnen we de unary plus gebruiken om er een getal van te maken:
let sum = 0.1 + 0.2;alert( +sum.toFixed(2) ); // 0.3
We kunnen de getallen ook tijdelijk met 100 (of een groter getal) vermenigvuldigen om er gehele getallen van te maken, de wiskunde doen, en dan terug delen. Omdat we dan met gehele getallen rekenen, neemt de fout iets af, maar we krijgen hem nog steeds bij het delen:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
Dus, de vermenigvuldigen/vermenigvuldigen aanpak vermindert de fout, maar neemt hem niet helemaal weg.
Soms kunnen we proberen om breuken helemaal te vermijden. Als we bijvoorbeeld te maken hebben met een winkel, dan kunnen we de prijzen in centen opslaan in plaats van in dollars. Maar wat als we een korting van 30% toepassen? In de praktijk is het zelden mogelijk om breuken volledig te omzeilen. Rond ze gewoon af om “staartjes” af te snijden als dat nodig is.
Probeer dit eens uit te voeren:
// Hello! I'm a self-increasing number!alert( 9999999999999999 ); // shows 10000000000000000
Dit heeft met hetzelfde probleem te maken: een verlies aan precisie. Er zijn 64 bits voor het getal, 52 daarvan kunnen worden gebruikt om cijfers op te slaan, maar dat is niet genoeg. Dus verdwijnen de minst significante cijfers.
JavaScript geeft geen foutmelding in zulke gevallen. Het doet zijn best om het getal in het gewenste formaat te passen, maar helaas is dit formaat niet groot genoeg.
Een ander grappig gevolg van de interne representatie van getallen is het bestaan van twee nullen: 0
en -0
.
Dat komt omdat een teken wordt gerepresenteerd door een enkele bit, dus het kan worden gezet of niet gezet voor elk getal inclusief een nul.
In de meeste gevallen is het onderscheid niet merkbaar, omdat operatoren geschikt zijn om ze als hetzelfde te behandelen.
Tests: isFinite en isNaN
Herken je deze twee speciale numerieke waarden nog?
-
Infinity
(en-Infinity
) is een speciale numerieke waarde die groter (kleiner) is dan iets. -
NaN
staat voor een fout.
Ze behoren tot het type number
, maar zijn geen “normale” getallen, dus zijn er speciale functies om ze te controleren:
-
isNaN(value)
converteert zijn argument naar een getal en test dan of hetNaN
is:alert( isNaN(NaN) ); // truealert( isNaN("str") ); // true
Maar hebben we deze functie nodig? Kunnen we niet gewoon de vergelijking
=== NaN
gebruiken? Sorry, maar het antwoord is nee. De waardeNaN
is uniek in die zin dat het aan niets gelijk is, ook niet aan zichzelf:alert( NaN === NaN ); // false
isFinite(value)
converteert het argument naar een getal en geefttrue
terug als het een gewoon getal is, nietNaN/Infinity/-Infinity
:alert( isFinite("15") ); // truealert( isFinite("str") ); // false, because a special value: NaNalert( isFinite(Infinity) ); // false, because a special value: Infinity
Soms wordt
isFinite
gebruikt om te valideren of een string waarde een regulier getal is:let num = +prompt("Enter a number", '');// will be true unless you enter Infinity, -Infinity or not a numberalert( isFinite(num) );
Let op dat een lege of een spatie-string wordt behandeld als
0
in alle numerieke functies inclusiefisFinite
.Vergelijken metObject.is
Er is een speciale ingebouwde methode
Object.is
die waarden vergelijkt zoals===
, maar die betrouwbaarder is voor twee randgevallen:- Het werkt met
NaN
Object.is(NaN, NaN) === true
, dat is een goede zaak. - Waarden
0
en-0
zijn verschillend:Object.is(0, -0) === false
, technisch is dat waar, want intern heeft het getal een tekenbit dat verschillend kan zijn, zelfs als alle andere bits nullen zijn.
In alle andere gevallen is
Object.is(a, b)
hetzelfde alsa === b
.Deze manier van vergelijken wordt vaak gebruikt in JavaScript specificatie. Wanneer een intern algoritme twee waarden moet vergelijken om te zien of ze precies hetzelfde zijn, gebruikt het
Object.is
(intern SameValue genoemd).parseInt en parseFloat
Numerieke conversie met behulp van een plus
+
ofNumber()
is strikt. Als een waarde niet precies een getal is, mislukt het:alert( +"100px" ); // NaN
De enige uitzondering zijn spaties aan het begin of aan het eind van de string, die worden genegeerd.
Maar in het echte leven hebben we vaak waarden in eenheden, zoals
"100px"
of"12pt"
in CSS. Ook komt in veel landen het valutasymbool achter het bedrag, dus we hebben"19€"
en willen daar een numerieke waarde uit halen.Daar zijn
parseInt
enparseFloat
voor.Ze “lezen” een getal uit een string tot ze niet meer kunnen. In geval van een fout wordt het verzamelde getal geretourneerd. De functie
parseInt
retourneert een geheel getal, terwijlparseFloat
een floating-point getal zal retourneren:alert( parseInt('100px') ); // 100alert( parseFloat('12.5em') ); // 12.5alert( parseInt('12.3') ); // 12, only the integer part is returnedalert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading
Er zijn situaties waarin
parseInt/parseFloat
NaN
zal teruggeven. Dit gebeurt wanneer er geen cijfers konden worden gelezen:alert( parseInt('a123') ); // NaN, the first symbol stops the process
Het tweede argument vanparseInt(str, radix)
DeparseInt()
functie heeft een optionele tweede parameter. Deze specificeert de basis van het getallenstelsel, zodatparseInt
ook reeksen hex-getallen, binaire getallen enzovoort kan ontleden:alert( parseInt('0xff', 16) ); // 255alert( parseInt('ff', 16) ); // 255, without 0x also worksalert( parseInt('2n9c', 36) ); // 123456
Andere wiskundige functies
JavaScript heeft een ingebouwd Math object dat een kleine bibliotheek van wiskundige functies en constanten bevat.
Een paar voorbeelden:
Math.random()
Retourneert een willekeurig getal van 0 tot 1 (1 niet meegerekend).
alert( Math.random() ); // 0.1234567894322alert( Math.random() ); // 0.5435252343232alert( Math.random() ); // ... (any random numbers)
Math.max(a, b, c...)
Math.min(a, b, c...)
Krijgt de grootste/kleinste uit het willekeurig aantal argumenten.
alert( Math.max(3, 5, -10, 0, 1) ); // 5alert( Math.min(1, 2) ); // 1
Math.pow(n, power)
Returns
n
verheven tot de gegeven macht.alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
Er zijn meer functies en constanten in het
Math
object, inclusief trigonometrie, die je kunt vinden in de docs voor het Math object.Samenvatting
Om getallen met veel nullen te schrijven:
- Voeg
"e"
met de nullen tellen toe aan het getal. Zoals:123e6
is hetzelfde als123
met 6 nullen123000000
. - Een negatief getal na
"e"
zorgt ervoor dat het getal gedeeld wordt door 1 met gegeven nullen. Bijv.123e-6
betekent0.000123
123
miljoensten).
Voor verschillende getallenstelsels:
- Kan getallen direct schrijven in hex (
0x
), octaal (0o
) en binair (0b
) stelsel. -
parseInt(str, base)
parseert de stringstr
in een geheel getal in getallenstelsel met gegevenbase
2 ≤ base ≤ 36
. -
num.toString(base)
converteert een getal naar een string in het getallenstelsel met de gegevenbase
.
Voor het omzetten van waarden als
12pt
en100px
naar een getal:- Gebruik
parseInt/parseFloat
voor de “zachte” conversie, die een getal uit een tekenreeks leest en vervolgens de waarde teruggeeft die ze vóór de fout konden lezen.
Voor breuken:
- Rond af met
Math.floor
Math.ceil
Math.trunc
Math.round
ofnum.toFixed(precision)
. - Houd er rekening mee dat er precisieverlies optreedt als je met breuken werkt.
Meer wiskundige functies:
- Zie het Math-object als je ze nodig hebt. De bibliotheek is erg klein, maar kan in de basisbehoeften voorzien.