4

In a simple geometric program written in Javascript and Canvas, when I set the angle to 270° (1½π), I expected the Math.cos(θ) to go to zero. The vector is straight down from the center, there's no x distance on a cartesian grid. Instead, I get this:

demo_angle = 270
ang = demo_angle * Math.PI / 180
x = Math.cos(ang) 
console.log(x)
> -1.836909530733566e-16

To see the output of the math functions, view the console. The source code is visible (in coffeescript) one level up in the URL.

I've had to define in my code "Any number whose absolute value is smaller than 1e-15 should be considered zero," but that's really unsatisfying. Needless to say, when I try doing math with the x value that small, especially since I'm trying to use x as the denominator in a slope calculation and then doing some quadratic manipulations, I eventually come up with figures that exceed Number.MAX_VALUE (or Number.MIN_VALUE).

I know floating point mathematics is, at the assembly language level, something of a dark art, but results like this just seem weirder than is acceptable. Any hints on what I should do?

Elf Sternberg
  • 16,129
  • 6
  • 60
  • 68
  • As someone currently in a university pre-calc class, I find this strangely interesting. – Michael Jasper Sep 26 '11 at 21:45
  • Obviously, the same problem exists at 90 degrees (1/2 pi), too. – Elf Sternberg Sep 26 '11 at 21:48
  • 1
    possible duplicate of [Javascript Math.cos and Math.sin are inaccurate. Is there any solution?](http://stackoverflow.com/questions/6223616/javascript-math-cos-and-math-sin-are-inaccurate-is-there-any-solution) – Jordan Running Sep 26 '11 at 21:52
  • Try to use `270.0` and `180.0`. Maybe you loose precision...? – Cipi Sep 26 '11 at 21:53
  • @Cipi: Nah, I tried that already, in every concrete value. Didn't change anything. – Elf Sternberg Sep 26 '11 at 21:58
  • @ElfSternberg Last time I checked PI is an infinite decimal number and can therefore obviously not be represented by a value of the Number type in JavaScript (or any other language). So `Math.PI` is merely as close as you can get... – Šime Vidas Sep 26 '11 at 22:03
  • @Jordan: Yeah, it's close to a duplicate. I note that the one you linked to is also marked as "potential duplicate," of another. That said, none offer the `Number.toFixed()` function as a "good enough" corrective. @Stephen_Canon's response and my own discovery of `Number.toFixed()` should be left as an offered solution. – Elf Sternberg Sep 27 '11 at 15:47

2 Answers2

9

The problem is not that "zero isn't exactly zero". On the contrary, zero is exactly zero.

The issue that you're encountering is that 3π/2 is not representable as a floating point number. So you're actually taking the cosine of a value that is not quite equal to 3π/2. How big is this representation error? About 1.8e-16, which is the source of the error you see in the cosine.

Some languages get around this problem by providing functions like sinpi and cospi that implicitly scale their arguments by a factor of π; that's one way to deliver exact results. Obviously, that's not an option for you because javascript doesn't have such functions. You could roll your own if you want, taking advantage of the symmetries of these functions, or you can simply clamp "nearly zero" values to zero, as you are now. Neither is particularly satisfactory, but both will probably work for your purposes.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
  • Thanks. I've used the javascript `Number.toFixed(15)` to handle the problem. You're right, it's just a clamp, and it's not satisfactory, but it'll work for the purpose of drawing pretty clock faces, which is all I'm trying do at the moment. – Elf Sternberg Sep 26 '11 at 22:03
3

The problem is that Math.PI isn't exactly equal to Pi, but is instead the number of the form m*2^e with 2^52 <= m < 2^53 closest to it.

Then multiplying by 270 introduces a small round-off error.

Then dividing by 180 causes some more round-off error.

So your ang value is not exactly equal to 3*Pi/2, and as a result, what you get back is not the 0 you expect.

The calculation itself is actually done very accurately.

Jeffrey Sax
  • 10,253
  • 3
  • 29
  • 40