问题
While toying with a tail recursion example I noticed a small discrepancy between the results of a normal recursive call and a tail recursive call:
scala> def fact(n: Int): Double = if(n < 1) 1 else n * fact(n - 1)
fact: (n: Int)Double
scala> fact(30)
res31: Double = 2.6525285981219103E32
scala> @tailrec def fact(n: Int, acc: Double = 1): Double = if(n < 1) acc else fact(n - 1, n * acc)
fact: (n: Int, acc: Double)Double
scala> fact(30)
res32: Double = 2.652528598121911E32
Just out of curiosity, can someone please explain to me why or where the rounding is happening. My guess is that because the Scala compiler translates the tail recursive version to a loop, the acc parameter is assigned at each iteration of the loop, and that the small rounding error slips in there.
回答1:
The result is different because the two versions do the multiplications in different order, thus leading to different rounding.
The normal recursive call leads to an expression n*([n-1]*([n-2]*(...))), because you first compute the value of fact(n-1) and then multiply it with n, whereas the tail recursive one leads to ((n*[n-1])*[n-2])*... because you first multiply by n and then iterate on to n-1.
Try rewriting one of the versions so that it iterates the other way and you should, theoretically, get the same answer.
回答2:
Your two functions are not doing the operations in the same order.
In C:
int main(int c, char **v)
{
printf ("%.16e %.16e\n",
30.*29*28*27*26*25*24*23*22*21*20*19*18*17*16*15*14*13*12*11*10*9*8*7*6*5*4*3*2,
2.*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20*21*22*23*24*25*26*27*28*29*30);
}
prints:
2.6525285981219110e+32 2.6525285981219103e+32
(I used a platform on which floating-point in C works predictably)
One version of your function computes 30.*29*... and the other computes 2.*3*....
It is normal for these two results to be slightly different: floating-point operations are not associative. But please note that there is nothing unfathomable about the results. One of your functions computes exactly the IEEE 754 double-precision expression 30.*29*... and the other computes exactly 2.*3*.... They both work as designed.
If I had to guess, I would expect that 2.*3*... is more accurate (closer to the result obtained with real numbers), but it does not matter: the two numbers are very close and very close to the real result.
回答3:
The difference isn't about the fact that Scala turns the tail recursion into a loop. The result would be the same without that optimization. Also recursion doesn't act differently with regards to rounding errors than loops do.
The difference is the order in which the numbers are multiplied. Your first solution recurses down all the way to 1 before it starts multiplying the numbers. So it will end up calculating n * ( (n - 1) * (... * (2 * 1))). The tail recursive version starts multiplying right away, so it ends up calculating n * (n-1) * ... * 2 * 1.
Of course usually, we'd say that those two are the same because multiplication is associative, but that's not true for floating point arithmetic. Using floating points (x * y) * z may very well be different from x * (y * z) because rounding errors propagate differently. So that explains your behavior.
Note that you'll see the same difference when using a for-loop that counts from 1 to n versus one that counts from n to 1 to implement the factorial.
来源:https://stackoverflow.com/questions/11829433/why-is-there-a-rounding-difference-between-my-normal-recursion-and-tail-recursio