www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Semantics of ^^

reply Don <nospam nospam.com> writes:
Based on everyone's comments, this is what I have come up with:

--------------------
x ^^ y is right associative, and has a precedence intermediate between 
multiplication and unary operators.

* The type of x ^^ y is the same as the type of x * y.
* If y == 0,  x ^^ y is 1.
* If both x and y are integers, and y > 0,  x^^y is equivalent to
    { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
* If both x and y are integers, and y < 0, an integer divide error 
occurs, regardless of the value of x. This error is detected at compile 
time, if possible.
* If either x or y are floating-point, the result is pow(x, y).
--------------------
Rationale:
(1) Although the following special cases could be defined...
  * If x == 1,  x ^^ y is 1
  * If x == -1 and y is even, x^^y == 1
  * If x == -1 and y is odd, x^^y == -1
... they are not sufficiently useful to justify the major increase in 
complexity which they introduce. In all other cases, a negative exponent 
indicates an error; it should be rewritten as (cast(real)x) ^^ y. Making 
these cases errors makes everything much simpler, and allows the 
compiler to use range propagation on the value of y to detect most 
exponentiation errors at compile time. (If those cases are legal, the 
compiler can't generate an error on x^^-2, because of the possibility 
that x might be 1 or -1).
Also note that making it an error leaves open the possibility of 
changing it to a non-error later, without breaking code; but going from 
non-error to error would be more difficult.

(2) USE OF THE INTEGER DIVIDE ERROR
Note that on x86 at least, a hardware "integer divide error", although 
commonly referred to as "division by zero", also occurs when the DIV 
instruction, which performs uint = ulong/uint, results in a value 
greater than uint.max. Raising a number to a negative power does involve 
a division, so it seems to me not unreasonable to use it for this case 
as well.
Note that 0 ^^ -1 is a division by zero.
This means that, just as you should check that y!=0 before performing 
x/y, you should check that y>=0 before performing x^^y.

(3) OVERFLOW
int ^^ int returns an int, not a long. Although a long would allow 
representation of larger numbers, even doubling the number of bits 
doesn't help much in avoiding overflow, because x^^y is exponential. 
Even a floating-point representation can easily overflow:
5000^5000 easily overflows an 80-bit real.
So, it's preferable to retain the simplicity that typeof(x^^y) is 
typeof(x*y).
Dec 08 2009
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Don:

 Based on everyone's comments, this is what I have come up with:
Looks good.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
You can rewrite both of those as: { typeof(x) u = 1; foreach (i; 0 .. y) { u *= x; } return u; }
 (1) Although the following special cases could be defined...
   * If x == 1,  x ^^ y is 1
   * If x == -1 and y is even, x^^y == 1
   * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in 
 complexity which they introduce.
This is not essential: (-1)**n is a common enough shortcut to produce an alternating +1 -1, you can see it used often enough in Python code (and in mathematics). This search gives 433 results: http://www.google.com/codesearch?q=\%28-1\%29\s*\*\*\s*[0-9a-zA-Z%28]+lang%3Apython When used for this purpose (-1) is always compile time constant, so the compiler can grow a simple rule the rewrites: (-1) ^^ n as (n & 1) ? -1 : 1 Bye, bearophile
Dec 08 2009
next sibling parent reply Don <nospam nospam.com> writes:
bearophile wrote:
 Don:
 
 Based on everyone's comments, this is what I have come up with:
Looks good.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
You can rewrite both of those as: { typeof(x) u = 1; foreach (i; 0 .. y) { u *= x; } return u; }
 (1) Although the following special cases could be defined...
   * If x == 1,  x ^^ y is 1
   * If x == -1 and y is even, x^^y == 1
   * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in 
 complexity which they introduce.
This is not essential: (-1)**n is a common enough shortcut to produce an alternating +1 -1, you can see it used often enough in Python code (and in mathematics). This search gives 433 results: http://www.google.com/codesearch?q=\%28-1\%29\s*\*\*\s*[0-9a-zA-Z%28]+lang%3Apython When used for this purpose (-1) is always compile time constant, so the compiler can grow a simple rule the rewrites: (-1) ^^ n as (n & 1) ? -1 : 1
That's an interesting one. With this proposal, that optimisation could still be made when it is known that n>=0. We *could* make a special rule for compile-time constant -1 ^^ n, to allow the optimisation even when n<0. But then you have to explain why: x = -1; y = x^^-2; is illegal, but y = -1^^-2 is legal. Can that be justified?
Dec 08 2009
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Don:

 But then you have to explain why:  x = -1; y = x^^-2; is illegal,
 but y = -1^^-2 is legal. Can that be justified?
Probably not :-) Bye, bearophile
Dec 08 2009
prev sibling parent BCS <none anon.com> writes:
Hello Don,

 bearophile wrote:
 
 Don:
 
 Based on everyone's comments, this is what I have come up with:
 
Looks good.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
 { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
You can rewrite both of those as: { typeof(x) u = 1; foreach (i; 0 .. y) { u *= x; } return u; }
 (1) Although the following special cases could be defined...
 * If x == 1,  x ^^ y is 1
 * If x == -1 and y is even, x^^y == 1
 * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase
 in
 complexity which they introduce.
This is not essential: (-1)**n is a common enough shortcut to produce an alternating +1 -1, you can see it used often enough in Python code (and in mathematics). This search gives 433 results: http://www.google.com/codesearch?q=\%28-1\%29\s*\*\*\s*[0-9a-zA-Z%28] +lang%3Apython When used for this purpose (-1) is always compile time constant, so the compiler can grow a simple rule the rewrites: (-1) ^^ n as (n & 1) ? -1 : 1
That's an interesting one. With this proposal, that optimisation could still be made when it is known that n>=0. We *could* make a special rule for compile-time constant -1 ^^ n, to allow the optimisation even when n<0. But then you have to explain why: x = -1; y = x^^-2; is illegal, but y = -1^^-2 is legal. Can that be justified?
Maybe. I think it's worth looking into.
Dec 08 2009
prev sibling parent KennyTM~ <kennytm gmail.com> writes:
On Dec 8, 09 19:16, bearophile wrote:
 Don:

 Based on everyone's comments, this is what I have come up with:
Looks good.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y>  0,  x^^y is equivalent to
      { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
You can rewrite both of those as: { typeof(x) u = 1; foreach (i; 0 .. y) { u *= x; } return u; }
 (1) Although the following special cases could be defined...
    * If x == 1,  x ^^ y is 1
    * If x == -1 and y is even, x^^y == 1
    * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in
 complexity which they introduce.
This is not essential: (-1)**n is a common enough shortcut to produce an alternating +1 -1, you can see it used often enough in Python code (and in mathematics). This search gives 433 results: http://www.google.com/codesearch?q=\%28-1\%29\s*\*\*\s*[0-9a-zA-Z%28]+lang%3Apython When used for this purpose (-1) is always compile time constant, so the compiler can grow a simple rule the rewrites: (-1) ^^ n as (n& 1) ? -1 : 1 Bye, bearophile
Other non-essential special cases are 2^^n == 1<<n and x^^2 == x*x, but these should be put in the optimizer, not the language. (And an integer power algorithm http://www.c2.com/cgi/wiki?IntegerPowerAlgorithm should be used instead of a simple foreach loop implementation-wise.)
Dec 08 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 08 Dec 2009 13:32:26 +0300, Don <nospam nospam.com> wrote:

 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between  
 multiplication and unary operators.

 * The type of x ^^ y is the same as the type of x * y.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error  
 occurs, regardless of the value of x. This error is detected at compile  
 time, if possible.
 * If either x or y are floating-point, the result is pow(x, y).
 --------------------
 Rationale:
 (1) Although the following special cases could be defined...
   * If x == 1,  x ^^ y is 1
   * If x == -1 and y is even, x^^y == 1
   * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in  
 complexity which they introduce. In all other cases, a negative exponent  
 indicates an error; it should be rewritten as (cast(real)x) ^^ y. Making  
 these cases errors makes everything much simpler, and allows the  
 compiler to use range propagation on the value of y to detect most  
 exponentiation errors at compile time. (If those cases are legal, the  
 compiler can't generate an error on x^^-2, because of the possibility  
 that x might be 1 or -1).
 Also note that making it an error leaves open the possibility of  
 changing it to a non-error later, without breaking code; but going from  
 non-error to error would be more difficult.

 (2) USE OF THE INTEGER DIVIDE ERROR
 Note that on x86 at least, a hardware "integer divide error", although  
 commonly referred to as "division by zero", also occurs when the DIV  
 instruction, which performs uint = ulong/uint, results in a value  
 greater than uint.max. Raising a number to a negative power does involve  
 a division, so it seems to me not unreasonable to use it for this case  
 as well.
 Note that 0 ^^ -1 is a division by zero.
 This means that, just as you should check that y!=0 before performing  
 x/y, you should check that y>=0 before performing x^^y.

 (3) OVERFLOW
 int ^^ int returns an int, not a long. Although a long would allow  
 representation of larger numbers, even doubling the number of bits  
 doesn't help much in avoiding overflow, because x^^y is exponential.  
 Even a floating-point representation can easily overflow:
 5000^5000 easily overflows an 80-bit real.
 So, it's preferable to retain the simplicity that typeof(x^^y) is  
 typeof(x*y).
Looks solid. Just a quick question: is ^^ going to stay as opPow, or is it about to join the opBinary party?
Dec 08 2009
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Don wrote:
 Based on everyone's comments, this is what I have come up with:
 
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between 
 multiplication and unary operators.
 
 * The type of x ^^ y is the same as the type of x * y.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
    { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error 
 occurs, regardless of the value of x. This error is detected at compile 
 time, if possible.
 * If either x or y are floating-point, the result is pow(x, y).
 --------------------
 Rationale:
 (1) Although the following special cases could be defined...
  * If x == 1,  x ^^ y is 1
  * If x == -1 and y is even, x^^y == 1
  * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in 
 complexity which they introduce. In all other cases, a negative exponent 
 indicates an error; it should be rewritten as (cast(real)x) ^^ y. Making 
 these cases errors makes everything much simpler, and allows the 
 compiler to use range propagation on the value of y to detect most 
 exponentiation errors at compile time. (If those cases are legal, the 
 compiler can't generate an error on x^^-2, because of the possibility 
 that x might be 1 or -1).
 Also note that making it an error leaves open the possibility of 
 changing it to a non-error later, without breaking code; but going from 
 non-error to error would be more difficult.
I agree.
 (2) USE OF THE INTEGER DIVIDE ERROR
 Note that on x86 at least, a hardware "integer divide error", although 
 commonly referred to as "division by zero", also occurs when the DIV 
 instruction, which performs uint = ulong/uint, results in a value 
 greater than uint.max. Raising a number to a negative power does involve 
 a division, so it seems to me not unreasonable to use it for this case 
 as well.
 Note that 0 ^^ -1 is a division by zero.
 This means that, just as you should check that y!=0 before performing 
 x/y, you should check that y>=0 before performing x^^y.
This is reasonable.
 (3) OVERFLOW
 int ^^ int returns an int, not a long. Although a long would allow 
 representation of larger numbers, even doubling the number of bits 
 doesn't help much in avoiding overflow, because x^^y is exponential. 
 Even a floating-point representation can easily overflow:
 5000^5000 easily overflows an 80-bit real.
 So, it's preferable to retain the simplicity that typeof(x^^y) is 
 typeof(x*y).
Absolutely. I think people who use exponentiation will be well aware of how easily it overflows, and will use long or BigInt (which should of course overload ^^) if they are worried about this. In any case, the most common uses of int^^int will be x^^2, x^^3, and x^^4. It looks like you've given this quite some thought. Thanks! -Lars
Dec 08 2009
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 08 Dec 2009 05:32:26 -0500, Don <nospam nospam.com> wrote:

 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between  
 multiplication and unary operators.

 * The type of x ^^ y is the same as the type of x * y.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error  
 occurs, regardless of the value of x. This error is detected at compile  
 time, if possible.
 * If either x or y are floating-point, the result is pow(x, y).
If x and y are both integral and x is 2, then the operation becomes 1 << y Also, what happens when you do 3^^1000000? I hope this does not result in the exact loop you wrote above. At the very least, when y > 32, the following code is more efficient: { /* get largest set bit, probably could do this more efficiently, note that the type of m and y should be unsigned */ typeof(y) m = 1 << (typeof(y).sizeof * 8 - 1); while(m > y) m >>= 1; long u = 1; for(; m > 0; m >>=1) { u *= u; if(m & y) u *= x; } return u; } -Steve
Dec 08 2009
parent reply Don <nospam nospam.com> writes:
Steven Schveighoffer wrote:
 On Tue, 08 Dec 2009 05:32:26 -0500, Don <nospam nospam.com> wrote:
 
 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between 
 multiplication and unary operators.

 * The type of x ^^ y is the same as the type of x * y.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error 
 occurs, regardless of the value of x. This error is detected at 
 compile time, if possible.
 * If either x or y are floating-point, the result is pow(x, y).
If x and y are both integral and x is 2, then the operation becomes 1 << y
And if x is 4, it becomes 1 << 2*y, etc. Google for "addition chains" if you're interested in the optimal sequences, it's a mathematical research area.
 Also, what happens when you do 3^^1000000?  I hope this does not result 
 in the exact loop you wrote above.
No, of course not. I was just describing semantics, not implementation. The little foreach loop implicitly describes what happens to overflow, etc.
Dec 08 2009
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 08 Dec 2009 10:17:30 -0500, Don <nospam nospam.com> wrote:

 Steven Schveighoffer wrote:
 On Tue, 08 Dec 2009 05:32:26 -0500, Don <nospam nospam.com> wrote:

 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between  
 multiplication and unary operators.

 * The type of x ^^ y is the same as the type of x * y.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error  
 occurs, regardless of the value of x. This error is detected at  
 compile time, if possible.
 * If either x or y are floating-point, the result is pow(x, y).
If x and y are both integral and x is 2, then the operation becomes 1 << y
And if x is 4, it becomes 1 << 2*y, etc. Google for "addition chains" if you're interested in the optimal sequences, it's a mathematical research area.
2^^n would be a very common entity in programming, it's definitely much more appealing to me than 1 << n. I just figured the compiler should avoid making the optimizer work on optimizing out that foreach loop and do the work up front. But in any case, since I misunderstood what you meant (that the above loop is not literally inserted), it doesn't matter as long as the implementation can be optimized.
 Also, what happens when you do 3^^1000000?  I hope this does not result  
 in the exact loop you wrote above.
No, of course not. I was just describing semantics, not implementation. The little foreach loop implicitly describes what happens to overflow, etc.
ok good. -Steve
Dec 08 2009
prev sibling next sibling parent reply Bill Baxter <wbaxter gmail.com> writes:
On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 multiplication and unary operators.
Is that consistent with math? I think in math they usually write (-1)^n with parens. See for example the sin power series here: http://en.wikipedia.org/wiki/Power_series What's the rationale for going against math here?
 * The type of x ^^ y is the same as the type of x * y.
Like it.
 * If y =3D=3D 0, =A0x ^^ y is 1.
Need to mention what happens when x is also 0.
 * If both x and y are integers, and y > 0, =A0x^^y is equivalent to
 =A0 { auto u =3D x; foreach(i; 1..y) { u *=3D x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error occurs=
,
 regardless of the value of x. This error is detected at compile time, if
 possible.
Can you explain why you think that's necessary? Seems like going too far towards a nanny compiler for no particularly good reason. The fact that 2^^-1 isn't particularly useful doesn't make it particularly error prone. No more so than integer division when the person meant floating point division. I just find it unexpected that a language would single out exponentiation for this kind of treatment.
 * If either x or y are floating-point, the result is pow(x, y).
 --------------------
 Rationale:
 (1) Although the following special cases could be defined...
 =A0* If x =3D=3D 1, =A0x ^^ y is 1
 =A0* If x =3D=3D -1 and y is even, x^^y =3D=3D 1
 =A0* If x =3D=3D -1 and y is odd, x^^y =3D=3D -1
 ... they are not sufficiently useful to justify the major increase in
 complexity which they introduce.
Hmm, I'm not so sure about that. I saw examples of this being used even in the small sampling of search results from Python and Fortran code that I looked at. Many mathematical constructs are defined as having a leading sign of (-1)^^n (like the sin series formula linked above).
 In all other cases, a negative exponent
 indicates an error; it should be rewritten as (cast(real)x) ^^ y. Making
 these cases errors makes everything much simpler, and allows the compiler=
to
 use range propagation on the value of y to detect most exponentiation err=
ors
 at compile time. (If those cases are legal, the compiler can't generate a=
n
 error on x^^-2, because of the possibility that x might be 1 or -1).
I didn't see this error as adding much value when there was nothing clearly lost from it. But here you're showing there is a real price to pay for this nanny compiler behavior. To me that makes the error clearly not worth the price of admission. I also think that since 0^0 and 0^-1 and such are mathematically undefined, careful users of opPow will already have to put some if() check before blindly doing an x^^y. Instead of if(x!=3D0 || y!=3D0) x^^y; the people who care can just change the check to if(x!=3D0 || y>0) x^^y; Doesn't changing that one operator there doesn't seem like an undue burden upon those who are careful checkers of their values.
 Also note that making it an error leaves open the possibility of changing=
it
 to a non-error later, without breaking code; but going from non-error to
 error would be more difficult.
I think it's pretty clear that the error goes too far right now without taking a wait-and-see stance. --bb
Dec 08 2009
next sibling parent reply "Phil Deets" <pjdeets2 gmail.com> writes:
On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com> wrote:

 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 [snip]
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
   { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error  
 occurs,
 regardless of the value of x. This error is detected at compile time, if
 possible.
Can you explain why you think that's necessary? Seems like going too far towards a nanny compiler for no particularly good reason. The fact that 2^^-1 isn't particularly useful doesn't make it particularly error prone. No more so than integer division when the person meant floating point division. I just find it unexpected that a language would single out exponentiation for this kind of treatment.
I was also wondering about this. If it can't be detected at compile time, is the result 0?
 [snip]
 (1) Although the following special cases could be defined...
  * If x == 1,  x ^^ y is 1
  * If x == -1 and y is even, x^^y == 1
  * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in
 complexity which they introduce.
Hmm, I'm not so sure about that. I saw examples of this being used even in the small sampling of search results from Python and Fortran code that I looked at. Many mathematical constructs are defined as having a leading sign of (-1)^^n (like the sin series formula linked above). [snip]
I think n is always non-negative in the trig series, but some Laurent series use negative n values. So (-1)^^n might be useful for negative n, but you could always rewrite it as (n%2 ? -1 : 1) or ~(n&1)+1.
Dec 08 2009
next sibling parent reply "Phil Deets" <pjdeets2 gmail.com> writes:
On Tue, 08 Dec 2009 16:44:46 -0500, Phil Deets <pjdeets2 gmail.com> wrote:

 I think n is always non-negative in the trig series, but some Laurent  
 series use negative n values. So (-1)^^n might be useful for negative n,  
 but you could always rewrite it as (n%2 ? -1 : 1) or ~(n&1)+1.
Oops, ~(n&1)+1 doesn't work, but ~((n&1)<<1)+2 does. -- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Dec 08 2009
parent Bill Baxter <wbaxter gmail.com> writes:
On Tue, Dec 8, 2009 at 1:58 PM, Phil Deets <pjdeets2 gmail.com> wrote:
 On Tue, 08 Dec 2009 16:44:46 -0500, Phil Deets <pjdeets2 gmail.com> wrote:

 I think n is always non-negative in the trig series, but some Laurent
 series use negative n values. So (-1)^^n might be useful for negative n, but
 you could always rewrite it as (n%2 ? -1 : 1) or ~(n&1)+1.
Oops, ~(n&1)+1 doesn't work, but ~((n&1)<<1)+2 does.
Right, and you can always write x^^y as pow(x,y). A major point of opPow is to avoid unnatural wonky-isms like you what you wrote there. --bb
Dec 08 2009
prev sibling parent reply Don <nospam nospam.com> writes:
Phil Deets wrote:
 On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com> wrote:
 
 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 [snip]
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
   { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error 
 occurs,
 regardless of the value of x. This error is detected at compile time, if
 possible.
Can you explain why you think that's necessary? Seems like going too far towards a nanny compiler for no particularly good reason. The fact that 2^^-1 isn't particularly useful doesn't make it particularly error prone. No more so than integer division when the person meant floating point division. I just find it unexpected that a language would single out exponentiation for this kind of treatment.
I was also wondering about this. If it can't be detected at compile time, is the result 0?
No, it's a compile-time error.
Dec 08 2009
parent reply "Phil Deets" <pjdeets2 gmail.com> writes:
On Tue, 08 Dec 2009 22:26:10 -0500, Don <nospam nospam.com> wrote:

 Phil Deets wrote:
 On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com>  
 wrote:

 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 [snip]
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
   { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error  
 occurs,
 regardless of the value of x. This error is detected at compile time,  
 if
 possible.
Can you explain why you think that's necessary? Seems like going too far towards a nanny compiler for no particularly good reason. The fact that 2^^-1 isn't particularly useful doesn't make it particularly error prone. No more so than integer division when the person meant floating point division. I just find it unexpected that a language would single out exponentiation for this kind of treatment.
I was also wondering about this. If it can't be detected at compile time, is the result 0?
No, it's a compile-time error.
It can't be a compile-time error if it can't be detected at compile time like I said in my question. In the code 2^^SomeFunctionFromACModuleThatReturnsInt(); the compiler can't know whether the exponent will be positive or negative. When the code runs and a negative result is returned, will the result be zero? -- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Dec 08 2009
parent reply "Phil Deets" <pjdeets2 gmail.com> writes:
On Wed, 09 Dec 2009 02:40:44 -0500, Phil Deets <pjdeets2 gmail.com> wrote:

 On Tue, 08 Dec 2009 22:26:10 -0500, Don <nospam nospam.com> wrote:

 Phil Deets wrote:
 On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com>  
 wrote:

 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 [snip]
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
   { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error  
 occurs,
 regardless of the value of x. This error is detected at compile  
 time, if
 possible.
Can you explain why you think that's necessary? Seems like going too far towards a nanny compiler for no particularly good reason. The fact that 2^^-1 isn't particularly useful doesn't make it particularly error prone. No more so than integer division when the person meant floating point division. I just find it unexpected that a language would single out exponentiation for this kind of treatment.
I was also wondering about this. If it can't be detected at compile time, is the result 0?
No, it's a compile-time error.
It can't be a compile-time error if it can't be detected at compile time like I said in my question. In the code 2^^SomeFunctionFromACModuleThatReturnsInt(); the compiler can't know whether the exponent will be positive or negative. When the code runs and a negative result is returned, will the result be zero?
Maybe you meant, if you can't prove it's positive, it's an error. If so, I would suggest requiring an unsigned type. -- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Dec 08 2009
parent Don <nospam nospam.com> writes:
Phil Deets wrote:
 On Wed, 09 Dec 2009 02:40:44 -0500, Phil Deets <pjdeets2 gmail.com> wrote:
 
 On Tue, 08 Dec 2009 22:26:10 -0500, Don <nospam nospam.com> wrote:

 Phil Deets wrote:
 On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com> 
 wrote:

 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 [snip]
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
   { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error 
 occurs,
 regardless of the value of x. This error is detected at compile 
 time, if
 possible.
Can you explain why you think that's necessary? Seems like going too far towards a nanny compiler for no particularly good reason. The fact that 2^^-1 isn't particularly useful doesn't make it particularly error prone. No more so than integer division when the person meant floating point division. I just find it unexpected that a language would single out exponentiation for this kind of treatment.
I was also wondering about this. If it can't be detected at compile time, is the result 0?
No, it's a compile-time error.
It can't be a compile-time error if it can't be detected at compile time like I said in my question. In the code
Sorry. I misread that. I wrote that at 3am.
 2^^SomeFunctionFromACModuleThatReturnsInt();

 the compiler can't know whether the exponent will be positive or 
 negative. When the code runs and a negative result is returned, will 
 the result be zero?
Maybe you meant, if you can't prove it's positive, it's an error. If so, I would suggest requiring an unsigned type.
Read the proposal. It's a runtime divide error.
Dec 08 2009
prev sibling next sibling parent reply Don <nospam nospam.com> writes:
Bill Baxter wrote:
 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 multiplication and unary operators.
Is that consistent with math? I think in math they usually write (-1)^n with parens. See for example the sin power series here: http://en.wikipedia.org/wiki/Power_series
Hmm, that's what's
 
 What's the rationale for going against math here?
 
 * The type of x ^^ y is the same as the type of x * y.
Like it.
 * If y == 0,  x ^^ y is 1.
Need to mention what happens when x is also 0.
No. 0^^0 is 1, as well.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
   { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error occurs,
 regardless of the value of x. This error is detected at compile time, if
 possible.
Can you explain why you think that's necessary? Seems like going too far towards a nanny compiler for no particularly good reason. The fact that 2^^-1 isn't particularly useful doesn't make it particularly error prone. No more so than integer division when the person meant floating point division.
Integer division is a well defined operation. 2^^-1 is *never* sensible. I just find it unexpected that
 a language would single out exponentiation for this kind of treatment.
 
 * If either x or y are floating-point, the result is pow(x, y).
 --------------------
 Rationale:
 (1) Although the following special cases could be defined...
  * If x == 1,  x ^^ y is 1
  * If x == -1 and y is even, x^^y == 1
  * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in
 complexity which they introduce.
Hmm, I'm not so sure about that. I saw examples of this being used even in the small sampling of search results from Python and Fortran code that I looked at. Many mathematical constructs are defined as having a leading sign of (-1)^^n (like the sin series formula linked above).
Yes, but n is always positive in those formulae.
 
 In all other cases, a negative exponent
 indicates an error; it should be rewritten as (cast(real)x) ^^ y. Making
 these cases errors makes everything much simpler, and allows the compiler to
 use range propagation on the value of y to detect most exponentiation errors
 at compile time. (If those cases are legal, the compiler can't generate an
 error on x^^-2, because of the possibility that x might be 1 or -1).
I didn't see this error as adding much value when there was nothing clearly lost from it. But here you're showing there is a real price to pay for this nanny compiler behavior. To me that makes the error clearly not worth the price of admission. I also think that since 0^0
Rubbish. 0^^0 is 1.
 and 0^-1 and such are mathematically undefined, careful users of opPow
 will already have to put some if() check before blindly doing an x^^y.
  Instead of
    if(x!=0 || y!=0)  x^^y;
 the people who care can just change the check to
    if(x!=0 || y>0) x^^y;
 
 Doesn't changing that one operator there doesn't seem like an undue
 burden upon those who are careful checkers of their values.
 
 Also note that making it an error leaves open the possibility of changing it
 to a non-error later, without breaking code; but going from non-error to
 error would be more difficult.
I think it's pretty clear that the error goes too far right now without taking a wait-and-see stance.
Do you realize you are asking for ^^ to be removed? I'm not joking. Walter's ready to pull it out. Please reconsider.
Dec 08 2009
parent reply Bill Baxter <wbaxter gmail.com> writes:
On Tue, Dec 8, 2009 at 7:24 PM, Don <nospam nospam.com> wrote:
 * If y =3D=3D 0, =A0x ^^ y is 1.
Need to mention what happens when x is also 0.
No. =A00^^0 is 1, as well.
Is it? That's rather embarrassing for Wolfram Alpha, then (and presumably Mathematica, too) since they have it as "indeterminate": http://www.wolframalpha.com/input/?i=3D0^0
 (1) Although the following special cases could be defined...
 =A0* If x =3D=3D 1, =A0x ^^ y is 1
 =A0* If x =3D=3D -1 and y is even, x^^y =3D=3D 1
 =A0* If x =3D=3D -1 and y is odd, x^^y =3D=3D -1
 ... they are not sufficiently useful to justify the major increase in
 complexity which they introduce.
Hmm, I'm not so sure about that. =A0I saw examples of this being used even in the small sampling of search results from Python and Fortran code that I looked at. =A0Many mathematical constructs are defined as having a leading sign of (-1)^^n =A0(like the sin series formula linked above).
Yes, but n is always positive in those formulae.
Well, non-negative actually. But yeh, that is true as far as the ones I know, too.
 I didn't see this error as adding much value when there was nothing
 clearly lost from it. =A0But here you're showing there is a real price
 to pay for this nanny compiler behavior. =A0 To me that makes the error
 clearly not worth the price of admission. =A0I also think that since 0^0
Rubbish. 0^^0 is 1.
Yeh, again Wolfram steered me wrong there, if you correct about that.
 and 0^-1 and such are mathematically undefined, careful users of opPow
 will already have to put some if() check before blindly doing an x^^y.
 =A0Instead of
 =A0 if(x!=3D0 || y!=3D0) =A0x^^y;
 the people who care can just change the check to
 =A0 if(x!=3D0 || y>0) x^^y;

 Doesn't changing that one operator there doesn't seem like an undue
 burden upon those who are careful checkers of their values.

 Also note that making it an error leaves open the possibility of changi=
ng
 it
 to a non-error later, without breaking code; but going from non-error t=
o
 error would be more difficult.
I think it's pretty clear that the error goes too far right now without taking a wait-and-see stance.
Do you realize you are asking for ^^ to be removed? I'm not joking. Walte=
r's
 ready to pull it out. Please reconsider.
Walter's ready to pull it if you don't make negative powers on integers an error? Nope, didn't realize that. Anyway, I've made my objections clear I think, so I won't reiterate. Basically I just want to KISS, and be consistent with how / works, and I think you guys get that but reject it. Ok. --bb
Dec 09 2009
next sibling parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Bill Baxter wrote:
 On Tue, Dec 8, 2009 at 7:24 PM, Don <nospam nospam.com> wrote:
 * If y == 0,  x ^^ y is 1.
Need to mention what happens when x is also 0.
No. 0^^0 is 1, as well.
Is it? That's rather embarrassing for Wolfram Alpha, then (and presumably Mathematica, too) since they have it as "indeterminate": http://www.wolframalpha.com/input/?i=0^0
It's not an either-or question. There are different definitions of 0^0, and it's not obvious which is the "best one": http://en.wikipedia.org/wiki/Exponentiation#Zero_to_the_zero_power I think 0^^0 should give the same result as 0.0^^0.0, for which C has set the precedent that pow(0.0, 0.0)==1.0. -Lars
Dec 09 2009
prev sibling parent Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Bill Baxter wrote:
 On Tue, Dec 8, 2009 at 7:24 PM, Don <nospam nospam.com> wrote:
 * If y == 0,  x ^^ y is 1.
Need to mention what happens when x is also 0.
No. 0^^0 is 1, as well.
Is it? That's rather embarrassing for Wolfram Alpha, then (and presumably Mathematica, too) since they have it as "indeterminate": http://www.wolframalpha.com/input/?i=0^0
Confirmed with Mathematica. In[2]:= 0^0 During evaluation of In[2]:= Power::indet: Indeterminate expression 0^0 encountered. >> Out[2]= Indeterminate -- Chris NS
Dec 09 2009
prev sibling parent reply Don <nospam nospam.com> writes:
Bill Baxter wrote:
 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 multiplication and unary operators.
Is that consistent with math? I think in math they usually write (-1)^n with parens. See for example the sin power series here: http://en.wikipedia.org/wiki/Power_series What's the rationale for going against math here?
Hmm. Several other languages give it that precedence. But you're right, it should be even higher than unary. Between unary and postfix ?
Dec 08 2009
parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Don wrote:
 Bill Baxter wrote:
 On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:
 Based on everyone's comments, this is what I have come up with:

 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 multiplication and unary operators.
Is that consistent with math? I think in math they usually write (-1)^n with parens. See for example the sin power series here: http://en.wikipedia.org/wiki/Power_series What's the rationale for going against math here?
Hmm. Several other languages give it that precedence. But you're right, it should be even higher than unary. Between unary and postfix ?
Good catch, I missed that one in the original proposal. Between unary and postfix sounds right. I would be extremely surprised if -2^^2 evaluated to 4, but I would have to think twice about i++^^2. -Lars
Dec 09 2009
prev sibling next sibling parent reply Don <nospam nospam.com> writes:
CHANGES BASED ON FURTHER COMMENTS
--------------------
x ^^ y is right associative, and has a precedence intermediate between
  unary and postfix operators.
The type of x ^^ y is the same as the type of x * y.

* If either x or y are floating-point, the result is pow(x, y).

If both x and y are integers, the following rules apply:

  * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 1 : 0
  * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
  * If x is the compile-time constant -1 and y is an integer, x^^y is 
rewritten as (y & 1) ? -1 : 1.

  * If y == 0, x ^^ y is 1.
  * If y > 0,  x ^^ y is functionally equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
  * If y < 0, an integer divide error occurs, regardless of the value of x.

-----------
Note that by definining the 0,1, -1 cases as "rewriting" rules rather 
than return values, it should be clearer that they don't apply to 
variables having those values.
I think this covers everything useful, while avoiding nasty surprises like

double y = x ^^ -1; // looks like reciprocal, but isn't!
// Yes, this IS the same problem you get with double y = 1/x.
// But that's doesn't make it acceptable. I have a possible solution to 
that one, too.

I don't think we can afford to spend much more time on this.
Is everyone happy now?
Dec 09 2009
next sibling parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather
 than return values, it should be clearer that they don't apply to
 variables having those values.
I don't care if x^^y with y < 0 is 0, a runtime error, or even undefined behavior. However, having different behavior if x is a compile-time constant than if x is a variable is unacceptable because it silently changes the (defined) behavior of a function when a runtime parameter is changed to a template parameter or vice versa, or even when the compiler becomes a bit more clever about CTFE. -- Rainer Deyke - rainerd eldwood.com
Dec 09 2009
parent reply "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Rainer Deyke wrote:
 Don wrote:
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather
 than return values, it should be clearer that they don't apply to
 variables having those values.
I don't care if x^^y with y < 0 is 0, a runtime error, or even undefined behavior. However, having different behavior if x is a compile-time constant than if x is a variable is unacceptable because it silently changes the (defined) behavior of a function when a runtime parameter is changed to a template parameter or vice versa, or even when the compiler becomes a bit more clever about CTFE.
I don't think it's a problem: Either it works as expected, or it causes a compile-time error. -Lars
Dec 09 2009
parent reply Rainer Deyke <rainerd eldwood.com> writes:
Lars T. Kyllingstad wrote:
 Rainer Deyke wrote:
 I don't care if x^^y with y < 0 is 0, a runtime error, or even undefined
 behavior.  However, having different behavior if x is a compile-time
 constant than if x is a variable is unacceptable because it silently
 changes the (defined) behavior of a function when a runtime parameter is
 changed to a template parameter or vice versa, or even when the compiler
 becomes a bit more clever about CTFE.
I don't think it's a problem: Either it works as expected, or it causes a compile-time error.
Not quite. Under the proposal, -1^^-1 works (i.e. produces the correct result) at compile time but fails at runtime. -- Rainer Deyke - rainerd eldwood.com
Dec 09 2009
next sibling parent reply Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Lars T. Kyllingstad wrote:
 Rainer Deyke wrote:
 I don't care if x^^y with y < 0 is 0, a runtime error, or even undefined
 behavior.  However, having different behavior if x is a compile-time
 constant than if x is a variable is unacceptable because it silently
 changes the (defined) behavior of a function when a runtime parameter is
 changed to a template parameter or vice versa, or even when the compiler
 becomes a bit more clever about CTFE.
I don't think it's a problem: Either it works as expected, or it causes a compile-time error.
Not quite. Under the proposal, -1^^-1 works (i.e. produces the correct result) at compile time but fails at runtime.
It won't pass CTFE.
Dec 09 2009
parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 Rainer Deyke wrote:
 Not quite.  Under the proposal, -1^^-1 works (i.e. produces the correct
 result) at compile time but fails at runtime.
It won't pass CTFE.
pure int f() { return -1; } void g(int)(int); g!(f() ^^ f())(0); // Works. g!(0)(f() ^^ f()); // Runtime error? 'f() ^^ f()' can be a compile-time constant, but isn't guaranteed to be evaluated at compile time. -- Rainer Deyke - rainerd eldwood.com
Dec 09 2009
parent reply Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Don wrote:
 Rainer Deyke wrote:
 Not quite.  Under the proposal, -1^^-1 works (i.e. produces the correct
 result) at compile time but fails at runtime.
It won't pass CTFE.
pure int f() { return -1; } void g(int)(int); g!(f() ^^ f())(0); // Works.
No, it doesn't work. It's exactly the same as: int intpow(int x, int y) { assert(y>=0); return x; } g!(intpow(f(), f())(0); f() ^^ f() is not a constant expression. It won't get transformed. It can be *interpreted* at compile time, but that'll generate an error.
 g!(0)(f() ^^ f()); // Runtime error?
Definitely.
 
 'f() ^^ f()' can be a compile-time constant, but isn't guaranteed to be
 evaluated at compile time.
No, it's not a compile-time constant, unless it's turned into one. If however you changed it to: template eval(int x) { enum int eval = x; } g!( eval!(f()) ^^ f() )(0); which turns f() into a compile-time constant, then it'd work. But then it'd work at runtime, as well.
Dec 09 2009
next sibling parent Don <nospam nospam.com> writes:
Don wrote:
 Rainer Deyke wrote:
 Don wrote:
 Rainer Deyke wrote:
 Not quite.  Under the proposal, -1^^-1 works (i.e. produces the correct
 result) at compile time but fails at runtime.
It won't pass CTFE.
pure int f() { return -1; } void g(int)(int); g!(f() ^^ f())(0); // Works.
No, it doesn't work. It's exactly the same as: int intpow(int x, int y) { assert(y>=0); return x; } g!(intpow(f(), f())(0); f() ^^ f() is not a constant expression. It won't get transformed. It can be *interpreted* at compile time, but that'll generate an error.
 g!(0)(f() ^^ f()); // Runtime error?
Definitely.
 'f() ^^ f()' can be a compile-time constant, but isn't guaranteed to be
 evaluated at compile time.
No, it's not a compile-time constant, unless it's turned into one. If however you changed it to: template eval(int x) { enum int eval = x; } g!( eval!(f()) ^^ f() )(0); which turns f() into a compile-time constant, then it'd work. But then it'd work at runtime, as well.
You can see this effect in action in the current version of DMD, because at present sqrt() can be evaluated at compile time, but pow() cannot be. (this behaviour will be fixed, but for now it illustrates the point). ----------- import std.math; int bar(double x)() { return 0; } double foo() { return 0.5; } void main() { int z = bar!( 7.0 ^^ 0.5 )(); // works -- 0.5 is a constant int z2 = bar!( 7.0 ^^ foo() )(); // fails -- foo() is not. }
Dec 09 2009
prev sibling parent reply Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 f() ^^ f() is not a constant expression. It won't get transformed. It
 can be *interpreted* at compile time, but that'll generate an error.
That's a very fine distinction. One that may not survive future evolution of the D language, and may not respected by other implementations of the D language. (I /should/ be removed. Having different rules for operators and functions unnecessarily complicates the language.) Code that depends on this distinction is highly fragile. It should not be possible to write code that depends on this distinction. -- Rainer Deyke - rainerd eldwood.com
Dec 09 2009
parent reply Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Don wrote:
 f() ^^ f() is not a constant expression. It won't get transformed. It
 can be *interpreted* at compile time, but that'll generate an error.
That's a very fine distinction.
Yes. This is a very narrow special case. Bill Baxter and others have argued that it is such an important one, that it needs to be allowed. But it's surrounded by nasty stuff which definitely must not be allowed. One that may not survive future
 evolution of the D language, and may not respected by other
 implementations of the D language. 
I think you're confusing 'pure' with 'constant expression'. They are not the same thing. To repeat what I said in the previous post: f() is *not* a compile-time constant. Never. But you can use it to make one. (I /should/ be removed. Having
 different rules for operators and functions unnecessarily complicates
 the language.) 
Do you mean the fact that constant folding always happens for operators, but that CTFE doesn't happen automatically? Code that depends on this distinction is highly fragile.
  It should not be possible to write code that depends on this distinction.
How can you write code that depends on this distinction?
Dec 09 2009
parent Rainer Deyke <rainerd eldwood.com> writes:
Don wrote:
 Rainer Deyke wrote:
  One that may not survive future
 evolution of the D language, and may not respected by other
 implementations of the D language. 
I think you're confusing 'pure' with 'constant expression'. They are not the same thing.
No.
  (I /should/ be removed.  Having
 different rules for operators and functions unnecessarily complicates
 the language.) 
Do you mean the fact that constant folding always happens for operators, but that CTFE doesn't happen automatically?
Yes.
  Code that depends on this distinction is highly fragile.
  It should not be possible to write code that depends on this
 distinction.
How can you write code that depends on this distinction?
Thinking about it again, this may not be as much of a problem as I have been saying. If -1 ^^ -1 works for compile-time constants, then expanding the definition of compile-time constant is unlikely to break any code. -- Rainer Deyke - rainerd eldwood.com
Dec 09 2009
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Rainer Deyke wrote:
 Lars T. Kyllingstad wrote:
 Rainer Deyke wrote:
 I don't care if x^^y with y < 0 is 0, a runtime error, or even undefined
 behavior.  However, having different behavior if x is a compile-time
 constant than if x is a variable is unacceptable because it silently
 changes the (defined) behavior of a function when a runtime parameter is
 changed to a template parameter or vice versa, or even when the compiler
 becomes a bit more clever about CTFE.
I don't think it's a problem: Either it works as expected, or it causes a compile-time error.
Not quite. Under the proposal, -1^^-1 works (i.e. produces the correct result) at compile time but fails at runtime.
That depends on which of these two rules gets the higher precedence: Don wrote:
  * If x is the compile-time constant -1 and y is an integer, x^^y is rewritten
as (y & 1) ? -1 : 1.
  * If y < 0, an integer divide error occurs, regardless of the value of x.
I suppose it should be the latter. -Lars
Dec 09 2009
prev sibling parent Don <nospam nospam.com> writes:
Rainer Deyke wrote:
 Lars T. Kyllingstad wrote:
 Rainer Deyke wrote:
 I don't care if x^^y with y < 0 is 0, a runtime error, or even undefined
 behavior.  However, having different behavior if x is a compile-time
 constant than if x is a variable is unacceptable because it silently
 changes the (defined) behavior of a function when a runtime parameter is
 changed to a template parameter or vice versa, or even when the compiler
 becomes a bit more clever about CTFE.
I don't think it's a problem: Either it works as expected, or it causes a compile-time error.
Not quite. Under the proposal, -1^^-1 works (i.e. produces the correct result) at compile time but fails at runtime.
No. That's why I wrote it as a transformation rule. CTFE is not permitted to perform transformations, it can only evaluate.
Dec 09 2009
prev sibling next sibling parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
On Wed, 09 Dec 2009 09:36:56 +0100, Don <nospam nospam.com> wrote:

 CHANGES BASED ON FURTHER COMMENTS
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
   unary and postfix operators.
 The type of x ^^ y is the same as the type of x * y.

 * If either x or y are floating-point, the result is pow(x, y).

 If both x and y are integers, the following rules apply:

   * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 1  
 : 0
   * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
   * If x is the compile-time constant -1 and y is an integer, x^^y is  
 rewritten as (y & 1) ? -1 : 1.

   * If y == 0, x ^^ y is 1.
   * If y > 0,  x ^^ y is functionally equivalent to
      { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
   * If y < 0, an integer divide error occurs, regardless of the value of  
 x.

 -----------
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather  
 than return values, it should be clearer that they don't apply to  
 variables having those values.
 I think this covers everything useful, while avoiding nasty surprises  
 like

 double y = x ^^ -1; // looks like reciprocal, but isn't!
 // Yes, this IS the same problem you get with double y = 1/x.
 // But that's doesn't make it acceptable. I have a possible solution to  
 that one, too.

 I don't think we can afford to spend much more time on this.
 Is everyone happy now?
Might I enquire as to why the exponent should be allowed to be signed for integer exponentiation? It's been covered several times - it makes no sense. Apart from that - great job! -- Simen
Dec 09 2009
next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
On Dec 9, 09 17:25, Simen kjaeraas wrote:
 On Wed, 09 Dec 2009 09:36:56 +0100, Don <nospam nospam.com> wrote:

 CHANGES BASED ON FURTHER COMMENTS
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 unary and postfix operators.
 The type of x ^^ y is the same as the type of x * y.

 * If either x or y are floating-point, the result is pow(x, y).

 If both x and y are integers, the following rules apply:

 * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 1 : 0
 * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
 * If x is the compile-time constant -1 and y is an integer, x^^y is
 rewritten as (y & 1) ? -1 : 1.

 * If y == 0, x ^^ y is 1.
 * If y > 0, x ^^ y is functionally equivalent to
 { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If y < 0, an integer divide error occurs, regardless of the value of x.

 -----------
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather
 than return values, it should be clearer that they don't apply to
 variables having those values.
 I think this covers everything useful, while avoiding nasty surprises
 like

 double y = x ^^ -1; // looks like reciprocal, but isn't!
 // Yes, this IS the same problem you get with double y = 1/x.
 // But that's doesn't make it acceptable. I have a possible solution
 to that one, too.

 I don't think we can afford to spend much more time on this.
 Is everyone happy now?
Might I enquire as to why the exponent should be allowed to be signed for integer exponentiation? It's been covered several times - it makes no sense. Apart from that - great job!
Because 3^^-1 would become 3^^4294967295 (note: int can be implicitly converted to uint) which then you spend 17 seconds to get 2863311531 and wonder what's going on.
Dec 09 2009
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
KennyTM~:
 Because 3^^-1 would become 3^^4294967295
I agree, no unsigned values, please. In D unsigned values are bug-prone, it's better to avoid them when possible, they are useful almost only when you need a bitfield (they are useful for arithmetic only exceptionally, when you really need a positive range larger than the signed one and you can't use a value with more bits). Bye, bearophile
Dec 09 2009
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
KennyTM~ wrote:
 On Dec 9, 09 17:25, Simen kjaeraas wrote:
 On Wed, 09 Dec 2009 09:36:56 +0100, Don <nospam nospam.com> wrote:

 CHANGES BASED ON FURTHER COMMENTS
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 unary and postfix operators.
 The type of x ^^ y is the same as the type of x * y.

 * If either x or y are floating-point, the result is pow(x, y).

 If both x and y are integers, the following rules apply:

 * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 1 
 : 0
 * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
 * If x is the compile-time constant -1 and y is an integer, x^^y is
 rewritten as (y & 1) ? -1 : 1.

 * If y == 0, x ^^ y is 1.
 * If y > 0, x ^^ y is functionally equivalent to
 { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If y < 0, an integer divide error occurs, regardless of the value 
 of x.

 -----------
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather
 than return values, it should be clearer that they don't apply to
 variables having those values.
 I think this covers everything useful, while avoiding nasty surprises
 like

 double y = x ^^ -1; // looks like reciprocal, but isn't!
 // Yes, this IS the same problem you get with double y = 1/x.
 // But that's doesn't make it acceptable. I have a possible solution
 to that one, too.

 I don't think we can afford to spend much more time on this.
 Is everyone happy now?
Might I enquire as to why the exponent should be allowed to be signed for integer exponentiation? It's been covered several times - it makes no sense. Apart from that - great job!
Because 3^^-1 would become 3^^4294967295 (note: int can be implicitly converted to uint) which then you spend 17 seconds to get 2863311531 and wonder what's going on.
On a non-degenerate base, any exponent greater than 32 (for int) and 64 (for long) would generate overflow. That can be figured with one test. That's why I'm saying the exponential grows fast. The range of useful exponents is extremely limited. Andrei
Dec 09 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Andrei Alexandrescu wrote:
 On a non-degenerate base, any exponent greater than 32 (for int) and 64 
 (for long) would generate overflow. That can be figured with one test.
In fact probably all exponents can be special-cased to use the minimal amount of multiplications (which in the general case is not easy to figure). Andrei
Dec 09 2009
prev sibling parent Don <nospam nospam.com> writes:
Simen kjaeraas wrote:
 On Wed, 09 Dec 2009 09:36:56 +0100, Don <nospam nospam.com> wrote:
 
 CHANGES BASED ON FURTHER COMMENTS
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
   unary and postfix operators.
 The type of x ^^ y is the same as the type of x * y.

 * If either x or y are floating-point, the result is pow(x, y).

 If both x and y are integers, the following rules apply:

   * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 
 1 : 0
   * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
   * If x is the compile-time constant -1 and y is an integer, x^^y is 
 rewritten as (y & 1) ? -1 : 1.

   * If y == 0, x ^^ y is 1.
   * If y > 0,  x ^^ y is functionally equivalent to
      { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
   * If y < 0, an integer divide error occurs, regardless of the value 
 of x.

 -----------
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather 
 than return values, it should be clearer that they don't apply to 
 variables having those values.
 I think this covers everything useful, while avoiding nasty surprises 
 like

 double y = x ^^ -1; // looks like reciprocal, but isn't!
 // Yes, this IS the same problem you get with double y = 1/x.
 // But that's doesn't make it acceptable. I have a possible solution 
 to that one, too.

 I don't think we can afford to spend much more time on this.
 Is everyone happy now?
Might I enquire as to why the exponent should be allowed to be signed for integer exponentiation? It's been covered several times - it makes no sense.
Because I think it would be too restrictive. Consider code like this: for (int n=0; n<10; ++n) { v[n] = x ^^ n; } Requiring it to be a uint would be OK if the compiler's range propagation was near-perfect, but I don't think that's going to happen.
 
 Apart from that - great job!
 
Dec 09 2009
prev sibling next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
On Dec 9, 09 16:36, Don wrote:
 CHANGES BASED ON FURTHER COMMENTS
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 unary and postfix operators.
 The type of x ^^ y is the same as the type of x * y.

 * If either x or y are floating-point, the result is pow(x, y).

 If both x and y are integers, the following rules apply:

 * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 1 : 0
 * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
 * If x is the compile-time constant -1 and y is an integer, x^^y is
 rewritten as (y & 1) ? -1 : 1.

 * If y == 0, x ^^ y is 1.
 * If y > 0, x ^^ y is functionally equivalent to
 { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If y < 0, an integer divide error occurs, regardless of the value of x.

 -----------
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather
 than return values, it should be clearer that they don't apply to
 variables having those values.
 I think this covers everything useful, while avoiding nasty surprises like

 double y = x ^^ -1; // looks like reciprocal, but isn't!
 // Yes, this IS the same problem you get with double y = 1/x.
 // But that's doesn't make it acceptable. I have a possible solution to
 that one, too.

 I don't think we can afford to spend much more time on this.
 Is everyone happy now?
0^^-1 == 0 ?
Dec 09 2009
parent Don <nospam nospam.com> writes:
KennyTM~ wrote:
 On Dec 9, 09 16:36, Don wrote:
 CHANGES BASED ON FURTHER COMMENTS
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
 unary and postfix operators.
 The type of x ^^ y is the same as the type of x * y.

 * If either x or y are floating-point, the result is pow(x, y).

 If both x and y are integers, the following rules apply:

 * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 1 : 0
 * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
 * If x is the compile-time constant -1 and y is an integer, x^^y is
 rewritten as (y & 1) ? -1 : 1.

 * If y == 0, x ^^ y is 1.
 * If y > 0, x ^^ y is functionally equivalent to
 { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If y < 0, an integer divide error occurs, regardless of the value of x.

 -----------
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather
 than return values, it should be clearer that they don't apply to
 variables having those values.
 I think this covers everything useful, while avoiding nasty surprises 
 like

 double y = x ^^ -1; // looks like reciprocal, but isn't!
 // Yes, this IS the same problem you get with double y = 1/x.
 // But that's doesn't make it acceptable. I have a possible solution to
 that one, too.

 I don't think we can afford to spend much more time on this.
 Is everyone happy now?
0^^-1 == 0 ?
Good catch. That line is completely wrong.
Dec 09 2009
prev sibling parent Don <nospam nospam.com> writes:
Don wrote:
 CHANGES BASED ON FURTHER COMMENTS
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between
  unary and postfix operators.
 The type of x ^^ y is the same as the type of x * y.
 
 * If either x or y are floating-point, the result is pow(x, y).
 
 If both x and y are integers, the following rules apply:
 
  * If x is the compile-time constant 0, x^^y is rewritten as (y==0)? 1 : 0
  * If x is the compile-time constant 1, x^^y is rewritten as (y,1)
  * If x is the compile-time constant -1 and y is an integer, x^^y is 
 rewritten as (y & 1) ? -1 : 1.
 
  * If y == 0, x ^^ y is 1.
  * If y > 0,  x ^^ y is functionally equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
  * If y < 0, an integer divide error occurs, regardless of the value of x.
 
 -----------
 Note that by definining the 0,1, -1 cases as "rewriting" rules rather 
 than return values, it should be clearer that they don't apply to 
 variables having those values.
 I think this covers everything useful, while avoiding nasty surprises like
 
 double y = x ^^ -1; // looks like reciprocal, but isn't!
 // Yes, this IS the same problem you get with double y = 1/x.
 // But that's doesn't make it acceptable. I have a possible solution to 
 that one, too.
 
 I don't think we can afford to spend much more time on this.
 Is everyone happy now?
Stuff it, it's too hard to explain. No negative exponents. Anyone who wants (-1) ^^ n where n is negative, will have to write (-1) ^^ abs(n). Sorry, Bill. It ain't worth the complexity just to save 5 characters of syntax sugar.
Dec 09 2009
prev sibling parent reply Jason House <jason.james.house gmail.com> writes:
I think you have a bad corner case:

enum int ct = -1;
immutable rt = -1;

ct ^^ ct // Error (compile time)
rt ^^ ct // Error (compile time)
rt ^^ rt // Error (run time)
ct ^^ rt // Works??? (after rewrite)

Don Wrote:

 Based on everyone's comments, this is what I have come up with:
 
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between 
 multiplication and unary operators.
 
 * The type of x ^^ y is the same as the type of x * y.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error 
 occurs, regardless of the value of x. This error is detected at compile 
 time, if possible.
 * If either x or y are floating-point, the result is pow(x, y).
 --------------------
 Rationale:
 (1) Although the following special cases could be defined...
   * If x == 1,  x ^^ y is 1
   * If x == -1 and y is even, x^^y == 1
   * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in 
 complexity which they introduce. In all other cases, a negative exponent 
 indicates an error; it should be rewritten as (cast(real)x) ^^ y. Making 
 these cases errors makes everything much simpler, and allows the 
 compiler to use range propagation on the value of y to detect most 
 exponentiation errors at compile time. (If those cases are legal, the 
 compiler can't generate an error on x^^-2, because of the possibility 
 that x might be 1 or -1).
 Also note that making it an error leaves open the possibility of 
 changing it to a non-error later, without breaking code; but going from 
 non-error to error would be more difficult.
 
 (2) USE OF THE INTEGER DIVIDE ERROR
 Note that on x86 at least, a hardware "integer divide error", although 
 commonly referred to as "division by zero", also occurs when the DIV 
 instruction, which performs uint = ulong/uint, results in a value 
 greater than uint.max. Raising a number to a negative power does involve 
 a division, so it seems to me not unreasonable to use it for this case 
 as well.
 Note that 0 ^^ -1 is a division by zero.
 This means that, just as you should check that y!=0 before performing 
 x/y, you should check that y>=0 before performing x^^y.
 
 (3) OVERFLOW
 int ^^ int returns an int, not a long. Although a long would allow 
 representation of larger numbers, even doubling the number of bits 
 doesn't help much in avoiding overflow, because x^^y is exponential. 
 Even a floating-point representation can easily overflow:
 5000^5000 easily overflows an 80-bit real.
 So, it's preferable to retain the simplicity that typeof(x^^y) is 
 typeof(x*y).
 
Dec 09 2009
next sibling parent Jason House <jason.james.house gmail.com> writes:
oops, my message was supposed to be in reply to version 3 that included the
rewrite rule for -1 ^^ y

Jason House Wrote:

 I think you have a bad corner case:
 
 enum int ct = -1;
 immutable rt = -1;
 
 ct ^^ ct // Error (compile time)
 rt ^^ ct // Error (compile time)
 rt ^^ rt // Error (run time)
 ct ^^ rt // Works??? (after rewrite)
 
 Don Wrote:
 
 Based on everyone's comments, this is what I have come up with:
 
 --------------------
 x ^^ y is right associative, and has a precedence intermediate between 
 multiplication and unary operators.
 
 * The type of x ^^ y is the same as the type of x * y.
 * If y == 0,  x ^^ y is 1.
 * If both x and y are integers, and y > 0,  x^^y is equivalent to
     { auto u = x; foreach(i; 1..y) { u *= x; } return u; }
 * If both x and y are integers, and y < 0, an integer divide error 
 occurs, regardless of the value of x. This error is detected at compile 
 time, if possible.
 * If either x or y are floating-point, the result is pow(x, y).
 --------------------
 Rationale:
 (1) Although the following special cases could be defined...
   * If x == 1,  x ^^ y is 1
   * If x == -1 and y is even, x^^y == 1
   * If x == -1 and y is odd, x^^y == -1
 ... they are not sufficiently useful to justify the major increase in 
 complexity which they introduce. In all other cases, a negative exponent 
 indicates an error; it should be rewritten as (cast(real)x) ^^ y. Making 
 these cases errors makes everything much simpler, and allows the 
 compiler to use range propagation on the value of y to detect most 
 exponentiation errors at compile time. (If those cases are legal, the 
 compiler can't generate an error on x^^-2, because of the possibility 
 that x might be 1 or -1).
 Also note that making it an error leaves open the possibility of 
 changing it to a non-error later, without breaking code; but going from 
 non-error to error would be more difficult.
 
 (2) USE OF THE INTEGER DIVIDE ERROR
 Note that on x86 at least, a hardware "integer divide error", although 
 commonly referred to as "division by zero", also occurs when the DIV 
 instruction, which performs uint = ulong/uint, results in a value 
 greater than uint.max. Raising a number to a negative power does involve 
 a division, so it seems to me not unreasonable to use it for this case 
 as well.
 Note that 0 ^^ -1 is a division by zero.
 This means that, just as you should check that y!=0 before performing 
 x/y, you should check that y>=0 before performing x^^y.
 
 (3) OVERFLOW
 int ^^ int returns an int, not a long. Although a long would allow 
 representation of larger numbers, even doubling the number of bits 
 doesn't help much in avoiding overflow, because x^^y is exponential. 
 Even a floating-point representation can easily overflow:
 5000^5000 easily overflows an 80-bit real.
 So, it's preferable to retain the simplicity that typeof(x^^y) is 
 typeof(x*y).
 
Dec 09 2009
prev sibling parent reply Don <nospam nospam.com> writes:
Jason House wrote:
 I think you have a bad corner case:
 
 enum int ct = -1;
 immutable rt = -1;
 
 ct ^^ ct // Error (compile time)
 rt ^^ ct // Error (compile time)
 rt ^^ rt // Error (run time)
 ct ^^ rt // Works??? (after rewrite)
No, they will all work. Both rt and ct are manifest constants.
Dec 09 2009
parent Don <nospam nospam.com> writes:
Don wrote:
 Jason House wrote:
 I think you have a bad corner case:

 enum int ct = -1;
 immutable rt = -1;

 ct ^^ ct // Error (compile time)
This would always get rewritten.
 rt ^^ ct // Error (compile time)
 rt ^^ rt // Error (run time)
 ct ^^ rt // Works??? (after rewrite)
No, they will all work. Both rt and ct are manifest constants.
(Actually it's a bit of wierd compiler quirk that const/immutables initialized with literal values are manifest constants; they aren't constants if initialized with anything else. This behaviour may change, but it doesn't affect this scheme). If however rt is just int rt = -1; then ct ^^ ct and ct ^^ rt work, and the other two fail. This is intended.
Dec 09 2009