mathjs: Why does 1cm + 1mm give rounding error, but 1mm + 1cm does not?

When using eval with units, first unit in string is set as the “ground” unit for the calculation. For example:

const m = require('mathjs')
m.eval('1cm + 1mm').toString()  // '1.0999999999999999 cm'
m.eval('1mm + 1cm').toString()  // '11 mm'

The result is a funny difference in the output format. Of course one could force output format:

m.eval('1cm + 1mm').format(2)  // '1.1 cm'
m.eval('1mm + 1cm').format(2)  // '11 mm'

I guess this is because first calculation is done with decimal (1 mm as 0.1 cm, which is not precise in binary) and second calculation in integers. As I see it, a sensible heuristic could be:

  1. “Sort” inputs by SI-prefix, smallest SI-prefix first.
  2. Convert all inputs to this SI-prefix.
  3. Do calculation.

Does this heuristic make sense for mathjs, or should the user of mathjs implement similar heuristics himself?

BTW, see that Math Notepad does what I expected: image

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 23 (7 by maintainers)

Most upvoted comments

Ok here we go 😃

math.eval('1 cm')        // a unit created holding 0.01 m
math.eval('1 mm')        // a unit created holding 0.001 m
math.eval('1 cm + 1 mm') // a unit created holding 0.011 m

So far no rounding errors.

When calling .toString(), the unit is transformed back to either mm or cm. The heuristics of the current implementation in mathjs will select the prefix of the first unit of the two added units.

math.eval('1 cm + 1 mm').toString() 
    // convert 0.011 m to cm -> 0.011 * 100 = '1.0999999999999999 cm'

math.eval('1 mm + 1 cm').toString() 
    // convert 0.011 m to mm -> 0.011 * 1000 = '11 mm'

So in this case 0.011 * 100 introduces a floating point rounding error, whilst 0.011 * 1000 doesn’t.

All good, my bad. Did not aply the fix in all my code. Works now!