Use cases for decimals in JavaScript

Here, I'd like to sketch out some of the use cas­es that have been dis­cussed for adding dec­i­mals to JavaScript.

Just to set up some of the ter­mi­nol­o­gy to avoid a po­ten­tial mis­un­der­stand­ing: when we say adding dec­i­mals to JavaScript we of course are re­fer­ring to some­thing new in the lan­guage. In a sense, dec­i­mals al­ready ex­ist in JavaScript as num­bers, and are ex­pressed as lit­er­als in the lan­gauge like so: 1.234, -42.9876, and so on. Syntat­i­cal­ly, one could call those dec­i­mals, but it would be more ac­cu­rate to say that those lit­er­als work out to bi­na­ry float­ing-point num­bers, which typ­i­cal­ly work out to 32-bit or 64-bit num­bers at the CPU lev­el (de­pend­ing on the un­der­ly­ing ma­chine ar­chi­tec­ture). When we talk about dec­i­mals here, we're not talk­ing about bi­na­ry float­ing-point num­bers as they cur­rent­ly ex­ist in JS. We mean some­thing new.

In fact, there are two dis­tinct data mod­els un­der dis­cus­sion: 128-bit float­ing-point num­bers (or Dec­i­mal128, for short), or ar­bi­trary-pre­ci­sion dec­i­mals (BigDec­i­mal, for lack of a bet­ter term). In the first case, dec­i­mals would re­tain their float­ing-point na­ture but would be (1) ex­act, and (2) get a lot more pre­ci­sion than you find even with 64-bit bi­na­ry floar­ing-point num­bers. For an anal­o­gy, think of how go­ing from IPv4 (32-bit IP ad­dress­es) to IPv6 (64-bit IP ad­dress­es) does vast­ly more than dou­ble the size of the ad­dress­able part of the In­ter­net. Of course, the to­tal ad­dress space af­ford­ed by IPv6 is lim­it­ed, so one could say that IPv6 still is­n't enough, but from where I'm sit­ting, to­day, IPv6 of­fers a lot of breath­ing room for growth.

Also, just to clar­i­fy: There is al­ready sup­port for dec­i­mals in var­i­ous li­braries. So when we say adding sup­port, we mean more than just "em­bed­ding} one of these li­braries into the lan­guage. What we mean is ex­tend­ing JavaScript so that, with­out any third-par­ty li­braries, sup­port for dec­i­mals is al­ready added.

Mon­ey

A nat­ur­al use case for dec­i­mals that ac­tu­al­ly work is mon­ey. There are two needs here: ex­act­ness, and sup­port for high-pre­ci­sion (that is, prop­er sup­port for more than 2 or four dig­its af­ter the dec­i­mal point).

Com­put­ing tax­es is an­oth­er case where, even with sim­ple ex­am­ples, one quick­ly gets into trou­ble. One typ­i­cal­ly needs to sum a bunch of dec­i­mal num­bers (the items of an or­der), mul­ti­ple the sum by a num­ber (0.05, 0.07, 0.19, you name it), and get a grand to­tal.

Ex­act­ness

We have al­ready seen how 0.1 + 0.2 is­n't 0.3. When work­ing with mon­ey, this should be a strong clue that we need to do some­thing to get thigns to work out as we need to. Some­how it feels wrong to re­sort to fall­backs like round­ing, or work­ing with num­bers as dig­it strings (!) rather than ac­tu­al num­bers. We want to work with num­bers as num­bers, where less-than and equal­i­ty work as we ex­pect them to work in arith­metic.

This is­sue shows that the is­sue of ex­act­ness is in­de­pen­dent of the idea of sup­port for high-pre­ci­sion. In the ex­am­ples above—of which count­less sim­i­lar ex­am­ples can be gen­er­at­ed—there is just one dec­i­mal af­ter the dig­it there!

High-pre­ci­sion

In most cur­ren­cies, there's a base unit (say, a dol­lar) that is typ­i­cal­ly (though not al­ways!) di­vid­ed into sub­units (cents). The sub­units are of­ten de­fined in terms of dec­i­mals (a cent are, by de­f­i­n­i­tion, 1/100 of a dol­lar). Some cal­cu­la­tions are done with 3 or even 4 "sub-units} (think: cents), but prob­a­bly not (much) more than that.

But that's "clas­si­cal} mon­ey. In many cryp­tocur­ren­cies, a sin­gle unit is di­vid­ed into mil­lions of sub­unut, pos­si­bly even bil­lions or tril­lions (or even more!). Here, we ac­tu­al­ly start brush­ing up against the bare pos­si­bil­i­ty of even rep­re­sent­ing such fine di­vi­sions of a unit as a num­ber at all. And even if these num­bers could be rep­re­sent­ed ex­act­ly as float­ing-point num­bers, we are like­ly ask­ing for trou­ble when we do arith­metic with them, even more so than with "low-dig­it" ex­am­ples like 0.1, 0.2, and so on.

Med­i­cine and sci­ence

Think of all the data that gets record­ed around the world. In air­planes, in weath­er sta­tions, in satel­lites. The list is end­less. And sure­ly some of these mea­sure­ments are done with high-pre­ci­sion dec­i­mal num­bers. Float­ing-point num­bers might well be good, but high-pre­ci­sion (or ar­bi­trary-pre­ci­sion) dec­i­mals might be een bet­ter. Think of bioin­for­mat­ics or med­ical sys­tems.

Unit con­ver­sions

Con­vert­ing be­tween dif­fer­ent units is of­ten a way where lot of dec­i­mal places oc­cur quite nat­u­ral­ly.

Han­dling data from oth­er sys­tems

Imag­ine in­gest­ing data from a data­base that works with high-pre­ci­sion or ar­rbi­trary pre­ci­sion dec­i­mals. If you im­port those kinds of num­bers into JavaScript, us­ing floats, you may well be ask­ing for trou­ble by ac­ci­den­tal­ly round­ing your re­sults into a more coarse-grained data. In some ap­pli­ca­tions, maybe that round­ing does­n't mat­ter much. But if the orig­i­nal DB was set up with high-pre­ci­sion or ar­bi­trary-pre­ci­sion dec­i­mals, pre­sum­ably that lev­el of pre­ci­sion re­flects a real need that your JS pro­gram can't re­spect. (Not with­out reach­ing for a third-par­ty li­brary to do the work.)

As an ex­am­ple, SQL data­bas­es