josdejong / mathjs

An extensive math library for JavaScript and Node.js
https://mathjs.org
Apache License 2.0
14.44k stars 1.24k forks source link

Incorrect Values for cos(90) and tan(90) #755

Closed abhikhatri closed 6 years ago

abhikhatri commented 8 years ago

Hey there,

I recently started working with mathjs and I seriously love this library. It has everything anyone can ask for but recently I came across an issue that is bothering me. Whenever I run math.eval('cos(90 deg)') it gives me 6.123233995736766e-17 instead of 0 and same issue with tan. Whenever I run math.eval('tan(90 deg)') it gives me 16331239353195370 instead of an error.

Is there way to fix that cause I am not sure If I am doing it wrong or it's actually throwing incorrect answers?

FSMaxB commented 8 years ago

This is because of round off errors, see #447.

abhikhatri commented 8 years ago

Thanks 😄

FSMaxB commented 8 years ago

You're welcome.

abhikhatri commented 8 years ago

I followed the referenced thread but it's throwing an error: Unexpected type of argument in function cos (expected: number or Array or Matrix, actual: BigNumber, index: 0)

when I add math.config({number: 'BigNumber'});.

FSMaxB commented 8 years ago

I cannot reproduce that:

math.config({number: 'BigNumber'});
var bignumber = math.parse('cos(90 deg)').eval();
bignumber.toString(); //"1.539082031431044993140174126710585339910740432566411533235469223e-64"

Although it is still not 0. But keep in mind that math.js is not an algebraic but a numeric library.

josdejong commented 8 years ago

@abhikhatri are you using the latest version of mathjs?

abhikhatri commented 8 years ago

@josdejong I updated it today after I saw this bug. @FSMaxB I am using this example to switch modes and apparently this was causing that exception. when I commented out math.import(replacements, {override: true}); it was working fine.

Is there any workaround to get the correct values of sin, cos and tan?

josdejong commented 8 years ago

@abhikhatri the example you link to does indeed only work for numbers, not for BigNumbers. We could probably extend the example. You can also work it out yourself for BigNumbers if needed.

Is there any workaround to get the correct values of sin, cos and tan?

This is a fundamental issue of numerical applications working with floating point numbers. The only real solution is using an algebra system instead (which does symbolic computation). The docs on numbers and BigNumbers give you some more explanation and some tips on how to deal with this:

http://mathjs.org/docs/datatypes/numbers.html#roundoff-errors http://mathjs.org/docs/datatypes/bignumbers.html#limitations

abhikhatri commented 8 years ago

The angle configuration example saved me!!!! 😃 Instead of bashing with the core library I messed with that example and finally got it working.

var trigPrecision = 1000000000;

switch (config.angles) {
        case 'deg':
          var angleDivisor = 180 / Math.PI;
          var eq = fn(x / angleDivisor);
          return Math.round(eq * trigPrecision) / trigPrecision;
        case 'grad':
          return fn(x / 400 * 2 * Math.PI);
        default:
          return fn(x);
      }

I've changed convert to degree method and added this simple code snippet to fix rounding error. It's working fine for me now 💃

To change the precision simply change the number of 0s in trigPrecision

harrysarson commented 8 years ago

When the cosine of a unit is calculated could there not be a check to see if it was exactly 90deg, 100grad, etc and if so return zero?

josdejong commented 8 years ago

@abhikhatri good to hear. For the rounding you could also use math.round(eq, numberOfDecimals).

@HarrySarson that's indeed a solution: create a set of special cases for which you return an exact answer. Another solution is to reduce the input to the range of [-pi, pi), since trigonometric functions get more inaccurate the further away you are from zero.

abhikhatri commented 8 years ago

@josdejong Thanks 😄

ThomasBrierley commented 8 years ago

Just for laughs :smiley:

I came across this issue before. Determined to find a better solution than rounding I came up with this ridiculous contrived method for sin with positive values. You should be able to adapt it for the other trig functions if you are crazy enough, might need branching for negatives:

codecogseqn

The idea is to exploit all of the symmetry in the sine function, one period of a sine wave has 4 identical sections which are mirrored in some way. The sin function can correctly calculate the intersection at zero and PI / 2 but not higher so the first quarter is good.

The first part of the expression folds all of the quarters into the first one... this is fine for the first half period which have the same output reflected, but the second half looses it's sign. So the second part of the expression multiplies by 1 or -1 depending on x.

f(x) = sin((PI / 2) - abs((x % PI) - (PI / 2))) * (1 - 2 * floor((x / PI) % 2))
f(PI * 0) // 0
f(PI * 0.5) // 1
f(PI * 1) // 0
f(PI * 1.5) // -1
f(PI * 2) // 0

You might notice that if you use radians then it's still reliant on the input being calculated with the same precision of PI that is being used to fold the function up. Instead if you make the boundaries into rational numbers by using degrees then it works perfectly (replacing PI with 180):

f(x) = sin(unit(90 - abs((x % 180) - 90), "deg") * (1 - 2 * floor((x / 180) % 2))
f(0) // 0
f(90) // 1
f(180) // 0
f(270) // -1
f(360) // 0
josdejong commented 7 years ago

@ThomasBrierley yes I meant something like that with reducing the input range though I wasn't aware of this formula :)