Having never used C# to do serious mathematical work, I have just noticed something which left me confused... If it is true that
double Test = Math.Sqrt(UInt64.MaxValue)
is equal to 4294967296.0, that is,
UInt32.MaxValue + 1, why is it that
ulong Test2 = UInt32.MaxValue * UInt32.MaxValue;
is equal to 1? At first sight it seems to me that overflow occurs here... But why is that since that product should fit a
Thank you very much.
the first one happens because double doesn't have 64 mantissa bits, but only around 53. So
UInt64.MaxValue will be rounded to
UInt64.MaxValue+1 during the conversion to
double. And the
Sqrt of that is obviously 2^32.
double can represent any value from
(U)Int32 exactly, but some of the larger 64 bit integers can't be represented as
The second one happens because you do the multiplication before casting to
UInt64, i.e. it happens as
UInt32, which obviously overflows. Cast at least one of your operands to
UInt64 and the problem will disappear.
ulong Test2 = UInt32.MaxValue * UInt32.MaxValue
Could be translated to :
UInt32 __temp = UInt32.MaxValue * UInt32.MaxValue; // Overflow ulong Test2 = (ulong)__temp;
as thee operation on the left of the = sign is always done without any inference on the type on the right obviously not what you want...
It should have been
ulong Test2 = (long)UInt32.MaxValue * UInt32.MaxValue;
That will be treated as :
ulong Test2 = (long)UInt32.MaxValue * (long)UInt32.MaxValue;
And will work.
The rules are in section 16.4.2 of the C# norm :
Numeric promotion consists of automatically performing certain implicit conversions of the operands of the predefined unary and binary numeric operators. Numeric promotion is not a distinct mechanism, but rather an effect of applying overload resolution to the predefined operators. Numeric promotion specifically does not affect evaluation of user-defined operators, although user-defined operators can be implemented to exhibit similar effects.
As an example of numeric promotion, consider the predefined implementations of the binary * operator:
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y); void operator *(long x, ulong y); void operator *(ulong x, long y); float operator *(float x, float y); double operator *(double x, double y); decimal operator *(decimal x, decimal y);
When overload resolution rules (§14.4.2) are applied to this set of operators, the effect is to select the first of the operators for which implicit conversions exist from the operand types. [Example: For the operation b * s, where b is a byte and s is a short, overload resolution selects operator *(int, int) as the best operator. Thus, the effect is that b and s are converted to int, and the type of the result is int. Likewise, for the operation i * d, where i is an int and d is a double, overload resolution selects operator *(double, double) as the best operator. end example]