inline power function replacement

L

lawrence.jones

Eric Sosman said:
C should have an exponentiation *operator*!

It has one: pow(). There's nothing stopping the compiler from recognizing
it and generating inline code, and some compilers do just that. Some
even recognize the case where the second argument is an integer and
generate more efficient code.

-Larry Jones

Hello, I'm wondering if you sell kegs of dynamite. -- Calvin
 
K

Keith Thompson

It has one: pow(). There's nothing stopping the compiler from recognizing
it and generating inline code, and some compilers do just that. Some
even recognize the case where the second argument is an integer and
generate more efficient code.

Sure, but they're not required to do so.

Efficiency aside, if C had an exponentation operator overloaded for
either integer or floating-point exponents, you could be sure that
2**3 would yield 8 -- but pow(2, 3), which is really pow(2.0, 3.0),
could yield 7.99999999375.
 
C

Chris Torek

Sure, but they're not required to do so.

Right. Specifically, making the operator look exactly like an
ordinary function call makes it particularly easy for "lazy"
compilers to generate an ordinary function call: the compiler-writer
does not even need to convert an expression tree node into a call
to __pow(), for instance.
Efficiency aside, if C had an exponentation operator overloaded for
either integer or floating-point exponents, you could be sure that
2**3 would yield 8 ...

Only if that were part of the specification. For instance, suppose
C really did have a "**" operator, but it were allowed to return
a "double", and a C compiler could turn:

a = b ** c;

into:

a = __pow((double)b, (double)c);

Then the situation would be the same as today.

In other words -- and this is a general principle -- the important
thing is not the syntax, but the required semantics.
 
K

Keith Thompson

Chris Torek said:
Right. Specifically, making the operator look exactly like an
ordinary function call makes it particularly easy for "lazy"
compilers to generate an ordinary function call: the compiler-writer
does not even need to convert an expression tree node into a call
to __pow(), for instance.


Only if that were part of the specification. For instance, suppose
C really did have a "**" operator, but it were allowed to return
a "double", and a C compiler could turn:

a = b ** c;

into:

a = __pow((double)b, (double)c);

Then the situation would be the same as today.

In other words -- and this is a general principle -- the important
thing is not the syntax, but the required semantics.

Agreed.

My point here is that C already has a limited form of overloading for
predefined operators (the meaning of x + y depends on the types of x
and y) , but not for functions. If we wanted to provide an overloaded
exponentation thingie, which would by definition be equivalent to
repeated multiplication for an integer right operand, the most natural
way to do it would be to make it an operator.

(I'm ignoring C99's <tgmath.h>.)

(And, of course, we couldn't use "**" without breaking existing code;
b ** c already means b * (*c). Disambiguating it based on the type of
the right operand would be possible, I suppose, but that would mean
that tokenization would be affected by expression types, which is just
too ugly to contemplate.)
 
E

Eric Sosman

Chris said:
[...]
In other words -- and this is a general principle -- the important
thing is not the syntax, but the required semantics.

Syntax is important, too, because a helpful syntax can
enhance readability (and writeability).

x1 = div(sub(neg(b),sqrt(sub(pow(b,2),mul(mul(4,a),c)))),mul(2,a));

(Confession: It took me *three* tries to get this grade-school
formula right -- if, indeed, I *have* gotten it right! I submit
that programmer productivity would be severely impaired if one
were to replace "ordinary" operators with function calls.)
 
T

Tak-Shing Chan

Chris said:
[...]
In other words -- and this is a general principle -- the important
thing is not the syntax, but the required semantics.

Syntax is important, too, because a helpful syntax can
enhance readability (and writeability).

x1 = div(sub(neg(b),sqrt(sub(pow(b,2),mul(mul(4,a),c)))),mul(2,a));

[OT] LISP programmers might disagree (with this particular
example).

Tak-Shing
 
E

Eric Sosman

Tak-Shing Chan said:
Chris said:
[...]
In other words -- and this is a general principle -- the important
thing is not the syntax, but the required semantics.


Syntax is important, too, because a helpful syntax can
enhance readability (and writeability).

x1 = div(sub(neg(b),sqrt(sub(pow(b,2),mul(mul(4,a),c)))),mul(2,a));


[OT] LISP programmers might disagree (with this particular
example).

[OT] LISP programmers go into debt trying to buy enough
parentheses to get through the day. Been there, done that
(professionally), still hearing from collection agencies. ;-)

Seriously, one just doesn't find Lisp programmers writing
single-expression forms as complex as those C and FORTRAN folks
dash off as a matter of course. The expression above, in LISP,
would most likely be split into two if not three parts, with
one or perhaps two temporary variables just to hold intermediate
results:

(setq d (- (pow b 2) (* 4 a c)))
(setq n (- (- b) (sqrt d)))
(setq x1 (/ n (mul 2 a)))

In principle, of course, this could be written

(setq x1 (/ (- (- b) (sqrt (- (pow b 2) (* 4 a c)))) (mul 2 a)))

.... but that hardly ever happens. When it does, it's as hard
to read as ... well, just *look* at it. (And note that this
example takes advantage of LISP's "variadic" functions in a way
not available to C; the C equivalent, above, is even worse.)

With practice one becomes more adept at reading this kind
of verbose linearized tree (as I said: been there), but it's
never going to be as readable as the C/FORTRAN/ALGOL syntax
that enjoys the benefit of a close resemblance to the standard
mathematical notation we've all been reading since grade school.

Real-life example: Here, in all its ugliness, is an actual
expression from one of my actual C programs:

quadratic (&t1, &t2,
SQUARE(xvj - xvk) + SQUARE(yvj - yvk),
2.0 * ((xcj - xck) * (xvj - xvk)
+ (ycj - yck) * (yvj - yvk)),
SQUARE(xcj - xck) + SQUARE(ycj - yck)
- SQUARE(rj + ball[k].r));

Even with the benefits of C's looks-like-mathematics syntax,
this thing is sufficiently difficult to read that I went to
a little extra work chopping it into multiple lines and adding
suggestive indentation. Still, it remains comprehensible, if
just barely so. Challenge: Translate this into LISP (or another
syntax using functional forms for operators) and make it no less
readable than the above. I'll even allow you to leave out the
first two arguments, as they're artifacts of the way C does things.
I'll bet you a dollar^H^H^H^H^H^H^H denarius^H^H^H^H^H^H^H^H^H^H^H
ten electrons you can't do it.
 
T

Tak-Shing Chan

On Fri, 28 Jul 2006, Eric Sosman wrote:

[snip]
[OT] LISP programmers go into debt trying to buy enough
parentheses to get through the day. Been there, done that
(professionally), still hearing from collection agencies. ;-)

Seriously, one just doesn't find Lisp programmers writing
single-expression forms as complex as those C and FORTRAN folks
dash off as a matter of course. The expression above, in LISP,
would most likely be split into two if not three parts, with
one or perhaps two temporary variables just to hold intermediate
results:

(setq d (- (pow b 2) (* 4 a c)))
(setq n (- (- b) (sqrt d)))
(setq x1 (/ n (mul 2 a)))

In principle, of course, this could be written

(setq x1 (/ (- (- b) (sqrt (- (pow b 2) (* 4 a c)))) (mul 2 a)))

... but that hardly ever happens. When it does, it's as hard
to read as ... well, just *look* at it. (And note that this
example takes advantage of LISP's "variadic" functions in a way
not available to C; the C equivalent, above, is even worse.)

With practice one becomes more adept at reading this kind
of verbose linearized tree (as I said: been there), but it's
never going to be as readable as the C/FORTRAN/ALGOL syntax
that enjoys the benefit of a close resemblance to the standard
mathematical notation we've all been reading since grade school.

Real-life example: Here, in all its ugliness, is an actual
expression from one of my actual C programs:

quadratic (&t1, &t2,
SQUARE(xvj - xvk) + SQUARE(yvj - yvk),
2.0 * ((xcj - xck) * (xvj - xvk)
+ (ycj - yck) * (yvj - yvk)),
SQUARE(xcj - xck) + SQUARE(ycj - yck)
- SQUARE(rj + ball[k].r));

Even with the benefits of C's looks-like-mathematics syntax,
this thing is sufficiently difficult to read that I went to
a little extra work chopping it into multiple lines and adding
suggestive indentation. Still, it remains comprehensible, if
just barely so. Challenge: Translate this into LISP (or another
syntax using functional forms for operators) and make it no less
readable than the above. I'll even allow you to leave out the
first two arguments, as they're artifacts of the way C does things.
I'll bet you a dollar^H^H^H^H^H^H^H denarius^H^H^H^H^H^H^H^H^H^H^H
ten electrons you can't do it.

I claim that this is more readable:

(quadratic &t1 &t2
(squared-dist xvdiff yvdiff)
(* 2.0
(+ (* xcdiff xvdiff) (* ycdiff yvdiff)))
(squared-dist-corrected xcdiff ycdiff rj ball[k].r))

Where can I get my ten electrons? ;-)

Tak-Shing
 
E

Eric Sosman

Tak-Shing Chan said:
[...]
Seriously, one just doesn't find Lisp programmers writing
single-expression forms as complex as those C and FORTRAN folks
dash off as a matter of course. The expression above, in LISP,
would most likely be split into two if not three parts, with
one or perhaps two temporary variables just to hold intermediate
results:
[...]

Real-life example: Here, in all its ugliness, is an actual
expression from one of my actual C programs:

quadratic (&t1, &t2,
SQUARE(xvj - xvk) + SQUARE(yvj - yvk),
2.0 * ((xcj - xck) * (xvj - xvk)
+ (ycj - yck) * (yvj - yvk)),
SQUARE(xcj - xck) + SQUARE(ycj - yck)
- SQUARE(rj + ball[k].r));

Even with the benefits of C's looks-like-mathematics syntax,
this thing is sufficiently difficult to read that I went to
a little extra work chopping it into multiple lines and adding
suggestive indentation. Still, it remains comprehensible, if
just barely so. Challenge: Translate this into LISP (or another
syntax using functional forms for operators) and make it no less
readable than the above. I'll even allow you to leave out the
first two arguments, as they're artifacts of the way C does things.
I'll bet you a dollar^H^H^H^H^H^H^H denarius^H^H^H^H^H^H^H^H^H^H^H
ten electrons you can't do it.

I claim that this is more readable:

(quadratic &t1 &t2
(squared-dist xvdiff yvdiff)
(* 2.0
(+ (* xcdiff xvdiff) (* ycdiff yvdiff)))
(squared-dist-corrected xcdiff ycdiff rj ball[k].r))

Where can I get my ten electrons? ;-)

Go to the FedEX web site and use the tracking number
I sent you earlier. (You didn't get the tracking number?
Use the trackingtracking number to find it. You didn't
get a trackingtracking number either? Time to look up
the trackingtrackingtracking number ...)

As to the readability, I agree that your formula is more
readable than mine, but I contend that's not due to the use
of a functional rather than an infix notation: it's because of
the introduction of temporary variables to hold intermediate
results. That's exactly the practice I mentioned earlier; it
seems to be the LISP programmer's reflexive response to the
inherent unreadability of his language. (At least, when I was
writing and reading a lot of LISP, that was my reflex ...)

Back to C: Its lack of an exponentiation operator is rather
conspicuous (Lawrence Jones' claim that pow() *is* an operator
notwithstanding). However, it's a lack we've learned to live
with, and despite my own grumblings it doesn't really hurt all
*that* badly. Without an exponentiator, C is without perfumed
breezes but not without oxygen.
 
L

lawrence.jones

Keith Thompson said:
(And, of course, we couldn't use "**" without breaking existing code;
b ** c already means b * (*c).

But it would be the best kind of breakage -- for b**c to be valid, c
must have a pointer type, which wouldn't be valid for exponentiation
and thus wouldn't compile. Hopefully, most people wouldn't write that
without any whitespace between the *'s, so there wouldn't be very much
broken code.

The bigger problem would be with unary usages (**p), since writing
the *'s without any intervening whitespace is typical and C's greedy
tokenization would interpret it as exponentiation instead of two
dereferences.

-Larry Jones

I don't see why some people even HAVE cars. -- Calvin
 
E

ena8t8si

But it would be the best kind of breakage -- for b**c to be valid, c
must have a pointer type, which wouldn't be valid for exponentiation
and thus wouldn't compile. Hopefully, most people wouldn't write that
without any whitespace between the *'s, so there wouldn't be very much
broken code.

The bigger problem would be with unary usages (**p), since writing
the *'s without any intervening whitespace is typical and C's greedy
tokenization would interpret it as exponentiation instead of two
dereferences.

No reason that ** has to be made a separate token. In the type
pass, whenever there is a**b and b isn't a pointer, the compiler
could adjust the parse tree so that exponentiation is done for
the two adjacent * operators. Alternatively, unary ** with a
pointer operand could mean "double dereference".

My fear is that someone will take these suggestions seriously
and propose one or the other in earnest to the ISO committee.
 
C

Chris Torek

No reason that ** has to be made a separate token. In the type
pass, whenever there is a**b and b isn't a pointer, the compiler
could adjust the parse tree so that exponentiation is done for
the two adjacent * operators.

(This alternative scares me :) )
Alternatively, unary ** with a
pointer operand could mean "double dereference".

This one, I like. Only one problem...

x = 2**p;

is valid today if p is a pointer; it parses as:

x = 2 * (*p);

You would have to make the rule be even more complicated: a ** b
means "a to the b power if b is arithmetic, but means a multiplied
by *b if b is a pointer". (The unary operator could always mean
double-indirection.) Note this still works with:

T **pp;
...
x = 2***pp;

which parses as:

x = 2 ** (*pp);

and (*pp) is still a pointer, so the ** operator does the right
thing. Similarly:

T ***q;
...
x = 2****q;

parses as:

x = 2 ** (**q);

and the unary "**" operator means double-indirection, leaving a
pointer, so that 2**(that) is still 2 * *(that).
My fear is that someone will take these suggestions seriously
and propose one or the other in earnest to the ISO committee.

Hey, it is no worse than:

char arr[4] = "ugh!"; /* no \0 */

:)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,188
Messages
2,571,002
Members
47,591
Latest member
WoodrowBut

Latest Threads

Top