Why dividing int.MinValue by -1 threw OverflowException in unchecked context?
Asked Answered
D

3

49
int y = -2147483648;
int z = unchecked(y / -1);

The second line causes an OverflowException. Shouldn't unchecked prevent this?

For example:

int y = -2147483648;
int z = unchecked(y * 2);

doesn't cause an exception.

Director answered 27/10, 2014 at 18:46 Comment(25)
looks like a bug. what .net version are you running your code at?Ansel
can you not use a long?Treasure
@DJBurb The question isn't asking how to work around it, but rather to explain the behavior. The code shouldn't throw an exception.Eyeleteer
@hexa without unchecked also, it won't throw.Cana
@hexa It's not overflowing a double, only an int.Eyeleteer
@Eyeleteer Even if that overflows double, it won't throw exception. floating point overflows will not result in exception IIRC.Cana
The only conceivable way to overflow an int when performing division is with the exact two operands listed here, namely dividing int.MinValue by -1. Every single other pair of operands does not overflow, so they likely just didn't think of this case and incorrectly assumed that integer division would never overflow.Eyeleteer
did you read the documentation.. just curious unchecked reference MSDNAbsorbing
@DJKRAZE that link is in Spanish :PAnticlimax
if you use google it pops up an option to translate.. I will post the link MSDN English version unchecked referenceAbsorbing
Although unchecked is supposed to avoid overflow exceptions, C# would have to generate code around each unchecked division to check for this specific case before calling idiv - that would slow down division in unchecked contexts, which would be rather unintuitive, since unchecked is supposed to remove extra checks and improve performance..Mescaline
@HansPassant I understand your removing your answer, though it is unfortunate. (Something might be wrong with the system here...)Gastineau
It got heavily downvoted. The C# crowd isn't that interested in how processors work.Snowden
@HansPassant - they aren't real developers then!Aware
@HansPassant I agree with the sentiment, but your answer was also a red herring as to the real cause of the behavior in OP's post.Magner
@Magner I Disagree. Hans' answer is probably the reason for the specs (in the currently only answer) explaining why it is so.Gastineau
Exactly. They could not fix this problem without making divisions very expensive so they had to document the special case.Snowden
@HansPassant: You got more upvotes than downvotes, I think you should bring your answer back. Especially since it's kind of the second/other half of Servy's answer.Cirilo
Meh, I'll keep it in my back pocket until somebody asks why the special rule exists.Snowden
@HansPassant if your answer was related to the accepted answer, that seems like a strange attitude from someone that I'm sure most consider an authority on the matter.Magner
@Blorgbeard: Division is inherently slow enough that adding a small amount of conditional logic shouldn't make it any worse. Actually, I'm a big surprised that .NET would actually perform the division by a constant -1 with a divide instruction, rather than use some other faster mechanism which (in an unchecked context) wouldn't care about "overflow".Monochloride
@Monochloride I doubt it's worth all the "Why is C# adding these extraneous CMPs and JNEs to my tight loop containing a divide instruction, only when it's unchecked? Isn't unchecked supposed to be faster?"..Mescaline
@Blorgbeard: If the divisor is a constant, the divide instruction could likely be replaced by other faster instructions (I would guess that for most CPUs, there would be every possible constant dividend an instruction sequence without DIV which would be faster than IDIV). Even for non-constant divisors some conditional logic might improve performance in many typical cases, especially if branches were properly predicted.Monochloride
The unchecked tag has no mention of C# but instead mentions javac - perhaps it would be a good idea to update the tag excerpt or untag it from this question (I can't do it as I've never used the unchecked keyword). Similarly checked is about HTML...Condom
Related: How can I stop OverflowException being thrown on integer division?Blaine
S
36

This is not an exception that the C# compiler or the jitter have any control over. It is specific to Intel/AMD processors, the CPU generates a #DE trap (Divide Error) when the IDIV instruction fails. The operating system handles the processor trap and reflects it back into the process with a STATUS_INTEGER_OVERFLOW exception. The CLR dutifully translates it to a matching managed exception.

The Intel Processor Manual is not exactly a gold mine of information about it:

Non-integral results are truncated (chopped) towards 0. The remainder is always less than the divisor in magnitude. Overflow is indicated with the #DE (divide error) exception rather than with the CF flag.

In English: the result of the signed division is +2147483648, not representable in an int since it is Int32.MaxValue + 1. Otherwise an inevitable side-effect of the way the processor represents negative values, it uses two's-complement encoding. Which produces a single value to represent 0, leaving an odd number of other possible encodings to represent negative and positive values. There is one more for negative values. Same kind of overflow as -Int32.MinValue, except that the processor doesn't trap on the NEG instruction and just produces a garbage result.

The C# language is of course not the only one with this problem. The C# Language Spec makes it implementation defined behavior (chapter 7.8.2) by noting the special behavior. No other reasonable thing they could do with it, generating the code to handle the exception surely was considered too unpractical, producing undiagnosably slow code. Not the C# way.

The C and C++ language specs up the ante by making it undefined behavior. That can truly get ugly, like a program compiled with the gcc or g++ compiler, typically with the MinGW toolchain. Which has imperfect runtime support for SEH, it swallows the exception and allows the processor to restart the division instruction. The program hangs, burning 100% core with the processor constantly generating #DE traps. Turning division into the legendary Halt and Catch Fire instruction :)

Snowden answered 27/10, 2014 at 19:2 Comment(4)
But the C# language shouldn't have generated code that would result in such an error in the first place (or it should have swallowed the exception and handled the situation rather than re-throwing), in order to fulfill the contract of unchecked. The fact that C# re-throws the error when the processor throws it doesn't really explain anything.Eyeleteer
To your edit, the whole point of unchecked is to not throw overflow exceptions when an integer operation overflows, but rather to wrap.Eyeleteer
The result of neg on -2147483648 isn't garbage, it is in fact +2147483648 (when interpreted unsigned)Degenerate
Hmya, that's how you blow up a rocket with software.Snowden
E
33

Section 7.72 (Division Operator) of the C# 4 specs states:

If the left operand is the smallest representable int or long value and the right operand is –1, an overflow occurs. In a checked context, [...]. In an unchecked context, it is implementation-defined as to whether a System.ArithmeticException (or a subclass thereof) is thrown or the overflow goes unreported with the resulting value being that of the left operand.

So the fact that this throws an exception in an unchecked context is not in fact a bug, since the behavior is implementation-defined.

Eyeleteer answered 27/10, 2014 at 19:12 Comment(16)
Maybe you can also quote Hans Passant's answer, which provides implementation details, i.e. exactly why it did not work for the OP? Saying that "because behavior is undefined, we are seeing some weird stuff" is hardly an answer by itself.Cirilo
@Neolisk But it is, that's the point. The specs define what behavior you can expect out of a C# program. That a particular processor instruction happens to throw an error when called with these operands only means something if you know that this C# code results in that exact processor instruction being called as the full implementation of that C# code (which you don't know). Someone could run that same C# code on a non-intel machine and have it return int.MinValue. When writing this C# code you really do need to assume it could have either behavior, you cannot even assume it will throw.Eyeleteer
Interesting. In the old version of .NET the behaviour was defined: If the left operand is the smallest representable int or long value and the right operand is –1, an overflow occurs. A System.OverflowException is always thrown in this situation, regardless of whether the operation occurs in a checked or an unchecked context.Mosenthal
@Servy: I understand your point. My point is that OP ran their program not on a spherical computer in vacuum. It was a particular hardware and software. According to Hans's answer, this behavior is specific to Intel/AMD, so you could specify why OP's problem happened - it was because they were running C# on Intel/AMD. Yes, it could be that other architectures provide similar behavior, but it's best to make a guess, than leaving it "undefined". If they are not using Intel/AMD, they could comment underneath, you can then adjust your answer.Cirilo
@Neolisk Clearly the OP already knows which of the two options his particular CPU happened to choose given that he stated the code is throwing for him. Saying that the CPU he's using happens to throw in this situation rather than return the first operand seems rather pointless. Either way, when using this C# code he needs to write the code as if the behavior is undefined, rather than relying on either behavior, regardless of what CPU he's using.Eyeleteer
@Servy: I agree - the code should not depend on the architecture. But the question was regarding the OverflowException, which is architecture-specific. In the spirit of StackOverflow, an ideal answer would list all existing architectures, where this error would occur as part of "undefined" behavior. I understand this is hard to gather, so listing at least one example would be nice. Otherwise, suppose OP's computer flew away into space after dividing by (-1), is it acceptable? Well, it fits into undefined behavior. But you probably want to clarify that C# compiler has nothing to do with it. :)Cirilo
A good answer needs both. First it needs to cite the spec to figure out if this is behaviour is intentional or a bug. Then it needs to describe the CPU behaviour to justify this unintuitive design decision.Efferent
@Neolisk The C# specs doesn't say that anything can possibly happen, including the universe exploding, when you make this call. It specifically states that it either returns the first operand or throws. You can rely on one of those two things happening, you just don't know which. If I'm writing this code I don't see how it would possibly be useful to know which CPUs will have which behavior. You have to write the code to support both cases no matter what. It's not like you should write the code to just support one and then say, "you can only run this program on an intel machine".Eyeleteer
@Efferent The actual behavior is obvious from the result. When running the code an exception is thrown because the CPU instruction throws an exception. Stating that seems obvious. The question is why a CPU instruction that throws is being called here, not whether or not the CPU instruction throws. Who do you really think is going to benefit from reading, "The code throws because the implementation of that behavior throws an exception."? It's effectively begging the question.Eyeleteer
@Eyeleteer It's not obvious that it's the division instruction that throws, instead of an explicit check throwing the exception. It's also not obvious if there is an alternative instruction that wouldn't throw. I suspect that the reason for this design decision is that different behaviour would have incurred a performance loss on a x86 CPU. I assume that without the performance hit, the designers would have chosen to not throw an exception. But I'm not sure about that, since as supercat notes division is slow in anyways and if you trap the exception you only incur the cost in the rare min/-1 case.Efferent
@Efferent It is obvious that it's the instruction that throws. If the C# code were adding in the check/throw then the behavior wouldn't be undefined, it would unquestionably throw. As to why the C# team choose to use this implementation, I'm not going to try to guess at why they decided to do this and put that in an answer. Performance could be the reason, or it could not be, either way I'm in no position to comment on it; only someone on the C# team would be.Eyeleteer
@Servy: It could be that historically .NET would always use a divide instruction (which would overflow) and so it was defined that way, and at some later time the JITter started optimizing for division by certain constants. For compatibility, upgraded JITter versions for older frameworks might include an overflow check for awhile, but if nobody feels the overflow check in that narrow context serves any useful purpose, documenting the behavior as undefined would allow the check to be eliminated in future.Monochloride
Just a nitpick about the wording. The specs say it is implementation-defined, but you conclude that it is undefined. Implementation-defined means that there is a defined behavior, just that it may vary depending on the system, etc., while undefined means there is no way to reason about its behavior.Catlin
@nhahtdh: to me it does not contradict what is said above, from a point of view of the specification it is undefined.Lawerencelawes
Also note this: If you change the declaration of y to be a const, the division is performed at compile-time of course. And the current implementation of the C# compiler allows this (given that you keep the unchecked keyword explicit); no compile-time error. So you have a situation where removing a const modifier will change the outcome of the program. The C# compiler and the runtime do not agree on how to divide integers.Blaine
@nhahtdh: The term "undefined behavior", when used in some common contexts, means anything can happen, while "implementation-defined" would imply that differing implementations may differ but any particular implementation will be consistent. Neither condition applies here. A better term might be "unspecified", or "an arbitrary selection from among defined alternatives", meaning that it would be perfectly legitimate for a loop which tries to computes -2147483648/-1 to get the value -2147483648 on the first 23 times and then throw an exception on the 24th.Monochloride
D
10

According to section 7.8.2 of the C# Language Specification 5.0 we have the following case:

7.8.2 Division operator
For an operation of the form x / y, binary operator overload resolution (§7.3.4) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator. The predefined division operators are listed below. The operators all compute the quotient of x and y.

  • Integer division:
    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    If the value of the right operand is zero, a System.DivideByZeroException is thrown. The division rounds the result towards zero. Thus the absolute value of the result is the largest possible integer that is less than or equal to the absolute value of the quotient of the two operands. The result is zero or positive when the two operands have the same sign and zero or negative when the two operands have opposite signs. If the left operand is the smallest representable int or long value and the right operand is –1, an overflow occurs. In a checked context, this causes a System.ArithmeticException (or a subclass thereof) to be thrown. In an unchecked context, it is implementation-defined as to whether a System.ArithmeticException (or a subclass thereof) is thrown or the overflow goes unreported with the resulting value being that of the left operand.
Denn answered 27/10, 2014 at 19:22 Comment(2)
How come you ended up quoting a different section of the specs, but with the same words?Cirilo
@Neolisk Different version of C#. He's quoting 5.0, I quoted 4.0. Obviously the relevant content isn't any different.Eyeleteer

© 2022 - 2024 — McMap. All rights reserved.