Blog om agile metoder, udvikling og ledelse


geek, javascript

Detaljer i dybet

Morten | 24.02.2020

For et par år siden fik jeg en fejlmelding på et projekt, om at vores web-app regnede forkert. Da vi kiggede lidt mere på det viste det sig, at vi havde ramt nogle af de steder, hvor javascript ikke regner helt som din matematiklærer lærte dig det. Et kendt eksempel er

0.1 + 0.2
> 0.30000000000000004

altså, at 0.1 og 0.2 lagt sammen giver lige en smule mere end 0.3. Det handler selvfølgelig om afrunding, og langt ude på 16. decimal, så til hverdag ikke så stort et problem. Men selvfølgelig, hvis man så regner videre kan det jo blive spændende:

let jackpot = (input) => (input - 3/10) > 0 ? 1e6 : 0;
> jackpot(0.3)
0
> jackpot(0.1+0.2)
1000000

Den, der har scoret 0.3 får 0 kroner i belønning, mens den, der scorede 0.1+0.2 får en million — og det var den slags, vores web-app ramte.

Man koder løs i god tillid til sine værktøjer, men hov — den regner da forkert?!

I den konkrete anvendelse blev det så yderligere kompliceret af, at vores udgangspunkt var nogle langhårede modeller bygget i Excel, og forventningen var, at vi skulle ramme det samme resultat som Excel. Og surprise: Excel regner heller ikke (matematisk) korrekt med kommatal, i hvert fald ikke hvis man bare bruger normale kommatal (som 0.1 og 0.2). Excel bruger lidt flere betydende cifre, men er ramt af samme fundamentale problem om binær repræsentation af kommatal.

Snakken dukkede op igen, da vi kom til at snakke om en anvendelse, hvor man skal regne på pengebeløb på en webside — for hvis man bare drøner der ud af med 10 øre her og 20 øre der, så har man snart 30.000000000000004 øre stående på skærmen, hvis man ikke passer på. Eller 1 million i jackpot...

Nu er der måske en ny datatype på vej til javascript, som kan regne korrekt med kommatal

Kort efter pengesnakken blev jeg gjort opmærksom på et forslag til en kommende udvidelse af Javascript med en ny type til at håndtere kommatal (mere) korrekt.

Forslaget er dejligt fuld af detaljer, der gør det klart, hvorfor det her er svært ikke er trivielt. "Selvfølgelig" skal den regne rigtigt. Og "selvfølgelig" skal man kunne regne derudaf med de sædvanlige regnearter, og "selvfølgelig" skal man bare kunne skrive a/b med et almindeligt binært divisionstegn, når man lige vil dividere — men ups: noget eller nogen skal tage stilling til, hvordan vi skal afrunde.

Og så havner vi jo nærmest samme sted som java...

BigDecimal vatAmount = totalExVat
    .multiply(BigDecimal.valueOf(vatPercent))
    .divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP)
    .setScale(2, RoundingMode.HALF_UP);