www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Re: Semantics of ^^

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)


 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