This project is read-only.

Distinguish between variables of `double` type and inferred (via `var`) from `double` expression

Topics: C# Language Design
Jun 23, 2014 at 4:31 PM
Edited Jun 23, 2014 at 4:32 PM
From what I understand, the present C# specification would allow a compiler given the code:
double totalError = 0.0;
double prevTot = 1.0;
foreach (double newValue in someCollection)
{
  double newTotal = prevTotal + totalError + newValue;
  totalError = (prevTotal - newTotal) - newValue;  // X
  prevTotal = newTotal;
}
to use in the statement marked X a value of newTotal which is more precise than the value which gets stored into prevTotal. While "extra" precision is useful in many cases, in some situations like the one above it can wreak havoc, since it will cause the compiler to mis-compute the amount by which the value that will be stored in prevTotal differs from the arithmetically-correct total.

I would suggest, as a compatible change to the compiler, that with variables whose type is explicitly declared as type double that the compiler be required to behave as though the assignment contained an explicit cast (thus ensuring that the rounding which may need to be performed before updating prevTotal will be performed before computing totalError. Code which doesn't care about such semantics but would prefer to receive any speed boost that might be obtained by omitting such rounding steps could declare variables using var rather than double.

Because the C# specification never promises precision beyond double, this change should not cause affect any specified behavior, but would merely cause behavior to be more strictly specified in certain cases where the present specification is a bit loose. It may cause some existing code to run more slowly than it would under the present rules, but on the flip side would help avoid the possibility that code which relies upon today's jitter forcing a rounding step might be broken by a future version of the jitter.

It is certainly possible for code which needs the explicit rounding step to request it using typecast syntax; semantically, however, I would think it cleaner to say that values of type double are automatically converted to a compiler-internal type DoubleRValue which may arbitrarily round to any degree of precision that's no worse than double, and are then implicitly converted to double. Using var to define a variable equal to an expression of double would thus create a variable of type DoubleRValue rather than double. Given code like:
var FiftyOne = 51.0;
var FiftyOnePercent = FiftyOne/100.0;
var OnePercent = FiftyOnePercent-0.5;
the logical semantic meaning is that FiftyOnePercent should be whatever type was yielded by the computation 51.0/100.0; if that expression happens to yield something more precise than a double, then it would be entirely logical for the value held by FiftyOnePercent to be likewise more precise.
Jun 25, 2014 at 10:03 PM
Java has this with strictfp, but I don't know about any usage stats.
Jun 26, 2014 at 8:22 PM
Something like strictfp forces all intermediate computations to be rounded to 53-bit mantissa (or fewer, for values below 2^-1024), and turns overly large values into +/- Inf. Making something like Kahan summation shouldn't require going that far. At many steps, extra precision would be beneficial if not harmless; only in a few places is forced-rounding necessary. Requiring a cast for semantically-correct behavior, and having the lack of a cast silently give semantically-incorrect behavior, seems more error-prone than saying that things whose type is explicitly defined as double should behave as containers that hold precisely that type.
Jun 28, 2014 at 9:51 PM
Edited Jun 28, 2014 at 9:52 PM
On the other hand, currently C# has a strict rule "var is nothing but a syntactic sugar", or more precisely "replacing var with an actual variable type doesn't affect the program", while with your suggestion it will be not so any more (but only in this specific case). I would consider this not entirely beautiful design decision.

Maybe a special keyword (like checked) would be a better syntax for your feature?
Jun 29, 2014 at 9:30 PM
I agree with VladD: If a change like this is implicit in the syntax, or redefines an old, stable convention, it makes reasoning about the code harder both for programmers and tools. With var/double, it's way too easy for this feature to no longer be invoked by someone changing it to what is in every other situation equivalent code.
Jun 30, 2014 at 6:12 PM
JesperTreetop wrote:
I agree with VladD: If a change like this is implicit in the syntax, or redefines an old, stable convention, it makes reasoning about the code harder both for programmers and tools. With var/double, it's way too easy for this feature to no longer be invoked by someone changing it to what is in every other situation equivalent code.
Consider the following:
double x=1E15; y=0.1;
double b0 = (double)(x+y);
var b1 = (double)(x+y);
double b2 = (x+y);
var b3 = (x+y);
double d0=b0-x;
double d1=b1-x;
double d2=b2-x;
double d3=b3-x;
Under the present rules, not only are b0 and b1 semantically equivalent, but the specification guarantees that they must receive the same value. By contrast, b2 and b3 are allowed to receive different values, despite being semantically equivalent [which would be observable by looking at d0, d1, d2, d3]; either or both may independently match b0, though neither is required to do so. My proposed change would state that b2 would be required to match b0, rather than merely being allowed to do so, on the basis that I would regard the failure of b2 to match b0 to be more "surprising" than the failure of b3 to do so.

The way the C# code actually behaves, adding two variables of type double may yield a result of an "arbitrarily rounded number" type which is implicitly convertible to double. If one were to regard the operators as returning the type they actually return, then one should expect that the type of b3 would be "arbitrarily rounded number" [since that's the type of the right-hand expression], while b0, b1, and b2 would all be double. The change would not be with the meaning of var, but rather would be with the fact that the result type would be "arbitrarily rounded number" rather than "thing which calls itself ``System.Double` but is actually "arbitrarily rounded number"]

The biggest problem I can see with the change is that code which would work 100% reliably on a compiler which implements the change might sometimes fail when compiled with one that doesn't. That is a reasonable concern, but even if programmers were told that they should use explicit casts in places where they want to force rounding, that wouldn't imply that a compiler shouldn't force rounding in places where it is allowed to do so, and where failure to do so would likely be "surprising".