Is this C code:
/* LERP(a,b,c) = linear interpolation macro, is 'a' when c == 0.0 and 'b' when c == 1.0 */
#define LERP(a,b,c) (((b) - (a)) * (c) + (a))
http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_T.html
Equals this C# code?
private static double LERP(double a, double b, double c) { return (((b) - (a)) * (c) + (a)); }
?
no.
consider the following:
LERP(x++,1,2);
The c code might also have a side effect of increasing x twice [it is undefined as mentioned by @phresnel], while the c# code is perfectly defined, and will increase x only once.
the result also might be different, since the first a
and the second one [in the macro] might have a different value, since it might have increased in the first one.
No. The C variant comes with all the deficiencies of #define
-macros. Reminder:
#define LERP(a,b,c) (((b) - (a)) * (c) + (a))
Potential Waste of Performance
Imagine a pure function call in an invokation of the #define
-macro:
int fac (int x) {
return x<=1 ? 1 : x*fac(x-1);
}
int main () {
std::cout << LERP(fac(5), fac(2), 0);
}
That line expands to:
std::cout << (((fac(2)) - (fac(5))) * (0) + (fac(5)))
Now you have potentially doubled the runtime for two invokations of your faculty function that was perviously just one.
This surely gets worse if you nest your lerping, as is for instance common in some graphics programming situations:
int main () {
std::cout << LERP(
LERP(fac(5), fac(2), 0),
LERP(fac(5), fac(2), 0),
0
);
}
Expanding to:
int main () {
std::cout << LERP(
(((fac(2)) - (fac(5))) * (0) + (fac(5))),
(((fac(2)) - (fac(5))) * (0) + (fac(5)))
0
);
}
Expanding to (formatting tweaked for readability):
int main () {
std::cout << ( (((((fac(2)) - (fac(5))) * (0) + (fac(5))))
- ((((fac(2)) - (fac(5))) * (0) + (fac(5)))))
* (c)
+ ((((fac(2)) - (fac(5))) * (0) + (fac(5)))))
}
Whereas the clean version does computationally not more than:
float a = LERP(fac(5), fac(2), 0);
float b = LERP(fac(5), fac(2), 0);
float c = LERP(a,b,0);
or
float fac_a = fac(5),
fac_b = fac(2);
float a = (fac_b-fac_a)*0 + fac_a;
float fac_c = fac(5),
fac_d = fac(2);
float a = (fac_d-fac_c)*0 + fac_c;
So in a two dimensional setup
- Proper version:
- 4 calls to
fac()
- 4 additions
- 2 multiplications
- ´#define` version:
- 9 calls to
fac()
- 8 additions
- 4 multiplications
It gets exponentially worse with every dimension you'd add. Even five-dimensional Perlin Noise is sometimes seen (3d volume + time + continous seed), for which some expressions are evaluated freaking 31 times, instead of just once!:
LERP( LERP(LERP(LERP(LERP(probe(),1,2), LERP(3,4,5), 6),
LERP(LERP(7,8,9), LERP(10,11,12), 13),
14),
LERP(LERP(LERP(90,91,92), LERP(93,94,95), 96),
LERP(LERP(97,98,99), LERP(910,911,912), 913),
914),
1014),
LERP(LERP(LERP(LERP(0,1,2), LERP(3,4,5), 6),
LERP(LERP(7,8,9), LERP(10,11,12), 13),
14),
LERP(LERP(LERP(90,91,92), LERP(93,94,95), 96),
LERP(LERP(97,98,99), LERP(910,911,912), 913),
914),
1014),
666)
You can also see the preprocessed code by invoking cpp
(note the single appearance of probe()
before).
foo@bar:~/ cpp heavy.cc
[snip] (((((((((((((((911) - (910)) * (912) + (910))) - ((((98) - (97)) * (99) + (97)))) * (913) + ((((98) - (97)) * (99) + (97))))) - (((((((94) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90)))))) * (914) + (((((((94) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90))))))) - ((((((((((11) - (10)) * (12) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3))) - ((((1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0)))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0)))))))) * (1014) + ((((((((((11) - (10)) * (12) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3))) - ((((1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0)))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0))))))))) - (((((((((((((911) - (910)) * (912) + (910))) - ((((98) - (97)) * (99) + (97)))) * (913) + ((((98) - (97)) * (99) + (97))))) - (((((((94) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90)))))) * (914) + (((((((94) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90))))))) - ((((((((((11) - (10)) * (12) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))))) * (1014) + ((((((((((11) - (10)) * (12) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))))))) * (666) + (((((((((((((911) - (910)) * (912) + (910))) - ((((98) - (97)) * (99) + (97)))) * (913) + ((((98) - (97)) * (99) + (97))))) - (((((((94) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90)))))) * (914) + (((((((94) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90))))))) - ((((((((((11) - (10)) * (12) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))))) * (1014) + ((((((((((11) - (10)) * (12) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe()))))))))))
Again, full source is here.
Potential Undefined Behaviour
You can put evil stuff into it:
LERP(++a,b,c)
which expands to
(((b) - (++a)) * (c) + (++a))
which is undefined behaviour in C¹ (and in C++, btw). a
might be increased twice, or it might be increased once, or an exception might be thrown that says Debug-Runtime-Exception: Invoked Undefined Behaviour
, or the compiler is smart enough to reject that code, or whatever.
The undefined behaviour comes from the fact that the C99-standard (and C++2003, too) does not allow a value to be modified multiple times before reaching the next Sequence Point.
ID pollution and infection
(This is more relevant if you'd convert the C# into the macro variant.)
The #define
-macro-name pollutes and infects the whole unit of translation from the point of definition to either the end-of-unit or its undefinition.
foo@bar:~/ cat main.cc
// Orbiter Physics Sim
#include "lerp.h"
int main () {
const int LERP = 2; // Linear Extrasolar Resonance Potential.
}
foo@bar:~/ g++ main.cc
main.cc:5:15: error: expected unqualified-id before ‘=’ token
More ...
- Macros are generic, but not typesafe. The macro-writer has no (clean) opportunity to put constraints on the types for which that macro should be valid.
- Macros don't have scope, though this is already implied by the last section
¹: C99 (ISO/IEC 9899:TC2), J.2, "Undefined Behaviour": Between two sequence points, an object is modified more than once, or is modified and the prior value is read other than to determine the value to be stored (6.5).
To get what you had in C you may use:
delegate double ValueGetter();
static double LERP(ValueGetter a, ValueGetter b, ValueGetter c) { return (b() - a()) * c() + a(); }
But I agree with amit. This might be the same you had in C (but solely for double not for other types) still it might not be what you really want.. (with delegate you could set 'i++' as a, not only result of i++)