Skip to main content
Stack Overflow
  1. About
  2. For Teams

Return to Revisions

2 of 5
edited body
Steve Summit
  • 49.2k
  • 9
  • 80
  • 113

I can't say why we should do it like the way he wants...

That's too bad. It doesn't sound like someone is doing a very good job at teaching.

I'm not a numerical analyst, so I'm not sure I can teach you what's going on, either, but as it happens I have studied this example. Here's what I can tell you about it.

In general, one of the common mistakes people make when coding floating-point expressions is to type them in straight from the mathematical formulation. But floating-point arithmetic does not always obey the same rules as pure mathematics, so it's not at all uncommon for the naïve approach to run into trouble.

In this case your professor's expression

acc += (a / berechneFak(i)) * Math.pow(x, i);

is more or less straight from the mathematical definition of the Taylor series. But it's got two, related problems:

  1. It does a lot of unnecessary multiplication. At iteration i+1, it essentially recomputes berechneFak(i) and Math.pow(x, i) on the way to computing them for i+1.
  2. Terms like berechneFak(i) and Math.pow(x, i) can get very big, very fast. That's not a problem in pure mathematics, but the range and precision of computer floating point numbers are limited. If a term overflows, it can demolish your results. When you have something like x = y/z, where y and z are both very big, you may lose precision in x even though x is nice and small and theoretically perfectly representable.

Here, there's a great way to address both problems. If you've already computed the factorial berechneFak(i), then you can simply multiply it by i+1 to get berechneFak(i+1). If you've already computed Math.pow(x, i), then you can simply multiply it by x again to get Math.pow(x, i+1). And if you perform both operations on a single running quotient variable term, as you did, you minimize the magnitude of the numbers involved, which reduces the possibilities for both overflow and precision loss.

So, based on these arguments, your implementation should be quite a bit better than the one suggested by your instructor.

The only problem with the arguments I've presented here is that, in my experience, they don't end up making much difference in practice. The efficiency argument is probably real. But it ends up being hard to show that the hypothetical inaccuracies due to overflow and precision loss will actually occur.

Assuming you've reduced the range of the input x properly, x won't be large and so Math.pow(x, i) won't grow so fast. (In fact, if you reduce the range to [-π/4, π/4], it won't grow at all.) And when you're computing y/z, even when y and z are large, the properties of division and of IEEE-754 floating point mean that you usually get a good result — you don't lose so much precision — after all. Finally, as I mentioned in a comment, the Taylor series for sin and cos are so darn good that even a naïve implementation tends to give great results. (That's why, for the problem you chose, your implementation and the professor's gave practically identical results.)

In my experience, when it comes to sine and cosine, the only way to demonstrate that the "better" implementation really is better is to deliberately omit the range reduction step. For example, using the improved technique, in single precision, and computing the sine instead of the cosine, if you try to compute sin(14) — that is, 14 radians — after 20 iterations you'll get 0.9817389, which is somewhat close to the correct answer of 0.9879668. But the naïve approach gives 2.082662, which is not only completely wrong, it's obviously not a sine value at all.

(I'd like to present that worked-out example here, but as I mentioned it's for sine instead of cosine, and I've been investigating it using C, not Java.)

What's the bottom line? There are three or four take-home lessons, I think:

(1) In general, beware of typing in mathematical expressions straight from the definition.

(2) In general, well-chosen rearrangements, which are theoretically mathematically equivalent but which work around the limitations of floating point, can be an excellent idea.

(3) It sounds like you might not want to take this particular instructor's teachings to heart.

But also,

(4) As I mentioned, I am not a numerical analyst, so I might not really know what I'm talking about here, either.

Steve Summit
  • 49.2k
  • 9
  • 80
  • 113

AltStyle によって変換されたページ (->オリジナル) /