Calculating integer factorials in constant time, taking advantage of overflow
blogs.msdn.microsoft.com
blogs.msdn.microsoft.com
n as 12 makes a lot of sense. n as a number too large to count doesn't make sense in the context of real programs.
The formal definition of time complexity measures relative to the size of the input, which in this case would be proportional to the number of elements in the list.
> n as a number too large to count doesn't make sense in the context of real programs
The definition of big-O notation involves taking the limit as n goes to infinity. It doesn't really even make sense if you only have a finite (even if huge) number of problem instances, since then everything is bounded by a constant.
More importantly, you ignore the nature of the problem when you treat 2^64 as a constant, but the number of values in the list as n. I'm comfortable betting that if you take this problem, and push n to 2^100, you'll find that your integer size has been changed to 2^128. In other words, iconjack's algorithm is actually O(n) space or worse.
It's not obfuscation, it's literally the definition.
I'm not saying that the algorithm in question is practical, but asymptotic complexity has a precise mathematical definition, so it's totally possible for an algorithm with "good" time complexity to be so complicated or have such a huge constant that it's impossible to use (see [1] and [2] for some examples).
It's also totally legitimate to give a constraint on additional memory size, so that this algorithm is disallowed altogether (which is probably the right answer), or change the problem so that the word size varies with the list length, but it's not legitimate to just say that the algorithm isn't linear time because the constant is too big.
[1] http://cstheory.stackexchange.com/questions/4491/powerful-al...
[2] https://rjlipton.wordpress.com/2010/10/23/galactic-algorithm...
It does not talk about smaller numbers at all.
If you use an algorithm and complexity that are close for both moderately large numbers and extremely large numbers, you've done a good job.
If you introduce dumb things like constants that are larger than the known universe, you are performing obfuscation.
The suggested algorithm is linear, but only when applied to a very specific interpretation of the problem. I'm disagreeing with that interpretation of the problem, not the conclusion that it's linear within that particular interpretation.
There narrower use is more like Θ(1) with the upper and lower constants identical.
The lower and upper bound differ, but they are both still constant.
It's also difficult to talk about constant time outside of an asymptotic context. There are a number of subtle sources for timing variances (e.g. branches, branch mispredicts, cache misses, pipeline stalls, context switches, etc..).
In simpler terms, it always runs in exactly k time.
It is a difficult problem. It's also a critically important one when you're doing things like cryptography. Maybe we look at different kinds of algorithms, because while I see a lot of worrying about O(n) vs. O(logn) vs. O(nlogn) vs. O(n^2), most of the time when I see people worry about whether something is constant or not, they mean truly constant, not asymptotically. They are worrying about side channels.
In relation to side-channel attacks, wouldn't it be sufficient to just pad the difference between upper and lower bounds using no-ops?
I'll keep that in mind so I don't give wrong impressions in the future.
>In relation to side-channel attacks, wouldn't it be sufficient to just pad the difference between upper and lower bounds using no-ops?
It can be extremely difficult to design algorithms so that they spend the same amount of time no matter what the input number is. You can't just use no-op instructions because you don't know how many cycles things will take, no-ops aren't perfect at soaking up extra time, and they don't help at all against something like an attacker on the same EC2 host. Even worse is if you have the OS interrupt mid-computation and wreck the cycle-accurate counters that would tell you how many no-ops to use. You want to design it so that the same instruction sequence happens every time, without branching based on input, without having varied memory accesses based on input. And avoiding other variable length pitfalls like division.
template<int64_t n> int64_t fact() { return n * fact<n-1>(); } template<> int64_t fact<0>() { return 1; }
The book "Modern C++ design" from Alexandrescu extensively covers that kind of stuff.
There might even be a way to use a loop and constexprs.
Eg, Stirlings approximation for ln(n!) should serve fairly well for moderate and above values of n. And knowing your statistical distribution at large n you can likely find a decent distribution from a discrete case requiring n! into a continuous one, eg Gaussian.
Only in number theory problems do you actually need bignums for factorials, and those are usually toy problems or modulo some big prime.
I don't know of any actual applications where you need to compute the bignum factorial. I'm curious what the GP needed it for.
When I wrote games for older hardware every frickin' thing possible used look up tables to maximize speed.
Coincidentally I received this type of problem later when interviewing at MS, but it wasn't Raymond Chen's group.
edit: Investigating further, this intrinsic was introduced into gcc around 2006, which means I'm not sure it would have been available to me at the time.
For portability reasons, it might be better just using bit tricks and letting the compiler try to optimize it though. Clang and GCC do pretty well here.
┬────────────────
┼─────────────┬──
│ ──┬──────── ┼ ┬
│ ┬─┼─┬────── │ │
│ │ │ ┼─┬─┬── │ │
│ │ │ ┼─┼─┼─┬ │ │
│ │ │ └─┤ ├─┘ │ │
│ │ │ ├─┘ │ │
│ │ ├───┘ │ │
│ ├─┘ │ │
└─┤ │ │
└───────────┤ │
└─┘
(a diagram of factorial on Church numerals)