digitalmars.D - Semantics of ^^
- Don (47/47) Dec 08 2009 Based on everyone's comments, this is what I have come up with:
- bearophile (13/23) Dec 08 2009 You can rewrite both of those as:
- Don (7/36) Dec 08 2009 That's an interesting one.
- bearophile (4/6) Dec 08 2009 Probably not :-)
- BCS (2/47) Dec 08 2009 Maybe. I think it's worth looking into.
-
KennyTM~
(5/28)
Dec 08 2009
Other non-essential special cases are 2^^n == 1<
- Denis Koroskin (3/50) Dec 08 2009 Looks solid. Just a quick question: is ^^ going to stay as opPow, or is ...
- Lars T. Kyllingstad (9/58) Dec 08 2009 This is reasonable.
- Steven Schveighoffer (21/33) Dec 08 2009 If x and y are both integral and x is 2, then the operation becomes 1 <<...
- Don (6/26) Dec 08 2009 And if x is 4, it becomes 1 << 2*y, etc.
- Steven Schveighoffer (10/37) Dec 08 2009 2^^n would be a very common entity in programming, it's definitely much ...
- Bill Baxter (39/68) Dec 08 2009 Is that consistent with math? I think in math they usually write
- Phil Deets (6/33) Dec 08 2009 I was also wondering about this. If it can't be detected at compile time...
- Phil Deets (4/7) Dec 08 2009 Oops, ~(n&1)+1 doesn't work, but ~((n&1)<<1)+2 does.
- Bill Baxter (4/9) Dec 08 2009 Right, and you can always write x^^y as pow(x,y). A major point of
- Don (2/24) Dec 08 2009 No, it's a compile-time error.
- Phil Deets (9/34) Dec 08 2009 It can't be a compile-time error if it can't be detected at compile time...
- Phil Deets (5/38) Dec 08 2009 Maybe you meant, if you can't prove it's positive, it's an error. If so,...
- Don (3/47) Dec 08 2009 Read the proposal. It's a runtime divide error.
- Don (9/86) Dec 08 2009 No. 0^^0 is 1, as well.
- Bill Baxter (17/58) Dec 09 2009 Is it? That's rather embarrassing for Wolfram Alpha, then (and
- Lars T. Kyllingstad (7/15) Dec 09 2009 It's not an either-or question. There are different definitions of 0^0,
- Chris Nicholson-Sauls (6/15) Dec 09 2009 Confirmed with Mathematica.
- Don (3/16) Dec 08 2009 Hmm. Several other languages give it that precedence. But you're right,
- Lars T. Kyllingstad (5/22) Dec 09 2009 Good catch, I missed that one in the original proposal. Between unary
- Don (26/26) Dec 09 2009 CHANGES BASED ON FURTHER COMMENTS
- Rainer Deyke (9/12) Dec 09 2009 I don't care if x^^y with y < 0 is 0, a runtime error, or even undefined
- Lars T. Kyllingstad (4/15) Dec 09 2009 I don't think it's a problem: Either it works as expected, or it causes
- Rainer Deyke (5/15) Dec 09 2009 Not quite. Under the proposal, -1^^-1 works (i.e. produces the correct
- Don (2/15) Dec 09 2009 It won't pass CTFE.
- Rainer Deyke (9/14) Dec 09 2009 pure int f() { return -1; }
- Don (17/30) Dec 09 2009 No, it doesn't work. It's exactly the same as:
- Don (13/56) Dec 09 2009 You can see this effect in action in the current version of DMD, because...
- Rainer Deyke (9/11) Dec 09 2009 That's a very fine distinction. One that may not survive future
- Don (14/24) Dec 09 2009 Yes. This is a very narrow special case. Bill Baxter and others have
- Rainer Deyke (9/27) Dec 09 2009 Yes.
- Lars T. Kyllingstad (5/20) Dec 09 2009 That depends on which of these two rules gets the higher precedence:
- Don (3/17) Dec 09 2009 No. That's why I wrote it as a transformation rule. CTFE is not
- Simen kjaeraas (7/36) Dec 09 2009 Might I enquire as to why the exponent should be allowed to be signed
- KennyTM~ (4/43) Dec 09 2009 Because 3^^-1 would become 3^^4294967295 (note: int can be implicitly
- bearophile (5/6) Dec 09 2009 I agree, no unsigned values, please.
- Andrei Alexandrescu (6/56) Dec 09 2009 On a non-degenerate base, any exponent greater than 32 (for int) and 64
- Andrei Alexandrescu (4/6) Dec 09 2009 In fact probably all exponents can be special-cased to use the minimal
- Don (8/53) Dec 09 2009 Because I think it would be too restrictive. Consider code like this:
- KennyTM~ (2/28) Dec 09 2009 0^^-1 == 0 ?
- Don (2/39) Dec 09 2009 Good catch. That line is completely wrong.
- Don (6/39) Dec 09 2009 Stuff it, it's too hard to explain.
- Jason House (8/60) Dec 09 2009 I think you have a bad corner case:
- Jason House (2/67) Dec 09 2009
- Don (2/11) Dec 09 2009 No, they will all work. Both rt and ct are manifest constants.
- Don (10/22) Dec 09 2009 (Actually it's a bit of wierd compiler quirk that const/immutables
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
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
bearophile wrote:Don: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?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
Dec 08 2009
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
Hello Don,bearophile wrote:Maybe. I think it's worth looking into.Don: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?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
Dec 08 2009
On Dec 8, 09 19:16, bearophile wrote:Don: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.)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
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
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
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
Steven Schveighoffer wrote:On Tue, 08 Dec 2009 05:32:26 -0500, Don <nospam nospam.com> wrote: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.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 << yAlso, 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
On Tue, 08 Dec 2009 10:17:30 -0500, Don <nospam nospam.com> wrote:Steven Schveighoffer wrote: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.On Tue, 08 Dec 2009 05:32:26 -0500, Don <nospam nospam.com> wrote: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.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 << yok good. -SteveAlso, 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
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=touse range propagation on the value of y to detect most exponentiation err=orsat compile time. (If those cases are legal, the compiler can't generate a=nerror 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=itto 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
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]I was also wondering about this. If it can't be detected at compile time, is the result 0?* 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 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.[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]
Dec 08 2009
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
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: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. --bbI 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.
Dec 08 2009
Phil Deets wrote:On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com> wrote:No, it's a compile-time error.On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote: [snip]I was also wondering about this. If it can't be detected at compile time, is the result 0?* 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.
Dec 08 2009
On Tue, 08 Dec 2009 22:26:10 -0500, Don <nospam nospam.com> wrote:Phil Deets wrote: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/On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com> wrote:No, it's a compile-time error.On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote: [snip]I was also wondering about this. If it can't be detected at compile time, is the result 0?* 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.
Dec 08 2009
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: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/Phil Deets wrote: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?On Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com> wrote:No, it's a compile-time error.On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote: [snip]I was also wondering about this. If it can't be detected at compile time, is the result 0?* 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.
Dec 08 2009
Phil Deets wrote:On Wed, 09 Dec 2009 02:40:44 -0500, Phil Deets <pjdeets2 gmail.com> wrote:Sorry. I misread that. I wrote that at 3am.On Tue, 08 Dec 2009 22:26:10 -0500, Don <nospam nospam.com> wrote:Phil Deets wrote: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 codeOn Tue, 08 Dec 2009 12:42:33 -0500, Bill Baxter <wbaxter gmail.com> wrote:No, it's a compile-time error.On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote: [snip]I was also wondering about this. If it can't be detected at compile time, is the result 0?* 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.Read the proposal. It's a runtime divide error.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.
Dec 08 2009
Bill Baxter wrote:On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:Hmm, that's what'sBased 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_seriesWhat's the rationale for going against math here?No. 0^^0 is 1, as well.* 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.Integer division is a well defined operation. 2^^-1 is *never* sensible. I just find it unexpected that* 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.a language would single out exponentiation for this kind of treatment.Yes, but n is always positive in those formulae.* 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).Rubbish. 0^^0 is 1.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^0and 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.Do you realize you are asking for ^^ to be removed? I'm not joking. Walter's ready to pull it out. Please reconsider.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.
Dec 08 2009
On Tue, Dec 8, 2009 at 7:24 PM, Don <nospam nospam.com> wrote: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^0No. =A00^^0 is 1, as well.* If y =3D=3D 0, =A0x ^^ y is 1.Need to mention what happens when x is also 0.Well, non-negative actually. But yeh, that is true as far as the ones I know, too.Yes, but n is always positive in those formulae.(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).Yeh, again Wolfram steered me wrong there, if you correct about that.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^0Rubbish. 0^^0 is 1.ngand 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=oit to a non-error later, without breaking code; but going from non-error t=r'sDo you realize you are asking for ^^ to be removed? I'm not joking. Walte=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.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
Bill Baxter wrote:On Tue, Dec 8, 2009 at 7:24 PM, Don <nospam nospam.com> wrote: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. -LarsIs 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^0No. 0^^0 is 1, as well.* If y == 0, x ^^ y is 1.Need to mention what happens when x is also 0.
Dec 09 2009
Bill Baxter wrote:On Tue, Dec 8, 2009 at 7:24 PM, Don <nospam nospam.com> wrote:Confirmed with Mathematica. In[2]:= 0^0 During evaluation of In[2]:= Power::indet: Indeterminate expression 0^0 encountered. >> Out[2]= Indeterminate -- Chris NSIs 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^0No. 0^^0 is 1, as well.* If y == 0, x ^^ y is 1.Need to mention what happens when x is also 0.
Dec 09 2009
Bill Baxter wrote:On Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:Hmm. Several other languages give it that precedence. But you're right, it should be even higher than unary. Between unary and postfix ?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?
Dec 08 2009
Don wrote:Bill Baxter wrote: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. -LarsOn Tue, Dec 8, 2009 at 2:32 AM, Don <nospam nospam.com> wrote:Hmm. Several other languages give it that precedence. But you're right, it should be even higher than unary. Between unary and postfix ?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?
Dec 09 2009
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
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
Rainer Deyke wrote:Don wrote:I don't think it's a problem: Either it works as expected, or it causes a compile-time error. -LarsNote 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.
Dec 09 2009
Lars T. Kyllingstad 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. -- Rainer Deyke - rainerd eldwood.comI 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.
Dec 09 2009
Rainer Deyke wrote:Lars T. Kyllingstad wrote:It won't pass CTFE.Rainer Deyke wrote:Not quite. Under the proposal, -1^^-1 works (i.e. produces the correct result) at compile time but fails at runtime.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.
Dec 09 2009
Don wrote:Rainer Deyke wrote: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.comNot 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
Rainer Deyke wrote:Don wrote: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.Rainer Deyke wrote:pure int f() { return -1; } void g(int)(int); g!(f() ^^ f())(0); // Works.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.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
Don wrote:Rainer Deyke wrote: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. }Don wrote: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.Rainer Deyke wrote:pure int f() { return -1; } void g(int)(int); g!(f() ^^ f())(0); // Works.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.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
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
Rainer Deyke wrote:Don wrote: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 futuref() ^^ 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.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. Havingdifferent 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
Don wrote:Rainer Deyke wrote: One that may not survive futureNo.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.(I /should/ be removed. HavingYes.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.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.comIt 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
Rainer Deyke wrote:Lars T. Kyllingstad wrote:That depends on which of these two rules gets the higher precedence: 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.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.* 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
Rainer Deyke wrote:Lars T. Kyllingstad wrote:No. That's why I wrote it as a transformation rule. CTFE is not permitted to perform transformations, it can only evaluate.Rainer Deyke wrote:Not quite. Under the proposal, -1^^-1 works (i.e. produces the correct result) at compile time but fails at runtime.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.
Dec 09 2009
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
On Dec 9, 09 17:25, Simen kjaeraas wrote:On Wed, 09 Dec 2009 09:36:56 +0100, Don <nospam nospam.com> wrote: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.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!
Dec 09 2009
KennyTM~:Because 3^^-1 would become 3^^4294967295I 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
KennyTM~ wrote:On Dec 9, 09 17:25, Simen kjaeraas 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. That's why I'm saying the exponential grows fast. The range of useful exponents is extremely limited. AndreiOn Wed, 09 Dec 2009 09:36:56 +0100, Don <nospam nospam.com> wrote: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.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!
Dec 09 2009
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
Simen kjaeraas wrote:On Wed, 09 Dec 2009 09:36:56 +0100, Don <nospam nospam.com> wrote: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.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!
Dec 09 2009
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
KennyTM~ wrote:On Dec 9, 09 16:36, Don wrote:Good catch. That line is completely wrong.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
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
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
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
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
Don wrote:Jason House wrote:This would always get rewritten.I think you have a bad corner case: enum int ct = -1; immutable rt = -1; ct ^^ ct // Error (compile time)(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.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