9

Consider the following code on a one's complement architecture:

int zero = 0;
int negzero = -0;
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;
  • What output would the code produce?
  • What lines are defined by the standard, what lines are implementation dependent, and what lines are undefined behaviour?
Vincent
  • 57,703
  • 61
  • 205
  • 388
  • I believe that there's no reason for compiler to generate anything other that 0 when you do "-0". Can't speak for all compilers and standards, but if you look at the assembly which GCC and Clang generate, negzero variable is 0, so you can do pretty much do everything you can do with zero variable. See for yourself: https://goo.gl/bMiUUs –  Dec 08 '15 at 06:22
  • @EliasDaler It should probably not be compiler dependent, rather architecture dependent. Your example is for a architecture that uses 2-complement which hasn't distinct negative zero. For architecture that has it's makes perfect sense to actually generate negative zeros if the language permits. – skyking Dec 08 '15 at 07:19
  • 1
    You example has to use `int`s? Because probably all of us have a one's complement architecture when we talk about floating point with our IEEE-754 compliant processors. If you change the type of your variables from `int` to `double`, the last three lines would be illegal (because the operations of bitwise negation and bit shifting require integral arguments), but the first three would provide some way for you to experiment with. Besides, math.h's functions/macros `signbit()` and `copybit()` takes zero's signs into account. – Paulo1205 Dec 08 '15 at 08:19
  • "negative zero" is a technical term that, in one's complement, refers to the all-1's bit pattern. Not the expression `-0` (which gives positive zero) – M.M Dec 08 '15 at 09:43

3 Answers3

3

Based on my interpretation of the standard:

The C++ standard in §3.9.1/p3 Fundamental types [basic.fundamental] actually throws the ball in the C standard:

The signed and unsigned integer types shall satisfy the constraints given in the C standard, section 5.2.4.2.1.

Now if we go to ISO/IEC 9899:2011 section 5.2.4.2.1 it gives as a forward reference to §6.2.6.2/p2 Integer types (Emphasis Mine):

If the sign bit is zero, it shall not affect the resulting value. If the sign bit is one, the value shall be modified in one of the following ways:

  • the corresponding value with sign bit 0 is negated (sign and magnitude);

  • the sign bit has the value −(2^M) (two’s complement);

  • the sign bit has the value −(2^M − 1) (ones’ complement).

Which of these applies is implementation-defined, as is whether the value with sign bit 1 and all value bits zero (for the first two), or with sign bit and all value bits 1 (for ones’ complement), is a trap representation or a normal value. In the case of sign and magnitude and ones’ complement, if this representation is a normal value it is called a negative zero.

Consequently, the existence of negative zero is implementation defined.

If we proceed further in paragraph 3:

If the implementation supports negative zeros, they shall be generated only by:

  • the &, |, ^, ~, <<, and >> operators with operands that produce such a value;

  • the +, -, *, /, and % operators where one operand is a negative zero and the result is zero;

  • compound assignment operators based on the above cases.

It is unspecified whether these cases actually generate a negative zero or a normal zero, and whether a negative zero becomes a normal zero when stored in an object.

Consequently, it is unspecified whether the related cases that you displayed are going to generate a negative zero at all.

Now proceeding in paragraph 4:

If the implementation does not support negative zeros, the behavior of the &, |, ^, ~, <<, and >> operators with operands that would produce such a value is undefined.

Consequently, whether the related operations result in undefined behaviour, depends on whether the implementation supports negative zeros.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
101010
  • 41,839
  • 11
  • 94
  • 168
  • No, it's very well defined. It should **not** produce negative zero in the example. – skyking Dec 08 '15 at 08:20
  • In the expression `-0`, no operand is a negative zero, and it doesn't use any of the bit-operators. Therefore, shouldn't `negzero` contain NO negative zero? – dyp Dec 08 '15 at 08:48
  • So the OP made a mistake at how to *get* a negative zero. That doesn't render the rest of his question void. – DevSolar Dec 08 '15 at 08:50
  • @dyp No. It's the unary minus applied to `0`, and `0` is probably **not** negative zero (and if it were it would be surprising and also surprising that minus negative zero becomes negative zero again). – skyking Dec 08 '15 at 08:51
  • @skyking I don't see where the unary minus is mentioned – 101010 Dec 08 '15 at 08:54
  • 1
    I'm not convinced that the forward reference in the C standard means that the C++ standard incorporated the portion of the C standard so referenced. – T.C. Dec 08 '15 at 08:55
  • @skyking I think the same reasoning applies to the literal/constant `0`: C lists all possible ways to produce a negative zero, and `0` is not one of them. – dyp Dec 08 '15 at 08:55
  • @T.C. This could be answered if we could ask the authors of §3.9.1/p3 :( – 101010 Dec 08 '15 at 09:00
  • @101010 The `-0` contains two tokens (`-` and `0`) and should parse to an expression meaning unary minus of the octal literal `0`. – skyking Dec 08 '15 at 09:07
  • @dyp Nice point, then `-0` would not be produce negative zero either. – skyking Dec 08 '15 at 09:08
  • @skyking why is it octal? Wouldn't that be 00? – Neil Kirk Dec 08 '15 at 10:58
  • @NeilKirk It's written so in the standard. Basically any `0` followed by zero or more octal digits are octal literals. Decimal literals are `1`-`9` followed by zero ore more decimal digits. It's just a matter of terminology, it's value is zero nevertheless. – skyking Dec 08 '15 at 11:02
3

First of all, your first premise is wrong:

int negzero = -0;

should produce a normal zero on any conformant architecture.

References for that were given in @101010's answer:

3.9.1 Fundamental types [basic.fundamental] §3:

... The signed and unsigned integer types shall satisfy the constraints given in the C standard, section 5.2.4.2.1.

Later in C reference: 5.2.4.2.1 Sizes of integer types

... Forward references: representations of types (6.2.6)

and (still C): 6.2.6 Representations of types / 6.2.6.2 Integer types § 3

If the implementation supports negative zeros, they shall be generated only by:

  • the &, |, ^, ~, <<, and >> operators with arguments that produce such a value;

  • the +, -, *, /, and % operators where one argument is a negative zero and the result is zero;

  • compound assignment operators based on the above cases.

So negzero = -0 is not such a construct and shall not produce a negative 0.

For following lines, I will assume that the negative 0 was produced in a bitwise manner, on an implementation that supports it.

C++ standard does not speak at all of negative zeros, and C standard just say of them that their existence is implementation dependant. I could not find any paragraph explicitly saying whether a negative zero should or not be equal to a normal zero for relational or equality operator.

So I will just cite in C reference : 6.5.8 Relational operators §6

Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.92) The result has type int.

and in C++ 5.9 Relational operators [expr.rel] §5

If both operands (after conversions) are of arithmetic or enumeration type, each of the operators shall yield true if the specified relationship is true and false if it is false.

My interpretation of standard is that an implementation may allow an alternate representation of the integer value 0 (negative zero) but it is still a representation of the value 0 and it should perform accordingly in any arithmetic expression, because C 6.2.6.2 Integer types § 3 says:

negative zeros[...] shall be generated only by [...] the +, -, *, /, and % operators where one argument is a negative zero and the result is zero

That means that if the result is not 0, a negative 0 should perform as a normal zero.

So these two lines at least are perfectly defined and should produce 1:

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

This line is clearly defined to be implementation dependant:

std::cout<<(~negzero)<<(~zero)<<std::endl;

because an implementation could have padding bits. If there are no padding bits, on a one's complement architecture ~zero is negzero, so ~negzero should produce a 0 but I could not find in standard if a negative zero should display as 0 or as -0. A negative floating point 0 should be displayed with a minus sign, but nothing seems explicit for an integer negative value.

For the last 3 line involving relational and equality operators, there is nothing explicit in standard, so I would say it is implementation defined

TL/DR:

Implementation-dependent:

std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;

Perfectly defined and should produce 1:

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
-2

First of all one's complement architectures (or even distinguish negative zero) are rather rare, and there's a reason for that. It's basically easier to (hardware-wise) add two's complement than one's complement.

The code you've posted doesn't seem to have undefined behavior or even implementation defined behavior, it should probably not result in negative zero (or it shouldn't be distinguished from normal zero).

Negative zeros should not be that easy to produce (and if you manage to do that it's implementation defined behavior at best). If it's a ones-complement architecture they would be produced by ~0 (bit-wise inversion) rather than -0.

The C++ standard is rather vague about the actual representation and requirements on the behavior of basic types (which means that the specification only deals with the actual meaning of the number). What this means is that you basically are out of luck in relating internal representation of the number and it's actual value. So even if you did this right and used ~0 (or whatever way is proper for the implementation) the standard still doesn't seem to bother with the representation as the value of negative zero is still zero.

#define zero (0)
#define negzero (~0)
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

the three first lines should produce the same output as if negzero was defined the same as zero. The third line should output two zeros (as the standard requires that 0 to be rendered as 0 without sign-sign). The two last should output ones.

There are some hints (on how to produce negative zeros) that can be found in the C standard which actually mentions negative zero, but I don't think there's any mentions about they should compare less than normal zero. The C-standard suggests that negative zero might not survive storage in object (that's why I avoided that in the above example).

The way C and C++ are related it's reasonable to think that a negative zero would be produced in the same way in C++ as in C, and the standard seem to allow for that. While the C++ standard allows for other ways (via undefined behavior), but no other seem to be available via defined behavior. So it's rather certain that if a C++ implementation is to be able to produce negative zeros in an reasonable way it would be the same as for a similar C implementation.

skyking
  • 13,817
  • 1
  • 35
  • 57
  • See http://stackoverflow.com/a/29974568 (and the rest of the thread) on why C and C++ don't necessarilly agree on all the finer points. Btw, I did not downvote your answer, but would heartily upvote any complete one fully backed by the applicable standards references. – dxiv Dec 08 '15 at 08:26
  • @dxiv I emphasized that reference to C is only hints. Implementations of C/C++ tend to be quite aligned in behavior suggesting that the way to produce negative zeros could be the same. The C++ standard is allowing for such a behavior. In addition there seem to be no other reasonable way to produce negative zeros in the C++ standard. – skyking Dec 08 '15 at 08:32
  • skyking, what is the use of referring to a standard if then you bury it under weasel words? "... _tend_ to be _quite aligned_ in behavior, _suggesting_ that ... _could_..." – hmijail Mar 22 '16 at 19:05
  • @hmijail Because C++ is not C, there's no requirement of a C++ implementation to derive it's behavior from the C standard even in cases where the C++ standard allows for such a behavior. However when you got an implementation that supports both C and C++ it's often the case that the C++ implementation behaves like the C implementation where the standard allows that. – skyking Mar 23 '16 at 06:19
  • I should clarify my point. I didn't downvote, but fully understand it, because weasel words should be red flags in a question about standards. And you are using them in spades. Again in your last comment: "... it's often the case ...". Things either are in a standard, or aren't; and defining when the standard is ambiguous is useful in itself. But being ambiguous about what might or might not be in the standard is useless, or worse, because you can end up convincing someone (yourself?) about something that is not in the standard. False security is worse than knowing that you are unsafe. – hmijail Mar 23 '16 at 08:29
  • @hmijail Was your objection against the answer as such or my comments? I don't think I'm ambiguous with whats in the standard. I rather mean that a normal implementation of the C and C++ standard would normally share behavior where allowed, but of course you would have to consult the documentation for the implementation. Normally the documentation it self would have large portions that applies to both the C and C++ implementations. – skyking Mar 23 '16 at 09:30
  • My objection was about both. Do I need to keep pointing out weasel words? Look at the last paragraph of the answer: "it's reasonable to think", "the standard seem to allow", "it's rather certain" (wtf!?). Another wtf++ : "the C++ standard allows for other ways (via undefined behavior)". Man, that is SO wrong. It's like "cars allow you to travel (via teleporting)". – hmijail Mar 23 '16 at 10:22
  • I'll look over my answer, but why do you think that is SO wrong? The standard allows anything to happen to undefined behavior - this includes compiler documented behavior (such as for example consistently produce a negative zero). Of course the standard may allow it in other ways as well, but as I see it it doesn't provide for a way that is not at least implementation defined. – skyking Mar 23 '16 at 10:49
  • "The C++ standard allows to generate negative zero through Undefined Behavior" is as valid as "The C++ standard allows to generate negative zero through repeatedly kicking your computer until the appropriate bits flip". It's an abuse of the word "allows". – hmijail Mar 27 '16 at 09:04
  • No, it's not. C++ standard allows **anything** to happen when you have undefined behavior. This includes for the implementation to behave in a very specific way, it includes behaving in a implementation defined way too. On the other hand the C++ standard do **not** allow to generate negative zero through repeatedly kicking your computer until the appropriate bitflips occur (or please show me where that's allowed in the standard). – skyking Mar 29 '16 at 05:08
  • @hmijail ...or to quote the standard on undefined behavior: "behavior for which this International Standard imposes no requirements" ... "Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or **program execution in a documented manner characteristic of the environment**" (my emphasization). Do you still think that the standard does not permit program execution in a documented manner characteristic of the environment upon undefined behavior? – skyking Mar 29 '16 at 05:21
  • As you yourself quoted, the Standards **don't say that they ALLOW** anything to happen on UB. They **_just_ "impose no requirements"**. You could get a compiler which documents that case, or you could kick the computer; they don't frikking care because they already washed their hands. And so, whatever assurance the Standard could give you is no longer there. Hope that your compiler won't (documentedly!) change its (documented!) behavior in an update. In summary, you are equating "not explicitly disallowed by the standard" = "allowed by the standard". That's wrong, and IMO, dangerous. – hmijail Mar 29 '16 at 10:14
  • @hmijail To me it means the same. To impose no requirement is the same as allowing anything. If you don't take my word for it the second passage I quoted: "permissible undefined behavior ranges..." - well it doesn't say "allow", it says "permit". Perhaps you could elaborate what "permit" is that "allow" isn't - english isn't my first language, perhaps there's an important difference between the words that alters the meaning totally. Since the standard doesn't define how to produce negative zero you would need to consult your compiler documentation anyway. – skyking Mar 29 '16 at 11:37
  • Look for the differences between "legal", "illegal" and "extralegal". You are wanting to understand "extralegal" as "legal". I am saying that the 3 are different, AND that the common behavior I had seen up to now was to try to stay in the "legal" side of the Standard. – hmijail Mar 29 '16 at 12:15
  • @hmijail I don't really see what you mean by "legal", "illegal" and "extralegal". IANAL, but when it comes to the law you have the legality principle which makes everything allowed unless there's a law against it. If you want to stay on the "legal" side of the standard and not rely on compiler specific behavior then you're basically out of luck. The standard leaves essential parts of the behavior to the implementation to decide. If you think it's dangerous to rely on compiler specifics then you should probably look for another language or accept the danger. – skyking Mar 30 '16 at 11:36
  • "Legality principle"? Really? What next, "the compiler violated my freedom of speech"? Let's recap. You don't see a difference between "allowed" and "not explicitly disallowed". You don't see a problem on talking about the standard by burying everything under weasel words. You admit "IANAL" AND actively avoid seeing things under the language-lawyer lens that is used by BOTH the standard bodies AND the compiler writers (have you even seen how these things are discussed in GCC bug reports?). You seem to think that one must rely on compiler specifics (check `gcc -pedantic`). Let's just disagree. – hmijail Mar 30 '16 at 21:42
  • @hmijail I didn't draw the parallell with legality, you did - I think it's a poor analogy. What I fail to see is the **big** difference between "allowed" and "permitted", as in "permissible undefined behavior ranges..." vs "allowed undefined behavior ranges...". The standard seem to explicitely permit such a behavior (or such behavior is permissible). But according to you it looks like it doesn't "allow"?! – skyking Mar 31 '16 at 06:15
  • @hmijail As for `gcc -pedantic`, please read the manual. It doesn't disallow compiler specific extensions or behavior, the manual explicitely says that (and that wording is part because a bug report I filed against `gcc -pedantic`). You do need to rely on implementation defined behavior as the standard explicitely requires that in the very first (IIRC) translation step. You also rely on it since the standard doesn't define a way to compile your program or even run it - you have to rely on implementation specific behavior there. – skyking Mar 31 '16 at 06:21
  • @hmijail But basically the problem here is that the standard doesn't seem to define a way to produce a distinct negative zero, so if you're going to produce such an integer you will have to resort to consulting the documentation of the compiler or equivalent. – skyking Mar 31 '16 at 06:24
  • Care to share the number or link to your bug report? – hmijail Mar 31 '16 at 06:58
  • @skyking, just in case you forgot, would you please share that bug report where you say you helped define the wording used in the gcc manual for `-pedantic`? I'm really curious! – hmijail Apr 04 '16 at 14:51
  • @hmijail Sorry, but that bug report was filed over a decade ago and I'm not able to find it again (besides my search only finds tickets changed since 2005). – skyking Apr 05 '16 at 05:23
  • Maybe you are not using correctly the gcc bugzilla search? Bug report nr. 1 from 1999 is perfectly accessible... so your bug should be there too. If you give me some hint I can help you look for it. Maybe an user name, mail, etc? – hmijail Apr 06 '16 at 19:08