Emil Jerabek wrote:
Hmm. What I would expect from range checking is that the expression is computed with unlimited precision, and then the result is checked to fit in the destination type (conceptually, it doesn't have to be implemented this way). In other words, the type of the expression is not determined by types of operands, but by its environment.
In this example, Size - 1 should indeed be of type Integer, not because it is Cardinal - Integer, but because the second argument of Inc is declared as Integer (or at least, it is documented as such).
I'm changing the documentation. If it were so, it would preclude using larger integer types than `Integer', which is in fact allowed. So any integer type is allowed for the second argument in fact.
Then adding (Size - 1) to Address should give a result of type SizeType (which is unsigned), because it is assigned back to Address.
I understand that this is essentially impossible to implement efficiently, since intermediary results in complex expressions would have to be computed with extra precision.
Yes. It's a nice idea (though IMHO not directly related with the range checking feature -- which does just what it says, i.e. do additional checks), but very difficult to implement.
However, this problem should not arise in the example above, because the only intermediary result here (Size - 1) is explicitely typed as Integer by the declaration of Inc.
Unfortunately not, see above. `Inc (a, b)' is really equivalent to `a := a + b', while evaluating the reference to `a' only once.
It solves the problem in AddHeapRange, but seems to introduce another failure.
TEST bprealtest.pas: ./a.out: error in exponentiation (Numerical result out of range) (error #700 at 804b08a)
Yes, I get this as well. The problem is this line:
x.r := (Random + 1e-6) * Exp (Random (86) - 43);
`Random' (with one argument) is declared to have a result of type `LongestCard'. Now, after making the constant (43) unsigned, we have a difference of two `LongestCard' values.
Differences of unsigned values are a tricky part. As I've just discussed with Waldek, I don't think we can make them signed (of the same precision), because then we'd have no way of computing differences such as `High (LongestCard) - 1'. While most other type selections also preclude some possibly results, it's at least possible to get them with (range-safe) type conversions, e.g. if the sum of two `LongestInt' values is signed (which is reasonable), one cannot directly get results larger than `High (LongestInt)'. But one can check if both values are >= 0 (only then this can happen), then assign them both to `LongestCard' variables (this is no dangerous type-cast, but just an assignment we know cannot range-fail), and add those.
For differences of large unsigned values, we have no such recourse. Therefore IMHO they need to produce unsigned results. If possible, we can use higher precision for the result, and then a signed type, which covers all possible results. But if the operands are already of the largest type, this isn't possible, and that's the case in this example.
Of course, we could work-around it in this example by an explicit cast or conversion, and perhaps that's actually what we *should* do, given that `Random' is of type `LongestCard'. So this failure is easy to fix, I'm just not certain if there are more serious problems like this ...
Ironically, that's just caused by making a constant to be unsigned. But I don't see a better way to choose the "signedness" of positive constants. Always making them signed (if the range matches) if what we've done before, and it caused problems in heap.pas. Of course, these problems were also easy to work-around, as was noted here. So one could ask which kind of problem is more common. I think it's the one in heap.pas, while here in bprealtest.pas we have a `LongestCard' value that we in fact know must be < 86 (but the compiler doesn't know). So making constants unsigned still seems a bit preferable.
Another idea would be to derive the constant's signedness from the other operand. While probably not too difficult, it wouldn't actually help here, since the other operand *is* unsigned, so it's probably not helpful ...
Finally, one could think of changing the result type of `Random'. Making it `LongestInt' would reduce the available range.
However, we could have different versions for `LongestCard' and plain `Cardinal', and choose the one to use based on the argument type. As we do this for other built-ins (e.g. `Write'), and it would fix this problem, and would probably even be somewhat more efficient, perhaps we should do this ...
Frank