4

I am a little annoyed with the inconsistency of the spacing of some math symbols like + and - when they appear to the left of a letter (or symbol) and when they appear between two letters (or symbols), as shown in the picture. Is there a reason for these spacing to be different? Is there any way to make them always equal without me having to do it manually every time they appear in the text?

Thanks.enter image description here

6
  • 1
    Welcome to TeX.SE. – Mico Mar 16 '20 at 5:26
  • Can you use LuaTeX? – Henri Menke Mar 16 '20 at 5:46
  • 4
    you shouldn't manually add spacing -2 needs no space but 1 - 2 needs space – David Carlisle Mar 16 '20 at 7:41
  • 4
    Your screenshot features two instances each of unary-type and binary-type - symbols. Interestingly, the lack of whitespace around the unary - symbol located between ( and w_1 doesn't seem to bother you -- or at least not as much as does the first unary minus symbol. – Mico Mar 16 '20 at 12:42
  • 1
    Don't try to adopt wrong spacing in serious papers (in general), because the editor is likely to complain and revert the change ... – L. F. Mar 18 '20 at 2:09
14

Fully automatic, thanks to LuaTeX.

\documentclass{article}
\usepackage{amsmath}
\usepackage{luacode}
\begin{luacode*}
local muglue_subtype
do
    for num, name in pairs(node.subtypes(node.id("glue"))) do
        if name == "muglue" then
            muglue_subtype = num
            break
        end
    end
end
assert(muglue_subtype, "No such subtype!")

function table.contains(haystack, needle)
    for _, straw in next, haystack do
        if straw == needle then
            return true
        end
    end
    return false
end

local glue_fields = { "width", "stretch", "stretch_order", "shrink", "shrink_order" }

local function binordspacing(head,style,penalties)
    local cur = head
    local match = 0
    while cur do
        local subtype = node.subtypes(cur.id)[cur.subtype]
        if match == 0 and table.contains({"op", "bin", "rel", "open", "punct"}, subtype) then
            match = 1
        elseif match == 1 and subtype == "bin" then
            match = 2
        elseif match == 2 and subtype == "ord" then
            local binordspacing = tex.getmath("binordspacing", style)
            local n = node.new("glue", muglue_subtype)
            for _, field in ipairs(glue_fields) do
                n[field] = binordspacing[field]
            end
            head = node.insert_before(head, cur, n)
            match = 0
        else
            match = 0
        end
        cur = cur.next
    end
    return true
end

luatexbase.add_to_callback("pre_mlist_to_hlist_filter", binordspacing, "binordspacing")
\end{luacode*}
\begin{document}
\begin{equation*}
    \alpha = -p(-w_1-v_0) - g(w_1)
\end{equation*}

\begin{center}
    $\alpha = -p(-w_1-v_0) - g(w_1)$
\end{center}
\end{document}

enter image description here

Ahhh, my eyes!


In the comments you mentioned that you'd also like to have space between the opening parenthesis and the minus (oh god, why?) so I came up with a more configurable approach. This allows you to insert random spacing in places where TeX would not consider that (for good reasons).

\documentclass{article}
\usepackage{amsmath}
\usepackage{luacode}
\begin{luacode*}
local muglue_subtype
do
    for num, name in pairs(node.subtypes(node.id("glue"))) do
        if name == "muglue" then
            muglue_subtype = num
            break
        end
    end
end
assert(muglue_subtype, "No such subtype!")

local glue = glue or {}
function glue.copy(src)
    local glue_fields = { "width", "stretch", "stretch_order", "shrink", "shrink_order" }
    local g = node.new("glue", muglue_subtype)
    for _, field in ipairs(glue_fields) do
        g[field] = src[field]
    end
    return g
end

local knuth_table = {
    ord   = { ord = "0"  , op = "1"  , bin = "(2)", rel = "(3)", open = "0"  , close = "0"  , punct = "0"  , inner = "(1)" },
    op    = { ord = "1"  , op = "1"  , bin = "*"  , rel = "(3)", open = "0"  , close = "0"  , punct = "0"  , inner = "(1)" },
    bin   = { ord = "(2)", op = "(2)", bin = "*"  , rel = "*"  , open = "(2)", close = "*"  , punct = "*"  , inner = "(2)" },
    rel   = { ord = "(3)", op = "(3)", bin = "*"  , rel = "0"  , open = "(3)", close = "0"  , punct = "0"  , inner = "(3)" },
    open  = { ord = "0"  , op = "0"  , bin = "*"  , rel = "0"  , open = "0"  , close = "0"  , punct = "0"  , inner = "0  " },
    close = { ord = "0"  , op = "1"  , bin = "(2)", rel = "(3)", open = "0"  , close = "0"  , punct = "0"  , inner = "(1)" },
    punct = { ord = "(1)", op = "(1)", bin = "*"  , rel = "(1)", open = "(1)", close = "(1)", punct = "(1)", inner = "(1)" },
    inner = { ord = "(1)", op = "1"  , bin = "(2)", rel = "(3)", open = "(1)", close = "0"  , punct = "(1)", inner = "(1)" }
}

local function subtype(n)
    if not n then
        return nil
    end
    return node.subtypes(n.id)[n.subtype]
end

local function traverse(head, style)
    for n in node.traverse(head) do
        if n.id == node.id("sub_mlist") then
            traverse(n.list, style)
        else
            if subtype(n) == "bin" then
                local undefined = false
                -- look at the previous
                local prevtype = subtype(n.prev)
                if knuth_table[prevtype] and knuth_table[prevtype].bin == "*" then
                    undefined = true
                    local g = glue.copy(tex.getmath(prevtype .. "binspacing", style))
                    head = node.insert_before(head, n, g)
                end
                -- look at the next
                local nexttype = subtype(n.next)
                if undefined or knuth_table.bin[nexttype] == "*" then
                    local g = glue.copy(tex.getmath("bin" .. nexttype .. "spacing", style))
                    head = node.insert_after(head, n, g)
                end
                undefined = false
            end
        end
    end
end

local function binordspacing(head,style,penalties)
    traverse(head, style)
    return true
end

luatexbase.add_to_callback("pre_mlist_to_hlist_filter", binordspacing, "binordspacing")
\end{luacode*}

% Add further abominations to this list
\Umathopenbinspacing\displaystyle=\thinmuskip
\Umathopenbinspacing\textstyle=\thinmuskip
\Umathbinordspacing\displaystyle=\thinmuskip
\Umathbinordspacing\textstyle=\thinmuskip

\begin{document}
\begin{equation*}
    \alpha = -p(-w_1-v_0) - g(w_1)
\end{equation*}

\begin{center}
    $\alpha = -p(-w_1-v_0) - g(w_1)$
\end{center}
\end{document}

enter image description here

9
  • 17
    Maybe, just maybe, giving the customers what they want isn't always right… :-) – Mico Mar 16 '20 at 6:55
  • 1
    @Gustavo I forgot to set the subtype of the glue to muglue. See updated answer. – Henri Menke Mar 17 '20 at 8:18
  • 3
    @Gustavo is not "much better". It is horrible. I really hope that I never have to read a document with this settings. – Ulrike Fischer Mar 17 '20 at 9:59
  • 1
    @HenriMenke For current LaTeX versions, I would recommend using pre_mlist_to_hlist_filter instead of mlist_to_hlist. Then you just have to replace return node.mlist_to_hlist(...) with return true and you magically restore compatibility with all packages which use mlist_to_hlist. – Marcel Krüger Mar 17 '20 at 10:15
  • 3
    Please don't teach peple to misuse LaTeX to create horrible typography. Pretty please. – vonbrand Mar 17 '20 at 18:42
27

You ask,

Is there a reason for these spacing[s] to be different?

Indeed there is! What you've (re-)discovered is the difference between - (and + too) being employed as either a unary or a binary operator.

If the spacing were the same for both types of operators, you would probably get annoyed that it is the same.

In essence, what you're observing is the result of centuries of fine math typography, which has established that making the spacing around unary and binary operators the same is not optimal.

Here are some practical examples of the application of spacing rules for unary and binary operators.

enter image description here

\documentclass{article}
\begin{document}      
\obeylines % just for this example               
``$-$'' and ``$+$'' treated as unary operators:
$\dots,-3,-2,-1,0,+1,+2,+3,\dots$
$-1 \ne +1$
\bigskip
``$-$'' and ``$+$'' treated as binary operators:
$\dots,{}-3,{}-2,{}-1,0,{}+1,{}+2,{}+3,\dots$ --- looks awful!
$1+1=2$ \quad $2-2=0$
\bigskip
Mixed use:
$-1=5-6$ \quad $-5+6=+1$
\end{document}
1
  • 5
    (+1) Crystal clear explanation! – Bernard Mar 16 '20 at 9:15
10

Welcome! This is actually a feature and not a bug. The first minus is a sign while the second one is a binary operator. If you want the larger spacing, you can add a {}.

\documentclass{article}
\usepackage{amsmath}
\begin{document}
\begin{align*}
 \alpha&=-p(-w_1-v0)-g(w_1)\\
 \alpha&={}-p(-w_1-v0)-g(w_1)\\
\end{align*}
\end{document}

enter image description here

But to me it seems more reasonable to treat signs different from operators.

5
  • 2
    Oh, go the whole way. Add {} in (-w...). Entirely horrible. – barbara beeton Mar 16 '20 at 15:07
  • 1
    @barbarabeeton I agree. Which is why I wrote "But to me it seems more reasonable to treat signs different from operators.". (Seems that one gets many more positive votes if one uses "unary" instead of sign...) – user194703 Mar 16 '20 at 15:09
  • Does this work in environments other than align? – Gustavo Mar 17 '20 at 4:37
  • @Gustavo Yes. Did you find one where it does not work? – user194703 Mar 17 '20 at 4:39
  • Didn't test it. Just asking. Thanks! – Gustavo Mar 17 '20 at 4:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.