Filter math oddity

Greetings all,

I am trying to improve my simple invoice system made with TiddlyWiki and came across this mathematical oddity and am trying to determine if this is just one of those “javascript math precision” oddities or a bug in the filter code. These are two examples of a filtered transclusion to get a bunch of billable entries associated with an invoice number, multiply some billable hours by some rate, then add them all up and calculate the tax using a <<tax_rate>> variable:

$<$text text={{{[tag[billable_entry]!has[draft.of]field:invoice_no{!!invoice_no}] :reduce[get[hours]multiply{!!rate}multiply<tax_rate>fixed[2]add<accumulator>]}}}/>

Sometimes gives a wonky number that is more than 2 decimal places … not what I would expect from adding a bunch of numbers with 2 decimal places.

$<$text text={{{[tag[billable_entry]!has[draft.of]field:invoice_no{!!invoice_no}] :reduce[get[hours]multiply{!!rate}multiply<tax_rate>add<accumulator>fixed[2]]}}}/>

Moving fixed to the end fixes the non-rounding but this one sometimes (for a tax_rate=0.1, for instance) gives a number off by a cent due to accumulated numeric cruft after the second decimal place that then gets rounded.

$<$text text={{{[tag[billable_entry]!has[draft.of]field:invoice_no{!!invoice_no}] :reduce[get[hours]multiply{!!rate}add<accumulator>] +[multiply<tax_rate>fixed[2]]}}}/>

It shouldn’t matter if I multiply each one by the tax rate and then add them up or add them up and then multiply the subtotal by the tax rate. However, This last one gives what I would expect the answer to be.

I’ve kind of solved my problem by using the last example but I was just throwing it out there for folks more intimately knowledgeable about how javascript handles precision to see if this was expected behaviour or something faulty going on inside the reduce run.

Thanks,
/Mike

In all three examples no parameters are passed to the add operator. I think it’s supposed to be add<accumulator> to add it to the previous result?

Actually … those should be add<accumulator> but because I didn’t put quotes around it, the <accumulator> part got eaten by the forum parser. :grinning: Fixed now.

Hi @mwiktowy I think you’re running into the peculiarities of floating point notation, which is the format that TiddlyWiki and JavaScript use to represent numbers.

The issue is that neat decimals don’t necessarily correspond to a neat representation in binary. This can lead to odd results – for example try 1.1*100 in a JavaScript console and you’ll get 110.00000000000001.

There’s a reasonable seeming article with the background here: JavaScript Quirks: Numbers Edition - DEV Community

You can avoid most of the rounding problems, if you start your numbers with * 1000. Make your calculations, divide the result by 1000 and cut the result down to 2 decimals.

So you should get precise results.

1 Like

Thanks @jeremyruston . I have read that (or similar) articles but was surprised that is showed up when I didn’t have that many significant digits. I’ll double check the dynamic range of my input numbers … maybe some garbage is not being ignored.

It is possible that the last solution that worked will fall down in other corner-cases so I will implement the conversion to dealing in integer amount of milli-dollars and then truncate to cents like @pmario helpfully suggested.

Thanks,
/Mike

IMO the best way to go is to stay with the full range, big numbers until you have the sum and then make divisions and percentage calculations and as the last step truncate. …

The problem here is that it really is truncating and not rounding.

Yes. Rounding is pretty problematic.

{{{=[[4525]] =[[1.13]] +[product[]]}}}

gives 5113.249999999999 instead of 5113.25

and adding a +[round[]] will round down instead of up.

Not only that, if you use more than 3 decimal place precision in your multiplier, * 1000 won’t be enough to avoid all the strange corner cases.

FWIW, in the end, I am using:

{{{[tag[billable_entry]!has[draft.of]field:invoice_no{!!invoice_no}] :reduce[{!!hours}multiply{!!rate}add<accumulator>] +[multiply[10000000000]] =[[1]add<tax_rate>] +[product[]] +[round[]] +[divide[100000000]] +[round[]] +[divide[100]] +[fixed[2]]}}}

in order to handle about 10 significant digits and using fixed[] to just zero pad. That should give me enough dynamic range.

Thanks for your help,
/Mike

But be aware of this: Number.MAX_SAFE_INTEGER - JavaScript | MDN So your sum can’t be higher than about 900000

1 Like