TransWikia.com

Fibonacci macro

TeX - LaTeX Asked by needle on April 21, 2021

I am trying to make a Fibonacci macro, that works as follows.

enter image description here

I was able to do so, and I tried to make everything private by using @ (i know it isn’t necessary here, but I am trying to practice…). I know that @curtab is supposed to be used for tabs, but since I saw @David Carlisle use it for other things, I tried to do the same.

Here is the code that I wrote

begin{document}

makeatletter

renewcommandfib[2][]{newcountprint@limit newcount@limit
    expandafterifxexpandafterrelaxdetokenize{#1}relax %no input#1
        print@limit=#2%
    else
        print@limit=#1%
    fi
    @tempcnta@ne @tempcntb@ne count@#2 @limittw@
    ifnumprint@limit<tw@ number@tempcntb, fi
    fib@i}
deffib@i{%
    ifnumcount@>@ne
        print@fib
        @curtab=numexpr@tempcnta+@tempcntbrelax
        @tempcnta@tempcntb @tempcntb@curtab
        advancecount@-@ne advance@limit@ne
        fib@i
    fi}
defprint@fib{%
    unlessifnum@limit<print@limit%
        number@tempcntb%
        ifnumcount@>thr@@ , %
        elseifnumcount@=thr@@ ~and %
    fififi}

makeatother

end{document}

It works just fine, and i even made it a bit better by changing the last print to m AND n instead of m, n.
My problem is that i wasn’t able to define fib using def, and had to resort to using newcommand because i couldn’t figure out how to get the optional argument using def.

Three Questions

  1. My question is how to use def and get an optional argument?
  2. Also, i was wondering if there was a better way that to check after each print ifnum>3 , ifnum=3 and, so that i get the correct
    display (i.e. commas except for the last number ‘and’).
  3. Finally, instead of using a loop ... repeat, i defined fib@i which acts like a loop. However, i was wondering if it is better
    to use recursion or loop in this case? In another code language, i
    would avoid using recursion as it can only support so many call, but
    here, the arithmetic overflow is reached before that happens, so i
    was wondering if one method was better than the other.

3 Answers

You should not allocate two new counters every time the macro is used (classic tex only has 256 counters, etex has more but still, ...) If you do use counters you should allocate them just once, outside of the macro, and also whatever dubious example you may have seen elsewhere allocate your own counter do not re-use @curtab

The question whether to use a loop or recursion doesn't really have an answer, as loop is simply a recursive macro, there is no looping primitive in TeX.

I'd probably do something like this (which would be expandable apart from the test for the optional argument)

enter image description here

documentclass{article}

makeatletter

deffib{@ifnextchar[fib@i{fib@i[relax]}}

deffib@i[#1]#2{%
  ifxrelax#1%
   fib@ii{#2}{#2}101%
   else
   fib@ii{#1}{#2}101%
  fi
  }
  
% #1 hide if less than
% #2 target index
% #3 current index
% #4 #5 last two numbers
deffib@ii#1#2#3#4#5{%
  ifnum#1<numexpr#3+1relax
    thenumexpr#5relax
  fi
  ifnum#3<#2relax
  ifnum#1<numexpr#3+1relax, fi
    fib@ii{#1}{#2}{numexpr#3+1relax}{#5}{numexpr#4+#5relax}%
  fi}

makeatother
begin{document}

%xtracingall

fib{20}

fib[1]{10}
end{document}

Correct answer by David Carlisle on April 21, 2021

Since you're learning, I'll present a different (fully expandable) solution with expl3:

documentclass{article}

ExplSyntaxOn

NewExpandableDocumentCommand{fib}{O{#2}m}
 {
  needle_fib:nn { #1 } { #2 }
 }

cs_new:Nn needle_fib:nn
 {% start the recursion from fib(1)=1, fib(2)=1
  % #1 = current step in the recursion
  % #2 = starting point
  % #3 = end point
  % #4 = fib(#1-2)
  % #5 = fib(#1-1)
  needle_fib_print:nnnnn { 1 } { #1 } { #2 } { 1 } { 0 }
 }

cs_new:Nn needle_fib_print:nnnnn
 {
  int_compare:nT { #1 >= #2 }
   {% the current step is at least the starting point, print the number
    int_eval:n { #4 + #5 }
    % a comma-space if not at the last but one
    int_compare:nTF { #3 - #1 = 1 }
     {% the first comparison is for the Oxford comma
      int_compare:nF { #3 - #2 = 1 } { , } ~and~ % before the last
     }
     { int_compare:nF { #3 = #1 } { ,~ } } % otherwise a comma-space
   }
  % recursion
  int_compare:nT { #1 < #3 }
   {
    needle_fib_print:ennne
     { int_eval:n { #1 + 1 } } % increase the step
     { #2 } % starting point
     { #3 } % end point
     { #5 } % previous fibonacci number
     { int_eval:n { #4 + #5 } }
   }
 }
cs_generate_variant:Nn needle_fib_print:nnnnn { ennne }

ExplSyntaxOff

begin{document}

fib{30}

fib[19]{20}

fib[1]{30}

edeftest{fib[1]{5}}
texttt{meaningtest}

fib[1]{46}

end{document}

enter image description here

The 47th Fibonacci number is beyond the arithmetic capabilities of TeX, unless you use bigintcalc as shown in another answer of mine (which is a variant of this one).

documentclass{article}
usepackage[margin=1cm,a4paper]{geometry}
usepackage{bigintcalc,siunitx}

ExplSyntaxOn

cs_set_eq:NN needle_bigint_add:nn bigintcalcAdd

NewExpandableDocumentCommand{fib}{O{#2}m}
 {
  needle_fib:nn { #1 } { #2 }
 }

cs_new:Nn needle_fib:nn
 {% start the recursion from fib(1)=1, fib(2)=1
  % #1 = current step in the recursion
  % #2 = starting point
  % #3 = end point
  % #4 = fib(#1-2)
  % #5 = fib(#1-1)
  needle_fib_print:nnnnn { 1 } { #1 } { #2 } { 1 } { 0 }
 }

cs_new:Nn needle_fib_print:nnnnn
 {
  int_compare:nT { #1 >= #2 }
   {% the current step is at least the starting point, print the number
    needle_bigint_add:nn { #4} { #5 }
    % a comma-space if not at the last but one
    int_compare:nTF { #3 - #1 = 1 }
     {% the first comparison is for the Oxford comma
      int_compare:nF { #3 - #2 = 1 } { , } ~and~ % before the last
     }
     { int_compare:nF { #3 = #1 } { ,~ } } % otherwise a comma-space
   }
  % recursion
  int_compare:nT { #1 < #3 }
   {
    needle_fib_print:ennne
     { int_eval:n { #1 + 1 } } % increase the step
     { #2 } % starting point
     { #3 } % end point
     { #5 } % previous fibonacci number
     { needle_bigint_add:nn { #4 } { #5 } }
   }
 }
cs_generate_variant:Nn needle_fib_print:nnnnn { ennne }

ExplSyntaxOff

begin{document}

The 200th Fibonacci number is num{fib{200}}

fib{30}

fib[19]{20}

fib[1]{30}

edeftest{fib[1]{5}}
texttt{meaningtest}

begin{flushleft}
fib[1]{200}
end{flushleft}

end{document}

In the picture I only show the first part, the rest is the same as in the other picture. Using num shows by itself that the macro is fully expandable.

enter image description here

Answered by egreg on April 21, 2021

-My question is how to use def and get an optional argument?

You can implement a loop based on futurelet for removing space tokens and looking at the next non-space-token for finding out if [ denoting an optional argument is present. This is what @ifnextchar of the LaTeX 2ε-kernel does.

catcode`@=11
longdef@firstofone#1{#1}%
longdef@ifnextchar#1#2#3{%
  letreserved@d=#1%
  defreserved@a{#2}%
  defreserved@b{#3}%
  futurelet@let@token@ifnch
}%
def@ifnch{%
  ifx@let@token@sptoken
    letreserved@c@xifnch
  else
    ifx@let@tokenreserved@d 
      letreserved@creserved@a
    else
      letreserved@creserved@b
    fi 
  fi
  reserved@c
}%
@firstofone{def@xifnch} {futurelet@let@token@ifnch}%
defMacroWithOptArg{%
  @ifnextchar[{InternalMacroWithOptArg}{InternalMacroWithNoOptArg}%
}%
longdefInternalMacroWithNoOptArg#1{InternalMacroWithOptArg[{#1}]{#1}}
longdefInternalMacroWithOptArg[#1]#2{%
  This is the optional argument: #1.
  This is the mandatory argument: #2.
}%

MacroWithOptArg[A]{B}

MacroWithOptArg{B}

bye

MacroWithOptArg at some stage of expansion yields @ifnextchar which in turn yields a bunch of def- and futurelet- and let-assignments and therefore is not expandable. Therefore, like @ifnextchar, MacroWithOptArg is not expandable, too.

(A macro being "expandable" means that all processing triggered by this macro is based on expansion only. In Knuth's analogy where TeX is an organism with eyes and a digestion-tract, all processing triggered by an expandable macro is done in the gullet, where expansion takes place. A macro being "not expandable" means that not just the gullet but also the stomach is involved in the processing. The stomach is the place where assignments are carried out etc.)

Unlike this example, the newcommand-macro of the LaTeX 2ε-kernel makes macros with optional arguments robust so that in so called "pure expansion contexts" they are not carried out as carrying them out in pure expansion contexts fails.

-Also, i was wondering if there was a better way that to check after each print ifnum>3 , ifnum=3 and, so that i get the correct display (i.e. commas except for the last number 'and').

Off the cuff I don't see a better way with your code. But fib@i recursively calls itself, nested between ifnum..fi. With each iteration another fi will be accumulated in the input-stack. If you do expandafterfib@ifi, the fi won't be accumulated as they get expanded (and hereby removed) before the next instance of fib@i is carried out. Besides this I modified your emptiness-test so that expandafter is not needed and it is not relied on relax not being redefined in terms of outer—which would be a very weird thing to do, but who knows...

documentclass{article}

makeatletter
newcountprint@limit newcount@limit
newcommandfib[2][]{%
    ifcat$detokenize{#1}$%no input#1
        print@limit=#2%
    else
        print@limit=#1%
    fi
    @tempcnta@ne @tempcntb@ne count@#2 @limittw@
    ifnumprint@limit<tw@ number@tempcntb, fi
    fib@i}
deffib@i{%
    ifnumcount@>@ne
        print@fib
        @curtab=numexpr@tempcnta+@tempcntbrelax
        @tempcnta@tempcntb @tempcntb@curtab
        advancecount@-@ne advance@limit@ne
        expandafter % <- !!!!!!!!!!
        fib@i
    fi}
defprint@fib{%
    unlessifnum@limit<print@limit%
        number@tempcntb%
        ifnumcount@>thr@@ , %
        elseifnumcount@=thr@@ ~and %
    fififi}
makeatother

begin{document}

fib{20}

fib[1]{10}

end{document}

However, i was wondering if it is better to use recursion or loop in this case?

loop is a recursive macro. ;-)

But it has some shortcomings:

  • Its argument, which is delimited by repeat, goes into a temporary macro. Therefore if loop is used to define macros that process arguments, hash-doubling is needed with these arguments.
  • You'd better not nest loop..repeat-constructs unless you know exactly what you do. The likelihood is high that the delimiter-matching with the argument-delimiter repeat won't work in the way expected by you. ;-)


As egreg proposed a full-expandable solution with expl3, I see the need of exhibiting shortcomings of expandable checking for the presence of optional arguments:

documentclass{article}

ExplSyntaxOn
NewExpandableDocumentCommand{macro}{O{#2}m}{
  message{^^J
    This~is~the~optional~argument:~[#1]^^J
    This~is~the~non~optional~argument:~{#2}^^J^^J
  }
}
ExplSyntaxOff

begin{document}

macro[optional]{non-optional}

macro{[}{optional}]{non-optional}

% The resulting messages should differ but don't.

end{document}

I get the message

This is the optional argument: [optional]
This is the non optional argument: {non-optional}

twice.

I expected the second message to be

This is the optional argument: [[]
This is the non optional argument: {[}

and the sequence {optional}]{non-optional} to yield the phrase optional]non-optional within the resulting .pdf-file.



For the sake of having fun I implemented a variant where fib can also handle negative numbers and where internally an expandable tail-recursive macro fibloop is called. ε-TeX's numexpr is used for doing the arithmetic and ε-TeX's detokenize is used for checking emptiness of an argument.
In case the upper bound is smaller than the lower bound, no numbers will be printed at all.

documentclass{article}

makeatletter
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@Exchange[2]{#2#1}%
newcommandfib[2][]{%
  expandafterUD@Exchangeexpandafter{expandafter{expandafter>number#2}}{%
    expandafterUD@Exchangeexpandafter{expandafter{%
       expandafter<numberifcat$detokenize{#1}$%
       expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi{#2}{#1}%
    }}{fibloop{0}{0}{1}{}{}}%
  }{+}%
}%
newcommandfibloop[8]{%
  % #1 = n
  % #2 = n-th Fibonacci-number
  % #3 = (n+1)-th/(n-1)-th fibonacci number
  % #4 = comma-space-separated list of Fibonacci-numbers found so far
  % #5 = separator to prepend / append
  % #6 = < lower bound of range /  > upper bound of range  = condition for appending Fibonacci-number to list of fibonacci-numbers found so far
  % #7 = > upper bound of range / < lower bound of range  = condition for ending the loop
  % #8 = + / - = operator of arithmetic operations
  ifnum#1#7 expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi{%
    ifx#8+expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
    {fibloop{-1}{1}{-1}{#4}{#5}{#7}{#6}{-}}{#4}%
  }{%
    ifnum#1#6 expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
    {UD@Exchange{{}{}}}{%
      ifx#8+expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
      {UD@Exchange{{#4#5#2}{, }}}{UD@Exchange{{#2#5#4}{, }}}%
    }{%
      expandafterUD@Exchangeexpandafter{expandafter{numbernumexpr#2#8#3relax}}{%
        expandafterfibloopexpandafter{numbernumexpr#1#81relax}{#3}%
      }%
    }{#6}{#7}{#8}%
  }%
}%
makeatother

parindent=-1.5cm

begin{document}

begin{tabular}{ll}
verb|fib{20}|:&fib{20}
verb|fib[]{20}|:&fib[]{20}
verb|fib[20]{20}|:&fib[20]{20}

verb|fib[1]{10}|:&fib[1]{10}

verb|fib[-10]{10}|:&fib[-10]{10}
verb|fib[0]{10}|:&fib[0]{10}
verb|fib[10]{20}|:&fib[10]{20}
verb|fib[5]{10}|:&fib[5]{10}
verb|fib[-8]{-8}|:&fib[-8]{-8}
verb|fib[-8]{-4}|:&fib[-8]{-4}
verb|fib[-1]{0}|:&fib[-1]{0}
verb|fib{-1}|:&fib{-1}
verb|fib[-1]{-1}|:&fib[-1]{-1}
verb|fib[]{-4}|:&fib[]{-4}
verb|fib[0]{0}|:&fib[0]{0}
verb|fib{0}|:&fib{0}
verb|fib{1}|:&fib{1}
verb|fib[0]{1}|:&fib[0]{1}
verb|fib[1]{1}|:&fib[1]{1}

verb|fib[-4]{-8}|:&fib[-4]{-8}
verb|fib[3]{0}|:&fib[3]{0}
verb|fib[8]{3}|:&fib[8]{3}
verb|fib[3]{-8}|:&fib[3]{-8}
end{tabular}

end{document}

enter image description here

Answered by Ulrich Diez on April 21, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP