用户定义的 \newcommand 位于另一个用户定义的 \newcommand 内

用户定义的 \newcommand 位于另一个用户定义的 \newcommand 内

我不明白下面的代码有什么问题:

\documentclass[a4paper]{article}

\usepackage[T1]{fontenc}

\usepackage{calc}
\usepackage{ifthen}

\usepackage[french]{babel}

\newcounter{a}
\newcounter{b}

\newcommand{\reste}[2]{
\setcounter{a}{#1}
\setcounter{b}{#2}
\whiledo{\(\thea > \theb\) \OR \(\thea = \theb\)}{
\setcounter{a}{\thea - \theb}
}
\thea
}

\newcounter{p}
\newcounter{q}
\newcounter{t}

\newcommand{\pgcd}[2]{
\setcounter{p}{#1}
\setcounter{q}{#2}
\whiledo{\theq > 0}{
\setcounter{t}{\reste{\thep}{\theq}}
\setcounter{p}{\theq}
\setcounter{q}{\thet}
}
\thep
}

\begin{document}

Le reste dans la division entière de $29$ par $6$ est $\reste{29}{6}$.

Le reste dans la division entière de $30$ par $6$ est $\reste{30}{6}$.

Le plus grand commun diviseur de $29$ et $6$ est $\pgcd{29}{6}$.

\end{document}

答案1

您需要使用纯扩展:

\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage[french]{babel}

\makeatletter
\newcommand{\reste}[2]{%
  \ifnum#1<#2
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {#1}{\expandafter\reste\expandafter{\the\numexpr#1-#2}{#2}}%
}

\newcommand{\pgcd}[2]{%
  \ifnum#1<#2
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\@pgcd{#2}{#1}}{\@pgcd{#1}{#2}}%
}
\newcommand{\@pgcd}[2]{%
  \ifnum#2=0
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {#1}{\expandafter\@@pgcd\expandafter{\expanded{\reste{#1}{#2}}}{#2}}%
}
\newcommand{\@@pgcd}[2]{\@pgcd{#2}{#1}}

\makeatother

\begin{document}

Le reste dans la division entière de $29$ par $6$ est $\reste{29}{6}$.

Le reste dans la division entière de $30$ par $6$ est $\reste{30}{6}$.

Le plus grand commun diviseur de $29$ et $6$ est $\pgcd{29}{6}$.

Le plus grand commun diviseur de $42$ et $12$ est $\pgcd{42}{12}$.

\end{document}

\reste宏被递归调用,直到第一个参数小于第二个参数。

类似地,\pgcd检查其参数的大小,必要时反转它们。然后它调用\@pgcd递归调用自身计算其余部分,就像往常一样。辅助函数\@@pgcd对于反转参数很有用,并且更容易用 来探究第一个参数\expandafter

使用\expanded允许使用\reste而不必担心提供结果所需的扩展次数。

在此处输入图片描述

这是使用更高级工具的不同版本:

\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage[french]{babel}
\usepackage{xparse}

\ExplSyntaxOn
\cs_set_eq:NN \reste \int_mod:nn

\NewExpandableDocumentCommand{\pgcd}{mm}
 {
  \int_compare:nTF { #1 < #2 }
   { \chauvin_pgcd:nn { #2 } { #1 } }
   { \chauvin_pgcd:nn { #1 } { #2 } }
 }

\cs_new:Nn \chauvin_pgcd:nn
 {
  \int_compare:nTF { #2 = 0 }
   { #1 }
   {
    \chauvin_pgcd:nn { #2 } { \int_mod:nn { #1 } { #2 } }
   }
 }
\ExplSyntaxOff

\begin{document}

Le reste dans la division entière de $29$ par $6$ est $\reste{29}{6}$.

Le reste dans la division entière de $30$ par $6$ est $\reste{30}{6}$.

Le plus grand commun diviseur de $29$ et $6$ est $\pgcd{29}{6}$.

Le plus grand commun diviseur de $42$ et $12$ est $\pgcd{42}{12}$.

\end{document}

答案2

关于 (La)TeX 如何工作的一些初步说明:

该命令不仅可以传递表示计数器值的 0..9 范围内的数字标记,还可以传递(不可扩展的)标记以漂亮地打印干扰计算和赋值的值。\the⟨counter⟩\setcounter

而是使用。\number\value{⟨counter⟩}


除了 expl3 的误导性术语(其中大量使用“功能”一词)之外,LaTeX 的编程范式不是像 C++ 或 Java 等高级编程语言那样的过程式/功能式,而是基于宏的声明式和符号式,其中符号由所谓的标记形成,并且在扩展阶段符号/标记被其他符号/标记替换。

在 Knuth 的消化过程类比中,TeX 具有

  • 眼睛
  • 消化道
  • 能够产生代币并将其放入嘴里,从而开始消化过程。

TeX 的眼睛读取 .tex 输入文件。TeX 由此将输入作为一组指令,用于生成标记并将这些标记逐个放入其口中。因此,这些标记形成一个“标记流”,其元素逐个通过 TeX 的消化道。

一个(可扩展的)标记的扩展 - 即用其他标记替换该标记(以及可能构成其参数的那些标记) - 发生在标记通过 TeX 的食道传输时。

这个类比中的分配(定义宏、为\count寄存器分配值等等)发生在 TeX 的胃里。

TeX 消化过程的最终结果将是生成的输出文件(.pdf 文件 / .dvi 文件、.log 文件、辅助文件(例如 .aux 文件和 .toc 文件以及 .lot / .lof 文件等))以及写入控制台的内容。

这里的关键一点是:

TeX-⟨数字⟩在扩展阶段,必须在 TeX 的喉咙中形成数量,这样形成 TeX 的标记序列才能形成⟨数字⟩-quantity 在 TeX 的“胃”中可用,在那里执行分配等操作。
\setcounter意味着在 LaTeX 的胃中将\count一个值分配给所讨论的 LaTeX 计数器下的寄存器,该值通过 LaTeX 的喉管(发生扩展的地方)作为标记序列传递,形成 TeX-⟨数字⟩-数量。

因此,扩展宏的结果不仅仅是最终值。结果是一组插入到标记流中的标记/符号。

例如,

\newcommand{\reste}[2]{%
  \setcounter{a}{#1}%
  \setcounter{b}{#2}%
  \whiledo{\(\thea>\theb\)\OR\(\thea=\theb\)}{%
    \setcounter{a}{\thea-\theb}%
  }%
  \thea
}

扩展的结果\reste{29}{6}不仅仅是来自 的最终结果\thea
结果将是删除 token 序列

\restecontrol-word-token,,,,,,,,​​​​​​
{explicit catcode-1(begin-group)-character-token
2explicit catcode-12(other)-character-token
9explicit catcode-12(other)-character-token
}explicit catcode-2(end-group)-character-token
{explicit catcode-1(begin-group)-character-token
6explicit catcode-12(other)-character-token
}explicit catcode-2(end-group)-character-token

从标记流中取出标记序列,而不是插入标记序列

\setcountercontrol-word-token,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
{explicit catcode-1(begin-group)-character-token
aexplicit catcode-11(letter)-character-token
}explicit catcode-2(end-group)-character-token
{explicit catcode-1(begin-group)-character-token
2explicit catcode-12(other)-character-token
9explicit catcode-12(other)-character-token
}explicit catcode-2(end-group)-character-token
\setcountercontrol-word-token
{explicit catcode-1(begin-group)-character-token
bexplicit catcode-11(letter)-character-token
}explicit catcode-2(end-group)-character-token
{explicit catcode-1(begin-group)-character-token
6explicit catcode-12(other)-character-token
}explicit catcode-2(end-group)-character-token
\whiledocontrol-word-token
{explicit catcode-1(begin-group)-character-token
\(control-symbol-token
\theacontrol-word-token
>explicit catcode-12(other)-character-token
\thebcontrol-word-token
\)control-symbol-token
\ORcontrol-word-token
\(control-symbol-token
\theacontrol-word-token
=explicit catcode-12(other)-character-token
\thebcontrol-word-token
\)control-symbol-token
}explicit catcode-2(end-group)-character-token
{explicit catcode-1(begin-group)-character-token
\setcountercontrol-word-token
{explicit catcode-1(begin-group)-character-token
aexplicit catcode-11(letter)-character-token
}explicit catcode-2(end-group)-character-token
{explicit catcode-1(begin-group)-character-token
\theacontrol-word-token
-explicit catcode-12(other)-character-token
\thebcontrol-word-token
}explicit catcode-2(end-group)-character-token
}explicit catcode-2(end-group)-character-token
\theacontrol-word-token

进入标记流。

该移除和插入在 LaTeX 的食道中进行。

有了这些知识,就会发现

\setcounter{t}{\reste{\thep}{\theq}}

无法工作:

指令的第二个参数\setcounter应为一组标记,其元素在扩展后仅产生 TeX 的组成部分 -⟨数字⟩-数量。

但是扩展的结果\reste{\thep}{\theq}(位于\setcounter{t}{...}-directive 的第二个参数内)不仅会产生 TeX 的组成部分,⟨数字⟩-数量。

该结果还包含与表示 TeX 组件无关的标记 -⟨数字⟩-数量。
例如,(嵌套)指令\setcounter{a}{\thep}\setcounter{b}{\theq}
例如,\whiledo-指令。

(嵌套)\setcounter指令(位于其他指令之下)产生的标记不表示 TeX 的数字/组件 -⟨数字⟩-数量,但表示将值分配给 LaTeX 计数器/将值分配给\count所讨论的 LaTeX 计数器下的 TeX 寄存器的操作。当该指令的底层寄存器赋值在 LaTeX 的胃中执行时
,来自嵌套指令的这些赋值表示标记\setcounter会扰乱周围的指令。\setcounter{t}{...}\count

使用\whiledo-directive 时,结果不是通过仅进行扩展(在 LaTeX 的喉咙中)来提供的。\whiledo还(在 LaTeX 的胃中)执行临时分配。这些临时分配所依据的标记不是产生 TeX 组件的标记-⟨数字⟩-数量。因此它们也“扰乱”了-\setcounter{t}{...}分配。

(在“嵌套”\setcounter指令\setcounter{a}{\thep}及其\setcounter{b}{\theq}本身中可能存在基本相同的问题:

与它不同,它不能保证提供一组标记,这些标记的元素只产生 TeX-\number\value{⟨counter⟩}\the⟨counter⟩⟨数字⟩-数量。可能存在一些用于“漂亮打印”的东西/标记,它们不能是 TeX 的组成部分-\the⟨counter⟩⟨数字⟩-数量,因此“扰乱”了-\setcounter分配。)


如何解决这个问题?

例如\reste,你可以有一个可选参数,该参数表示要插入到这些标记后面的标记,这些标记会产生将所需的值分配给 LaTeX-counter 的标记a

尽可能少地修改代码,您可能可以得到如下期望的结果:

\documentclass[a4paper]{article}

\usepackage[T1]{fontenc}

\usepackage{calc}
\usepackage{ifthen}

\usepackage[french]{babel}

\newcounter{a}
\newcounter{b}

\newcommand{\reste}[3][\thea]{%
  \setcounter{a}{#2}%
  \setcounter{b}{#3}%
  \whiledo{\(\number\value{a}>\number\value{b}\)\OR\(\number\value{a}=\number\value{b}\)}%
          {\setcounter{a}{\number\value{a}-\number\value{b}}}%
  #1%
}

\newcounter{p}
\newcounter{q}
\newcounter{t}

\newcommand{\pgcd}[3][\thep]{%
  \setcounter{p}{#2}%
  \setcounter{q}{#3}%
  \whiledo{\number\value{q}>0}{%
    \reste[{\setcounter{t}{\number\value{a}}}]{\number\value{p}}{\number\value{q}}%
    \setcounter{p}{\number\value{q}}%
    \setcounter{q}{\number\value{t}}%
  }%
  #1%
}

\begin{document}

Le reste dans la division entière de $29$ par $6$ est $\reste{29}{6}$.

Le reste dans la division entière de $30$ par $6$ est $\reste{30}{6}$.

Le plus grand commun diviseur de $29$ et $6$ est $\pgcd{29}{6}$.

\end{document}

在此处输入图片描述

如果\numexpr有 ε-TeX 扩展,则根本不需要使用 LaTeX 计数器就可以获得结果。

不要使用 LaTeX 计数器,而是使用宏参数在扩展级联期间保存值:

\documentclass[a4paper]{article}

\usepackage[T1]{fontenc}

\usepackage[french]{babel}
\usepackage{amsmath}

\makeatletter
\newcommand\PassFirstToSecond[2]{#2{#1}}%
\newcommand{\reste}[3]{%
  % #1 = marker what the remainder shall look like;
  %      Assume: (integer divisor) = (integer dividend)*(integer number K) + (integer remainder)
  %        p/P = remainder positive
  %        s/S = remainder with same sign as dividend
  %        v/V or anything else = remainder with smallest absolute value 
  % #2 = divisor
  % #3 = dividend
  \romannumeral0%
  \ifnum#3=0 %
    \expandafter\@firstoftwo
  \else
     \expandafter\@secondoftwo
  \fi
  { \text{non-défini}}%
  {%
    \PassFirstToSecond{#1}{%
      \ifnum#3<0 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
      {\PassFirstToSecond{-}}%
      {\PassFirstToSecond{}}%
      {%
        \ifnum#2<0 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
        {\PassFirstToSecond{-}}%
        {\PassFirstToSecond{}}%
        {%
          \expandafter\PassFirstToSecond\expandafter{\number\ifnum#3<0 -\fi\number#3}{%
            \expandafter\PassFirstToSecond\expandafter{\number\ifnum#2<0 -\fi\number#2}{\resteloop}%
          }%
        }%
      }%
    }%
  }%
}%
\@ifdefinable\gobbletoexclam{\long\def\gobbletoexclam#1!{}}%
\@ifdefinable\forkpsv{\long\def\forkpsv#1!p!s!v!P!S!V!#2#3!!!!{#2}}%
\newcommand\psvfork[4]{%
  % #1 = argument to check
  % #2 = tokens in case argument to check is p or P
  % #3 = tokens in case argument to check is s or S
  % #4 = tokens in case argument to check is something else.
  \if\relax\detokenize\expandafter{\gobbletoexclam#1!}\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {%
    \forkpsv!#1!s!v!P!S!V!{#2}% <- case p
            !p!#1!v!P!S!V!{#3}% <- case s
            !p!s!#1!P!S!V!{#4}% <- case v
            !p!s!v!#1!S!V!{#2}% <- case P
            !p!s!v!P!#1!V!{#3}% <- case S
            !p!s!v!P!S!#1!{#4}% <- case V
            !p!s!v!P!S!V!{#4}% <- case something else without !
            !!!!%
  }%
  {#4}% <- case something else with !
  % The fork internally has eight branches:
  %  1. p
  %  2. s
  %  3. v
  %  4. P
  %  5. S
  %  6. V
  %  7. something else without "!"
  %  8. something else with "!"
  % Branches 1 and 4 collapse into one branch.
  % Branches 2 and 5 collapse into one branch.
  % Branches 3, 6, 7 and 8 collapse into one branch.
  % Thus you could instead implement a fork with six branches:
  %  1. p
  %  2. s
  %  3. P
  %  4. S
  %  5. something else without "!"
  %  6. something else with "!"
  % and collapse branches 1 and 3 into one branch
  % and branches 2 and 4 into one branch
  % and branches 5 and 6 into one branch.
  % But this way introducing different behavior for case v or V
  % than for case something else with or without "!" is more
  % easy if needed. (You may wish the triggering of error-
  % messages in the latter case rather than the same behavior as in
  % case v/V.)
}%
\newcommand{\resteloop}[5]{%
  % #1 = absolute value of divisor (digit sequence)
  % #2 = absolute value of dividend (digit sequence)
  % #3 = sign of divisor (empty = not negative; - = negative)
  % #4 = sign of dividend (empty = not negative; - = negative)
  % #5 = marker what the remainder shall look like;
  %     Assume: (integer divisor) = (integer dividend)*(integer number K) + (integer remainder)
  %      p/P = remainder positive
  %      s/S = remainder with same sign as dividend
  %      v/V or anything else = remainder with smallest absolute value 
  \ifnum#1<#2 \expandafter\@secondoftwo\else\expandafter\@firstoftwo\fi
  {\expandafter\resteloop\expandafter{\number\numexpr#1-#2\relax}{#2}{#3}{#4}{#5}}%
  {%
     \psvfork{#5}{%
        %Case p/P:
       \ifx\relax#3\relax\expandafter\@secondoftwo\else\expandafter\@firstoftwo\fi
       {%
         \@firstofone{\expandafter} \number\numexpr\number#3#1+#2\relax
       }%
       {%
         \@firstofone{\expandafter} \number#3#1%
       }%
     }{%
        %Case s/S:
       \ifx\relax#3\relax\expandafter\@secondoftwo\else\expandafter\@firstoftwo\fi
       {%
         \ifx\relax#4\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
         {%remainder negative module positive
           \@firstofone{\expandafter} \number\numexpr\ifnum#1=0 \else#2+\fi\number#3#1\relax
         }{% remainder negative module negative
           \@firstofone{\expandafter} \number#3#1%
         }%
       }{%
         \ifx\relax#4\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
         {%   remainder positive module positive
           \@firstofone{\expandafter} \number#3#1%
         }{%   remainder positive module negative
           \@firstofone{\expandafter} \number\numexpr\ifnum#1=0 \else-#2+\fi\number#3#1\relax
         }%
       }%
     }{%
        %Case someting else, e.g. v/V:
       \ifnum\number\numexpr#2-#1\relax<#1 %
       \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
       {%
         \@firstofone{\expandafter} \number\numexpr\ifnum\number#3#1<0 \else-\fi#2+\number#3#1\relax
       }{%
         \@firstofone{\expandafter} \number#3#1%
       }%
     }%
  }%
}%
\newcommand{\pgcd}[2]{%
  % #1 = first integer number
  % #2 = second integer number
  \romannumeral0%
  \ifnum#1=0 \expandafter\@firstofone\else\expandafter\@secondoftwo\fi
  {%
    \ifnum#2=0 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    { \text{non-défini}}%
  }%
  {%
    \expandafter\PassFirstToSecond\expandafter{%
      \number\ifnum#2<0 -\fi\number#2%
    }{%
      \expandafter\PassFirstToSecond\expandafter{%
        \number\ifnum#1<0 -\fi\number#1%
      }{\pgcdloop}%
    }%
  }%
}%
\newcommand{\pgcdloop}[2]{%
  \ifnum#2>0 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
  {%
    \expandafter\PassFirstToSecond\expandafter{%
      \romannumeral0\resteloop{#1}{#2}{}{}{p}%
    }{\pgcdloop{#2}}%
  }%
  { #1}%
}
\makeatother

\begin{document}

\footnotesize\parindent=0ex

Le reste avec la valeur absolue la plus petite dans la division entière de $0$ par $6$ est $\reste{v}{0}{6}$.\\
Le reste avec le meme signe que le module dans la division entière de $0$ par $6$ est $\reste{s}{0}{6}$.\\
Le reste avec valeur positive dans la division entière de $0$ par $6$ est $\reste{p}{0}{6}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $6$ par $0$ est $\reste{v}{6}{0}$.\\
Le reste avec le meme signe que le module dans la division entière de $6$ par $0$ est $\reste{s}{6}{0}$.\\
Le reste avec valeur positive dans la division entière de $6$ par $0$ est $\reste{p}{6}{0}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $29$ par $6$ est $\reste{v}{29}{6}$.\\
Le reste avec le meme signe que le module dans la division entière de $29$ par $6$ est $\reste{s}{29}{6}$.\\
Le reste avec valeur positive dans la division entière de $29$ par $6$ est $\reste{p}{29}{6}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $29$ par $-6$ est $\reste{v}{29}{-6}$.\\
Le reste avec le meme signe que le module dans la division entière de $29$ par $-6$ est $\reste{s}{29}{-6}$.\\
Le reste avec valeur positive dans la division entière de $29$ par $-6$ est $\reste{p}{29}{-6}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $-29$ par $6$ est $\reste{v}{-29}{6}$.\\
Le reste avec le meme signe que le module dans la division entière de $-29$ par $6$ est $\reste{s}{-29}{6}$.\\
Le reste avec valeur positive dans la division entière de $-29$ par $6$ est $\reste{p}{-29}{6}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $-29$ par $-6$ est $\reste{v}{-29}{-6}$.\\
Le reste avec le meme signe que le module dans la division entière de $-29$ par $-6$ est $\reste{s}{-29}{-6}$.\\
Le reste avec valeur positive dans la division entière de $-29$ par $-6$ est $\reste{p}{-29}{-6}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $30$ par $6$ est $\reste{v}{30}{6}$.\\
Le reste avec le meme signe que le module dans la division entière de $30$ par $6$ est $\reste{s}{30}{6}$.\\
Le reste avec valeur positive dans la division entière de $30$ par $6$ est $\reste{p}{30}{6}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $30$ par $-6$ est $\reste{v}{30}{-6}$.\\
Le reste avec le meme signe que le module dans la division entière de $30$ par $-6$ est $\reste{s}{30}{-6}$.\\
Le reste avec valeur positive dans la division entière de $30$ par $-6$ est $\reste{p}{30}{-6}$.\\

Le reste avec la valeur absolue la plus petite dans la division entière de $0$ par $0$ est $\reste{v}{0}{0}$.\\
Le reste avec le meme signe que le module dans la division entière de $0$ par $0$ est $\reste{s}{0}{0}$.\\
Le reste avec valeur positive dans la division entière de $0$ par $0$ est $\reste{p}{0}{0}$.\\

Le plus grand commun diviseur de $29$ et $6$ est $\pgcd{29}{6}$.

Le plus grand commun diviseur de $0$ et $6290$ est $\pgcd{0}{6290}$.

Le plus grand commun diviseur de $6324$ et $0$ est $\pgcd{6324}{0}$.

Le plus grand commun diviseur de $0$ et $-6290$ est $\pgcd{0}{-6290}$.

Le plus grand commun diviseur de $-6324$ et $0$ est $\pgcd{-6324}{0}$.

Le plus grand commun diviseur de $6324$ et $6290$ est $\pgcd{6324}{6290}$.

Le plus grand commun diviseur de $-6324$ et $6290$ est $\pgcd{-6324}{6290}$.

Le plus grand commun diviseur de $6324$ et $-6290$ est $\pgcd{6324}{-6290}$.

Le plus grand commun diviseur de $-6324$ et $-6290$ est $\pgcd{-6324}{-6290}$.

Le plus grand commun diviseur de $0$ et $0$ est $\pgcd{0}{0}$.

\end{document}

在此处输入图片描述

相关内容