Why does exponentiation (e.g., 10^6) take 4 times longer than calculator notation (e.g., 1e6) in R?
Asked Answered
J

2

20

Using the scientific notation 10^6 in an R code (as I customarily do) results in a significantly longer computing time than using the calculator representation 1e6:

> system.time(for (t in 1:1e7) x=10^6) 
  utilisateur     système      écoulé 
        4.792       0.000       4.281 
> system.time(for (t in 1:1e7) x=1e6) 
 utilisateur     système      écoulé 
       0.804       0.000       1.051
> system.time(for (t in 1:1e7) x=exp(6*log(10)))
 utilisateur     système      écoulé 
       6.301       0.000       5.702

Why is it the case that R recomputes 10^6 in about the same times as it computes exp{6*log(10)}? I understand the fact that R executes a function when computing 10^6, but why was it coded this way?

Joist answered 2/5, 2015 at 15:51 Comment(6)
I'd guess that one is a numeric literal that only needs to be translated while the other is an expression that first needs to be evaluated.Djakarta
Why do you think that R calculates 10^6 via exp(6*log(10))?Susy
@cryo111: I do not know how R computes 10^6but it takes as long as using exp(6*log(10)). I will rephrase this sentence, thank you.Soy
It's not a matter of "being coded in a particular way" as you say but that you are explicitly telling R to compute 10 to the power of 6... For instance, would you rather assign double a=1e6; or double a=pow(10,6); in C++?Susy
@cryo111: I understand the reason but bemoan the loss in efficiency when using a power notation. This is not the end of the world, but I have to be careful about this in my future codes.Soy
@Xi'an A good question BTW. Also, you got two nice answers with some interesting additional insights by MrFlick and Josh. +1 allSusy
T
37

It's because 1e6 is a constant, and is recognized as such by the parser, whereas 10^6 is parsed as a function call which has to be further evaluated (by a call to the function ^()). Since the former avoids the expensive overhead of a function call, evaluating it is a lot faster!

class(substitute(1e6))
# [1] "numeric"
class(substitute(10^6))
# [1] "call"

To better see that it's a call, you can dissect it like this:

as.list(substitute(10^6))
# [[1]]
# `^`
# 
# [[2]]
# [1] 10
# 
# [[3]]
# [1] 6

A few other interesting cases:

## negative numbers are actually parsed as function calls
class(substitute(-1))
[1] "call"

## when you want an integer, 'L' notation lets you avoid a function call 
class(substitute(1000000L))
# [1] "integer"
class(substitute(as.integer(1000000)))
# [1] "call"
Townswoman answered 2/5, 2015 at 15:59 Comment(2)
Very interesting additional cases! But puzzling. I had never heard of using the L before (but it does not seem to save time when allocating a negative number)Soy
@Xi'an Yeah, R's use of a trailing L to indicate integers is distinct from its treatment of - as a function call. As far as I know, there's simply no way to supply a negative constant without executing a function call --- no "1N" or the like.Pfaff
L
8

In the case of 1e6 you are specifying a literal numeric value. There is no calculation happening there.

When you request 10^6, R does the work of raising 10 to the 6th power. The ^ is a numeric operator. It doesn't do anything special for base 10. It doesn't know the different between 10^6 and 12^14. It must do the calculation to find the answer.

Leeke answered 2/5, 2015 at 15:59 Comment(2)
Thanks. It is sad that 10 is not given special status, though!Soy
Giving 10 special status would complicate the parser. I have obviously done no tests, but chances are this will slow things down more than the occasional power calculation.Laodicean

© 2022 - 2024 — McMap. All rights reserved.