Definitions for the PlusMinus operator
By Ted Ersek 
ted.ersek@navy.mil

PlusMinus Examples

Evaluate the next cell and say Yes when asked if you want to evaluate the Initialization cells.

The PlusMinus usage message is given below.

?PlusMinus

PlusMinus[-5] in the next cell evaluates to PlusMinus[5].

y = PlusMinus[-5]

When we raise a PlusMinus expression to an even power we get a positive number. However, if we raise it to an odd number we get another PlusMinus expression.

{y^2, (y^3)}

Limit[4/0, x->0] approaches -∞ from the left, and approaches +∞ from the right, So I prefer to have (4/0)return ±∞ as in the next cell. However, dividing a complex number by zero still returns ComplexInfinity.

{4/0, (I/0)}

With my PlusMinus definitions the complex part of (4/0) is zero.

Im[(4/0)]

In the next two lines (4/0)evaluates to PlusMinus[∞], then (Tanh[±∞]->±1), and (\(TraditionalForm\`ArcTan[\(±∞\)] -> \(±\(π\/2\)\)\)).

x = Tanh[4/0]

x = ArcTan[4/0]

Also when we add a finite value to ±∞ we get ±∞.

6 + 4/0

When we add ∞ to PlusMinus[(π/2)] we get ∞.

∞ + PlusMinus[(π/2)]

I couldn't think of an elegant way to represent the result of adding a finite number to an expression such as (±(π/2)), so I made it return Indeterminate.

θ = ±π;
θ + (π/4)

If the argument of PlusMinus equals zero, then the argument is returned. The next cell gives some examples.

{PlusMinus[0], PlusMinus[0.0], PlusMinus[0.0 + 0.0  I], 
      PlusMinus[0.``34]}

Nested layers of PlusMinus are automatically flattened.

PlusMinus[PlusMinus[PlusMinus[-4.1]]]

I made definitions to ensure Max, and Min can take arguments with the head PlusMinus.

Max[2, 1, ±π, 0, 3]

Max[2, 1, ±π, 0, 8, -12, 3]

Min[2, 1, ±π, 0, 3]

Min[2, 1, ±π, 0, 8, -12, 3]

Below we see some simple arithmetic and formatting of output works well when PlusMinus is involved.

q = 2; w = -5;
tst = ±(Sqrt[5]) q w

Clear[e];
tst = ±(Sqrt[8]) q w e

Nest Tan[x] returns  ±∞  when it would normally return ComplexInfinity.

Tan[((-7 π)/2)]

I could have provided a similar capability for Gamma, Csc and many other functions to return ±∞ in certain cases.

{Gamma[-3], Csc[6  π]}

Another thing I could have added is support for PlusMinus expressions in inequalities. If I did that the following would return True.

±5 < 10

I don't care for the way the next output is formatted, and I am not even sure what should be returned. However, any well defined definitions could be implemented with a little effort. Besides that there are probably many other problems that could be fixed as well, but at least I made a good start.

Clear[t];
PlusMinus[4] + t

PlusMinus Definitions

Off[Attributes::ssle];

PlusMinus::usage = "PlusMinus[x] represents a number with magnitude \
equal to x, but unknown Sign.";

The next two functions are pure functions, and as a result they run faster than  HeadTest[expr_]:=....
because pattern matching is not involved. However, pure functions are not a good choice for functions the end user will use because you can't make a pure function that only accepts certain arguments.

HeadTest = ((Intersection[{#}, {Min, Max, PlusMinus, Set, 
                      SetDelayed, UpSet, UpSetDelayed, TagSet, 
                      TagSetDelayed}] === {}) && 
              MemberQ[Attributes[#], NumericFunction] &);

NumbQ = (NumericQ[#] || (# === ∞) || (# === -∞) &);

The next definition is given because in some cases the default formatting uses parentheses that I consider unnecessary.

MakeBoxes[PlusMinus[x_], 
          form : (StandardForm | TraditionalForm)] := 
        RowBox[{"±", MakeBoxes[x, form]}];

PlusMinus[PlusMinus[x_]] ^:= PlusMinus[x];

Open this Cell Group to see why I made the next definition so complicated.

I could have made the next definition much simpler and it would work most of the time. However, I added steps to ensure it's highly reliable.

The first extra step I take is to wrap each test in Which in the head TrueQ.  This ensures that each test evaluates to True or False. In the next cell we see what happens when one of the arguments is neither True or False. I wanted to ensure my PlusMinus definitions don't do this.

Clear[x];
Which[
x < 100, 10, 
3 < 50, 
      5, 
True, 0
]

Many of the tests in Which have the form lhs==rhs. In some cases a comparison such as this will only return True when it's in a head such as Simplify or FullSimplify. The next cell gives a simple example. In nearly every example we will end up evaluating FullSimplify[True] or FullSimplify[False].  There may be rare cases where the code below needs to use FullSimplify.

Off[$MaxExtraPrecision::meprec, 
        N::meprec];
{2 (π + 1) == 2  π + 2, 
      FullSimplify[2 (π + 1) == 2  π + 2]}

At one point I use the test (yn == yp) || (yn === yp).  This may seem redundant. The test is written this way to ensure it returns True if (yn, yp) are numerically equal, or if (yn,yp) are both ComplexInfinity.

At one point I perform the following test.
(Im[yn]==Im[yp]==0)&&(Head[yn]=!=Complex)&&(Head[yp]=!=Complex)
This may also seem redundant. First I use Im[yn]==Im[yp]==0 to ensure the complex parts of yp and yn are numerically zero. Then I also need to see if an expression has the head Complex. In a case such as (2.1+0.0i) the complex part is numerically equals zero, but we still say it's complex.

I also use FullSimplify when checking to see if an expression is complex. In the next cell I give an example that has no complex part, and special steps are needed to see that this is the case.

FullSimplify[
      Im[-2 + 
            (4/((((1/2) (-7 + 3 I (Sqrt[23]))))^(1/3))) + ((((1/2) (-7 + 3 I (Sqrt[23]))))^(1/3))] == 
        0]

PlusMinus /: 
        f_[a___?NumbQ, PlusMinus[x_?NumbQ], b___?NumbQ] /; 
          HeadTest[f] := 
With[{yn = f[a, -x, b], 
            yp = f[a, x, 
                b]}, 
Which[
TrueQ@
              FullSimplify[((yn == yp) || (yn === 
                          yp)) && (Im[yn] == Im[yp] == 
                      0) && (Head[yn] =!= Complex) && (Head[yp] =!= 
                      Complex)], 
yp, 
TrueQ@
              FullSimplify[(yn == -yp) && (Im[yn] == 
                      Im[yp] == 0) && (Head[yn] =!= 
                      Complex) && (Head[yp] =!= 
                      Complex)], 
PlusMinus[
              yp], 
TrueQ[(Abs[yn] === 
                    Abs[yp] === ∞) && (Im[yn] =!= 
                    0)], 
ComplexInfinity, 
True, 
Indeterminate
]
];

PlusMinus[(neg_?(Negative[#] && AtomQ[#] &))*(x_. )] := 
        PlusMinus[-neg*x];

PlusMinus /: Times[y__, PlusMinus[x_]] := 
        PlusMinus[y*x];

With the next definition it isn't sufficient to have a definition for PlusMinus[0] because we want a definition that applies to PlusMinus[0.0], PlusMinus[0.0+0.0i] and similar forms.

PlusMinus[x_?(# == 0 &)] := x;

PlusMinus[ComplexInfinity] = ComplexInfinity;

PlusMinus[Indeterminate] = Indeterminate;

Unprotect[Power, Max, Min, Tan];

With the next definition it isn't sufficient to have a definition for Power[0,-1] because we want a definition that applies to Power[0.0,-1], Power[0.0+0.0i,-1] and similar forms.

Power[_?(# == 0 &), -1] = 
        PlusMinus[∞];

$ModifyMax = $ModifyMin = $ModifyTan = True;

Max[args__] /; $ModifyMax := 
        Block[{$ModifyMax}, 
Max @@ (Replace[{args}, 
                PlusMinus[x_] :> 
                  Sequence[
                    x, -x], {1}])
];

Min[args__] /; $ModifyMin := 
        Block[{$ModifyMin}, 
Min @@ (Replace[{args}, 
                PlusMinus[x_] :> 
                  Sequence[
                    x, -x], {1}])
];

Tan[
            x_?(Element[#, 
                    Reals] &)] /; $ModifyTan := 
Block[{$ModifyTan}, 
          With[{y = Tan[x]}, 
            If[y === ComplexInfinity, PlusMinus[∞], 
              y]]];

Protect[Power, Max, Min, Tan];