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))
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
fac()
fac()
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.
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.
(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
¹: 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).
Technically, no they are not equal. The C macro can take any type: int
, float
, byte
, etc.
Your C# version can only handle double, without explicit casts. You'd need to add overloads as needed for other types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With