8.6.1.1230 DNEGATE d-negate DOUBLE

( d1 -- d2 )

d2 is the negation of d1.

Testing:

T{   0. DNEGATE ->  0. }T
T{   1. DNEGATE -> -1. }T
T{  -1. DNEGATE ->  1. }T
T{ max-2int DNEGATE -> min-2int SWAP 1+ SWAP }T
T{ min-2int SWAP 1+ SWAP DNEGATE -> max-2int }T

ContributeContributions

JimPetersonavatar of JimPeterson [400] Possible Reference ImplementationSuggested reference implementation2025-08-12 12:25:58

I suggest the following (somewhat obscure) reference implementation for DNEGATE:

: DNEGATE ( d1 -- d2 ) >R NEGATE DUP 0<> >R - ;

It's not as straightforward as 0. 2SWAP D-, and that sort of brings up the question of the purpose of reference implementations in this document. Do we present these to the reader as an easy means of filling gaps in an implementation, or are we more attempting to directly and precisely describe a word's behavior? While I prefer the above implementation that I'm suggesting when attempting to implement DNEGATE without raw code, the 0. 2SWAP D- version is far more communicative about what the word does (not that it's that obscure, in this case) but is not too useful for implementing words if D- is then suggested to be implemented as DNEGATE D+.

It is certainly more interesting to find the "clever" and efficient implementations of a word, but it may not be as instructive.

EricBlakeavatar of EricBlake

: DNEGATE ( d1 -- d2 ) >R NEGATE DUP 0<> >R - ;

That assumes twos-complement utilizing all bits of the lower cell (future Forth is headed that way, but Forth-2012 still permits sign-magnitude or ones-complement, as well as implementations where valid u values are capped at fewer than the full bits in the cell). Maybe we could also show three implementations, along the lines of A.3.2.1:

: DNEGATE ( d1 -- d2 ) >R NEGATE DUP 0<> >R - ; \ correct for twos complement
: DNEGATE ( d1 -- d2 ) 2DUP OR IF SWAP INVERT SWAP INVERT THEN ; \ correct for ones complement, avoiding -0
: DNEGATE ( d1 -- d2 ) 2DUP OR IF HIGH-BIT XOR THEN ; \ correct for sign-magnitude, where HIGH-BIT is the mask of the sign bit, avoiding -0

Showing a circular reference of:

: DNEGATE ( d1 -- d2 ) 0. 2SWAP D- ; : D- ( d1 d2 -- d3 ) NEGATE D+ ;

would be "correct" regardless of encoding, but then leaves it to the implementation to be familiar with the rules of the encoding.

And all of the above still assumes that d has twice as many bits as n. As stated in A.3.2.1, "There is no requirement to implement circular unsigned arithmetic, nor to set the range of unsigned numbers to the full size of a cell." If n is not using all of the bits of a single cell, then I question whether any of the above implementations are correct for a double-cell d. (And as I mentioned here, I'm working on a niche environment where my n has at least 48 bits but no easy-to-determine most-significant bit, because the underlying implementation could be any of 64-bit twos-complement integers [overflow is circular], BigInt integers [usable as sign-magnitude, no overflow but no definitive sign bit], or IEEE doubles used as 53-bit integers [sign-magnitude, overflow loses precision but not magnitude]). Since Forth-2012 only requires n to use at least 15 bits plus sign, and d to use at least 31 bits plus sign, I'm still aiming to be a standard system by declaring that my n has at least 48 bits plus sign, and my d has at least 48 bits plus sign, where my d is encoded as all significant bits in the lower cell. My initial idea for DNEGATE in that environment is this, subject to change based on what I hit while coding other double words: : DNEGATE ( d1 -- d2 ) D>S -1 * S>D ;, where D>S includes -24 throw if the upper cell was not 0 or -1).

True, the reference implementations need not be universal. But when a reference implementation limits itself to a particular environmental constraint, like twos complement, it should be called out.

JimPetersonavatar of JimPeterson

I definitely agree that a reference implementation that relies on two's complement is an environmental dependency until the standard insists on two's complement.

I don't really see how the implementation I propose is insufficient for your system, unless 0<> does not return either 0 or -1. I see that TRUE is declared to return "a single-cell value with all bits set", so maybe that doesn't translate to -1 in your system?

Despite these points, I'm far more interested in the other question: should reference implementations proposed here focus more towards concisely conveying the intent of the word (like 0. 2SWAP D-), or should they attempt to offer reasonable implementations that can be used to fill out a nascent system (such as something that does not cyclically reference D-'s implementation as DNEGATE D+)?

EricBlakeavatar of EricBlake

I don't really see how the implementation I propose is insufficient for your system, unless 0<> does not return either 0 or -1. I see that TRUE is declared to return "a single-cell value with all bits set", so maybe that doesn't translate to -1 in your system?

  • In a twos-complement system: -a cell with all bits set is -1 when interpreted as a signed number.
  • In a ones-complement system, -1 is equal to 1 INVERT (so it has most bits set, but the least significant cleared); the value with all bits set is -0 (except that the standard points out that the usual arithmetic operations like + and * should never result in a -0; you can only get it as a flag or via bit manipulations).
  • In a sign-magnitude system, -1 is equal to 1 SIGN-BIT XOR (so it has only 2 bits set); the value with all bits set is the negative of the maximum positive signed value; this representation also has a -0, but that only has one bit set. Historical sign-magnitude machines and IEEE floating point uses the most significant bit of storage as the sign bit, at least when converting the bit pattern to the corresponding unsigned value; but it is also possible to have sign-magnitude where the sign bit is adjacent to the least-significant bit.

In all three of those numeric encodings, the expressions 1 -1 * and 1 NEGATE will result in -1, but it is not usable as a canonical flag since the number of bits set differs by the encoding. Meanwhile, a cell with all bits set treated as an unsigned integer would be the maximum unsigned integer - but only if the system does not use the standard's escape clause of capping the maximum u value at the same as the maximum d value rather than using the full cell.

(And there's the historical mess that older Forth standards allowed systems where TRUE was equal to 1 rather than all bits set, matching some common hardware setups and the C language)

It is because the interpretation of a cell with all bits set has three different values in the three different encodings that Forth declares it to be ambiguous behavior if you ever perform math directly on a flag value; at most, you are guaranteed that you can perform bitwise logical operations on a flag value plus an integer to then result in an integer or zero (that is, a b = 1+ is ambiguous, but a b = -1 XOR 1+ is well-defined). In fact, because all bits set in ones complement is -0, the standard is careful to describe non-canonical flags as a cell with at least one bit set (rather than a cell with a non-zero value). Of course, when the next version of Forth requires twos-complement, it should also get rid of the ambiguity of doing math on a flag value, and we could do an editorial cleanup of all other places where the standard was dancing around concessions to ones-complement or sign-magnitude cell behavior.

On my particular system atop a VM with no fixed cell size, and for that matter, no native negative number support, I have found it easier to actually encode positive vs. negative by using a sign-magnitude with the least-significant bit as the sign bit, dispatching to the right variant of + or * based on the sign bit, then shifting that bit away before using the VM's native math on the resulting unsigned values. But when it comes to other operations, like XOR, I'm finding it easier to just declare that I convert a number between positive and negative as if by twos-complement modular arithmetic rules (even if that's not what is actually happening in the underlying bit representation). At which point, I'm finding it easier to declare that by fiat, -1 behaves as if it has all bits set (even though in the VM representation it may only have 2 bits set).

Despite these points, I'm far more interested in the other question: should reference implementations proposed here focus more towards concisely conveying the intent of the word (like 0. 2SWAP D-), or should they attempt to offer reasonable implementations that can be used to fill out a nascent system (such as something that does not cyclically reference D-'s implementation as DNEGATE D+)?

As a user, I prefer concise implementations that convey intent, even if it leads to circular references. As an implementer, I would prefer the extra leg up in having a topological sorting on how to build bigger pieces out of smaller building blocks, even if that leads to more verbosity or non-optimal implementations. I guess it boils down to deciding whether the standard is intended to be more user-friendly or more implementer-friendly; in my book, I think the balance swings in favor of users.

Reply New Version