#define conversion from C to C#

2020-06-16 03:16发布

问题:

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)); }

?

回答1:

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.



回答2:

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

  1. Proper version:
    1. 4 calls to fac()
    2. 4 additions
    3. 2 multiplications
  2. ´#define` version:
    1. 9 calls to fac()
    2. 8 additions
    3. 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).



回答3:

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.



回答4:

It's basically equivalent, yes. You can also get rid of some parentheses:

private static double LERP(double a, double b, double c) { return (b - a) * c + a; }


回答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++)



回答6:

Yes, but you can write it more simple:

return (b - a) * c + a; 


标签: c# c macros