% -*- coding: utf-8; time-stamp-format: "%02d-%02m-%:y at %02H:%02M:%02S %Z" ; time-stamp-active: nil -*-
%<*dtx>
\def\bnedtxtimestampaux #1<#2>{#2}
\edef\bnedtxtimestamp{\bnedtxtimestampaux Time-stamp: <27-09-2025 at 18:04:58 CEST>}
\catcode0 0 \catcode`\\ 12
^^@iffalse
%</dtx>
%<*drv>
%% ---------------------------------------------------------------
\def\bnepackdate{2025/09/27}%
\def\bneversion {1.7b}%
%</drv>
%<*readme>--------------------------------------------------------
| Source:  bnumexpr.dtx
| Version: v1.7b, 2025/09/27
| Author:  Jean-François Burnol
| Info:    Expressions with big integers
| License: LPPL 1.3c

Usage and aim
=============

The package provides `\bnumeval`.

To use with LaTeX, add `\usepackage{bnumexpr}` to the preamble.
To use with Plain TeX, execute `\input miniltx.tex` then
`\input bnumexpr.sty`.

`\bnumeval` is an expandable expression parser extending
LaTeX's `\inteval` (a wrapper of eTeX's `\numexpr`).

Compared to `\inteval`, the added functionalities are:

- support for arbitrarily big integers,
- `//` for floored division,
- `/:` for the associated remainder,
- `^` and `**` for powers,
- `!` for factorials,
- comma separated calculations,
- space character or underscore may serve to separate digits,
- `0b`, `0o` and `0x` are recognized as prefixes for binary, octal,
  and hexadecimal inputs (in addition to the TeX prefixes `'` and `"`),
- letters used for hexadecimal input may be in lowercase,
- optional argument `[b]`, `[o]` or `[h]` for converting the output
  to binary, octal, or hexadecimal (`[ha]` for lowercase hexadecimal),
- quasi complete customizability and extensibility of the syntax.

The last item means that not only is it possible to map any given
operation to a macro of one's own choosing, it is also possible to add
new infix or postfix operators to the syntax.

Installation
============

The package is provided by the major LaTeX distributions.

For a manual installation  obtain `bnumexpr.dtx`, from CTAN:

> <http://www.ctan.org/pkg/bnumexpr>

Run `"etex bnumexpr.dtx"` to extract in particular the style file
`bnumexpr.sty` (and other files inclusive of this `README.md`).  Put it
where (La)TeX can find it and you are done.

To build the documentation check the instructions in extracted
file `bnumexpr.tex`.

License
=======

Copyright © 2014-2022, 2025 Jean-François Burnol

| This Work may be distributed and/or modified under the
| conditions of the LaTeX Project Public License 1.3c.
| This version of this license is in

>   <http://www.latex-project.org/lppl/lppl-1-3c.txt>

| and version 1.3 or later is part of all distributions of
| LaTeX version 2005/12/01 or later.

This Work has the LPPL maintenance status "author-maintained".

The Author and Maintainer of this Work is Jean-François Burnol.

This Work consists of the main source file and its derived files

    bnumexpr.dtx, bnumexpr.sty, bnumexpr.pdf, bnumexpr.tex,
    bnumexprchanges.tex, README.md

%</readme>--------------------------------------------------------
%<*!readme>
%% ---------------------------------------------------------------
%% The bnumexpr package: Expressions with big integers
%% Copyright (C) 2014-2022, 2025 Jean-Francois Burnol
%%
%</!readme>
%<*changes>-------------------------------------------------------
\item[1.7b (2025/09/27)] Add to documentation an expandable macro
  computing Fibonacci numbers and illustrate it with the computation
  of $F_{10000}$.
\item[1.7a (2025/09/14)]\mbox{}\newline
  \textbf{Bug fix:} inputs consisting exclusively of zeros and underscores
  following an hexadecimal or other prefix caused a crash due to a |1.7|
  regression. \emph{Yes, we have now added a long delayed test suite doing
    more than checking only those examples as included in the documentation.}

  \textbf{New feature:} if the parser expects a value and finds only
  underscores (they are allowed as separators for blocks of digits) before
  hitting an operator, an opening parenthesis (or |\bnumexpr| sub-expression),
  a comma, or the end of the expression, it will consider it has fetched the
  value zero.  Formerly, for example expressions reduced to |_|, |__|, or
  entirely empty, or for example |_^_| all reported errors.
   
  \textbf{Other:} \cs{evaltohex} which signaled via an expandable
  error its deprecation since |1.6| has been removed.
\item[1.7 (2025/09/13)]\mbox{}\newline
  \textbf{Bug fix:} inputs with an underscore immediately after a
  hexadecimal (or other) prefix caused a crash due to a |1.6| regression.

\textbf{New features:}
\begin{itemize}
\item Support for hexadecimal input using letters in lowercase.
\item Optional argument |[ha]| for lowercase hexadecimal output.
\end{itemize}
\item[1.6a (2025/09/07)]\mbox{}\newline
  \textbf{Bug fix:} the |1.6| support for Babel-active
  characters worked with |\bnumeval| (which is recommended interface)
  but not with |\bnumexpr|.
\item[1.6 (2025/09/05)]\mbox{}\newline
\textbf{Breaking changes:}
    \begin{itemize}[noitemsep]
    \item Release |1.4n| or later of the \xintname bundle is required
          (for those components actually used, which by default are
           \xintkernelname, \xintcorename and \xintbinhexname).
    \item \cs{evaltohex} is deprecated and causes
      an error signaling it. \emph{It was removed at |1.7a|}. Use new |\bnumeval||[h]|.
    \item \cs{bnumexprsetup} was deprecated at |1.5| and kept as alias of
         |\bnumsetup|.  It has now been removed.
    \item \cs{bnumprintonetohex} and \cs{bnumhextodec}, which were documented
      as customizable do not exist anymore.  Check the documentation for
      |\bnumprintonehex| and |\bnumsetup|'s key |hextodec|.
    \item Under the |custom| option, not only \xintcorename but
      also \xintbinhexname are not loaded.
      Use |customcore| to avoid that.   There is also |custombinhex|.
    \end{itemize}

\textbf{Bug fix:}
  An underscore |_| located in front of a number used to cause
        an error. It is now ignored.

\textbf{New features:}
  \begin{itemize}
  \item |0b|, |0o| and |0x| are recognized as prefixes for binary, octal, and
    hexadecimal inputs. And |'| is recognized as prefix for octal input, in
    addition to |"| for hexadecimal.
  \item |\bnumeval| accepts an optional argument |[b]| or |[o]| or
        |[h]| for automatic conversion of the calculated value
        (or comma separated values) to
        respectively binary, octal, or hexadecimal.
  \item Babel-active characters (such as |:| and |!| with French)
        do not need any preventive measures anymore such as using
        |\string!| in place of |!|.
  \item |\bnumsetup| can now be used also to customize which macros
        implement conversion from decimal to other bases.
  \end{itemize}
The documentation was extensively revised and made more user-friendly.
\item[1.5 (2021/05/17)]
  \begin{itemize}
  \item \textbf{breaking change:} the power operators act now in a right
    associative way; this has been announced at \xintexprname as a probable
    future evolution, and is implemented in anticipation here now.
  \item \textbf{fix two bugs} (imported from upstream \xintexprname) regarding
    hexadecimal input: impossibility to use |"\foo| syntax (one had to do
    |\expandafter"\foo| which is unexpected constraint; a very longstanding
    \xintexprname bug) and issues with leading zeros (since \xintexprname |1.2m|).
  \item renamed \cs{bnumexprsetup} into |\bnumsetup|; the former remains
    available but is deprecated. [REMOVED AT |1.6|]
  \item the customizability and extendibility is now total:
    \begin{enumerate}
      \item |\bnumprintone|, |\bnumprintonetohex|, |\bnumprintonesep|, |\bnumhextodec|,
      \item |\bnumdefinfix| which allows to add extra infix operators,
      \item |\bnumdefpostfix| which allows to add extra postfix operators.
    \end{enumerate}
  \item |\bnumsetup|, |\bnumdefinfix|, |\bnumdefpostfix| obey the
    |\xintglobaldefstrue| and |\xintverbosetrue| settings.
  \item documentation is extended, providing details regarding the precedence
    model of the parser, as inherited from upstream \xintexprname; also an
    example of usage of |\bnumsetup| is included on how to transform
    |\bnumeval| into a calculator with fractions.
  \end{itemize}

\item[1.4a (2021/05/13)]
  \begin{itemize}
  \item fix undefined control sequences errors encountered by the parser in
    case of either extra or missing closing parenthesis (due to a problem
    in technology transfer at |1.4| from upstream \xintexprname).
  \item fix |\BNE_Op_opp| must now be \emph{f}-expandable (also caused as a
    collateral to the technology transfer).
  \item fix user documentation regarding the constraints applying to the user
    replacement macros for the core algebra, as they have changed at |1.4|.
  \end{itemize}

\item[1.4 (2021/05/12)]
  \begin{itemize}
  \item technology transfer from \xintexprname |1.4| of
    |2020/01/31|. The |\expanded| primitive is now required (TeXLive
    2019).
  \item addition to the syntax of the |"| prefix for hexadecimal
    input.
  \item addition of \cs{evaltohex} which is like |\bnumeval| with an extra
    conversion step to hexadecimal notation.
  \end{itemize}

\item[1.2e (2019/01/08)]
  Fixes a documentation glitch (extra braces when mentioning |\the\numexpr| or
  |\thebnumexpr|).

\item[1.2d (2019/01/07)]
  \begin{itemize}
  \item requires \xintcorename |1.3d| or later (if not using option |custom|).
  \item adds |\bnumeval|\marg{expression} user interface.
  \end{itemize}

\item[1.2c (2017/12/05)] \textbf{Breaking changes:}
  \begin{itemize}
  \item requires \xintcorename |1.2p| or later (if not using option |custom|).
  \item |divtrunc| key of \cs{bnumexprsetup} is renamed to |div|.
  \item the |//| and |/:| operators are now by default associated to the
    \emph{floored} division. This is to keep in sync with the change of
    \xintcorename at |1.2p|.
  \item for backwards compatibility, one
    may add to existing document:\newline
    \string\bnumexprsetup\{div=\string\xintiiDivTrunc,
    mod=\string\xintiiModTrunc\}
  \end{itemize}

\item[1.2b (2017/07/09)]
  \begin{itemize}
  \item the |_| may be used to separate visually blocks of digits in long
    numbers.
  \end{itemize}

\item[1.2a (2015/10/14)]
  \begin{itemize}
  \item requires \xintcorename |1.2| or later (if not using option |custom|).
  \item additions to the syntax: factorial |!|, truncated division
    |//|, its associated modulo |/:| and |**| as alternative to |^|.
  \item all options removed except |custom|.
  \item new command \cs{bnumexprsetup} which replaces the commands such as
    |\bnumexprusesbigintcalc|.
  \item the parser is no more limited to numbers with at most 5000 digits.
  \end{itemize}

\item[1.1b (2014/10/28)]
  \begin{itemize}
  \item README converted to |markdown/pandoc| syntax,
  \item the package now loads only |xintcore|, which belongs to |xint| bundle
    version |1.1| and extracts from the earlier |xint| package the core arithmetic
    operations as used by |bnumexpr|.
  \end{itemize}

\item[1.1a (2014/09/22)]
  \begin{itemize}
  \item added |l3bigint| option to use experimental \LaTeX3
    package of the same name,

  \item added Changes and Readme sections to the documentation,

  \item better |\BNE_protect| mechanism for use of
  |\bnumexpr...\relax| inside an |\edef| (without |\bnethe|). Previous one,
  inherited from |xintexpr.sty 1.09n|, assumed that the |\.=<digits>| dummy
  control sequence encapsulating the computation result had \cs{relax}
  meaning. But removing this assumption was only a matter of letting
  |\BNE_protect| protect two, not one, tokens. This will be backported to
  next version of \xintexprname, naturally (done with |xintexpr.sty 1.1|).
  \end{itemize}

\item[1.1 (2014/09/21)] First release. This is down-scaled from the
  (development version of) \xintexprname. Motivation came the previous day
  from a chat with \textsc{Joseph Wright} over big int status in \LaTeX3.
  The |\bnumexpr...\relax| parser can be used on top of big int macros of
  one's choice. Functionalities limited to the basic operations. I leave
  the power operator |^| as an option.
%</changes>-------------------------------------------------------
%<*drv>-----------------------------------------------------------
%% latex bnumexpr.tex (thrice) && dvipdfmx bnumexpr.dvi
%% to produce bnumexpr.pdf
%%
%% or pdflatex bnumexpr.tex (no need to change \Withdvipdfmx toggle,
%% pdf engine will be detected automatically)
%%
\NeedsTeXFormat{LaTeX2e}[2020/02/02]
\ProvidesFile{bnumexpr.tex}%
[\bnepackdate\space v\bneversion\space driver file for %
  bnumexpr documentation (JFB)]%
\PassOptionsToClass{a4paper,fontsize=11pt}{scrartcl}
\chardef\Withdvipdfmx 1 %
\chardef\NoSourceCode 0 % replace 0 by 1 for not including source code
\input bnumexpr.dtx
%%% Local Variables:
%%% mode: latex
%%% End:
%</drv>-----------------------------------------------------------
%<*dtx>
^^@fi ^^@catcode`\ 0 \catcode0 12
\chardef\noetex 0
\ifx\numexpr\undefined \chardef\noetex 1 \fi
\ifnum\noetex=1 \chardef\extractfiles 0 % extract files, then stop
\else
    \ifx\ProvidesFile\undefined
      \chardef\extractfiles 0 % no LaTeX2e; etex, ... on bnumexpr.dtx
    \else % latex/pdflatex on bnumexpr.tex or on bnumexpr.dtx
      \ifx\Withdvipdfmx\undefined
        % latex run is on bnumexpr.dtx, we will extract all files
        \chardef\extractfiles 1 % 1 = extract and typeset, 2=only typeset
        \chardef\Withdvipdfmx 0 % 0 = pdflatex or latex+dvips, 1 = dvipdfmx
        \chardef\NoSourceCode 0 % 0 = include source code, 1 = do not
        \NeedsTeXFormat{LaTeX2e}[2020/02/02]%
        \PassOptionsToClass{a4paper,fontsize=11pt}{scrartcl}%
      \else % latex run is on bnumexpr.tex,
        \chardef\extractfiles 2 % no extractions
      \fi
      \ProvidesFile{bnumexpr.dtx}[bundle source (\bnepackdate)]%
    \fi
\fi
\ifnum\extractfiles<2 % extract files
\def\MessageDeFin{\newlinechar10 \let\Msg\message
\Msg{^^J}%
\Msg{********************************************************************^^J}%
\Msg{*^^J}%
\Msg{* To finish the installation you have to move the following^^J}%
\Msg{* files into a directory searched by TeX:^^J}%
\Msg{*^^J}%
\Msg{*\space\space\space\space bnumexpr.sty^^J}%
\Msg{*^^J}%
\Msg{* To produce the documentation run latex thrice on bnumexpr.tex^^J}%
\Msg{* then dvipdfmx on bnumexpr.dvi. (ignore the dvipdfmx warnings)^^J}%
\Msg{*^^J}%
\Msg{* Happy TeXing!^^J}%
\Msg{*^^J}%
\Msg{********************************************************************^^J}%
}%
\begingroup
    \input docstrip.tex
    \askforoverwritefalse
    \generate{\nopreamble\nopostamble
    \file{README.md}{\from{bnumexpr.dtx}{readme}}
    \usepreamble\defaultpreamble
    \usepostamble\defaultpostamble
    \file{bnumexprchanges.tex}{\from{bnumexpr.dtx}{changes}}
    \file{bnumexpr.tex}{\from{bnumexpr.dtx}{drv}}
    \file{bnumexpr.sty}{\from{bnumexpr.dtx}{package}}}
\endgroup
\fi % end of file extraction
\ifnum\extractfiles=0
% direct tex/etex/xetex on bnumexpr.dtx, files now extracted, stop
  \MessageDeFin\expandafter\end
\fi
% no use of docstrip to extract files if latex compilation was on bnumexpr.tex
\ifdefined\MessageDeFin\AtEndDocument{\MessageDeFin}\fi
%-------------------------------------------------------------------------------
\documentclass {scrartcl}
%%% START OF CUSTOM doc.sty LOADING (May 21, 2022)
\usepackage{doc}[=v2]
% As explained above I was formerly using scrdoc hence ltxdoc indirectly.
% Let's emulate here the little I appear to need from ltxdoc.cls and
% srcdoc.cls.
%
\AtBeginDocument{\MakeShortVerb{\|}}
\DeclareFontShape{OT1}{cmtt}{bx}{n}{<-> ssub * cmtt/m/n}{}
\DeclareFontFamily{OMS}{cmtt}{\skewchar\font 48}  % '60
\DeclareFontShape{OMS}{cmtt}{m}{n}{<-> ssub * cmsy/m/n}{}
\DeclareFontShape{OMS}{cmtt}{bx}{n}{<-> ssub * cmsy/b/n}{}
\DeclareFontShape{OT1}{cmss}{m}{it}{<->ssub*cmss/m/sl}{}
\ifnum\NoSourceCode=1
  \OnlyDescription
\fi
\CodelineNumbered
\DisableCrossrefs
% \setcounter{StandardModuleDepth}{1} % we don't use this mechanism currently
\def\cmd#1{\cs{\expandafter\cmd@to@cs\string#1}}
\def\cmd@to@cs#1#2{\char\number`#2\relax}
% This is hyperref compatible (contrarily to using \bslash)
% I need the \detokenize due to usage in implementation part with names
% containing the underscore for example
\DeclareRobustCommand\cs[1]{\texttt{\textbackslash\detokenize{#1}}}
\providecommand\marg[1]{%
  {\ttfamily\char`\{}\meta{#1}{\ttfamily\char`\}}}
\providecommand\oarg[1]{%
  {\ttfamily[}\meta{#1}{\ttfamily]}}
\providecommand\parg[1]{%
  {\ttfamily(}\meta{#1}{\ttfamily)}}

% There is very little we seem to need from the scrdoc extras: page geometry
% is set by geometry package and a4paper option from bnumexpr.tex file.  So it
% seems I only need the hologo loading:
\usepackage{hologo}
\DeclareRobustCommand*{\eTeX}{\hologo{eTeX}}%
%\DeclareRobustCommand*{\LuaTeX}{\hologo{LuaTeX}}%
%
%%% end of ltxdoc+srcdoc emulation.

\ifnum\NoSourceCode=1 \OnlyDescription\fi
\usepackage{ifpdf}
\ifpdf\chardef\Withdvipdfmx 0 \fi
\makeatletter
\ifnum\Withdvipdfmx=1
% I think those are mostly obsolete...
   \@for\@tempa:=hyperref,bookmark,graphicx,xcolor,pict2e\do
            {\PassOptionsToPackage{dvipdfmx}\@tempa}
   %
   \PassOptionsToPackage{dvipdfm}{geometry}
   \PassOptionsToPackage{bookmarks=true}{hyperref}
   \PassOptionsToPackage{dvipdfmx-outline-open}{hyperref}
   %\PassOptionsToPackage{dvipdfmx-outline-open}{bookmark}
   %
   \def\pgfsysdriver{pgfsys-dvipdfm.def}
\else
   \PassOptionsToPackage{bookmarks=true}{hyperref}
\fi
\makeatother

\pagestyle{headings}

\usepackage[T1]{fontenc}

\usepackage[hscale=0.66,vscale=0.75]{geometry}

% requires newtxtt 1.05 or later
\usepackage[zerostyle=d,scaled=0.95,straightquotes]{newtxtt}
\renewcommand\familydefault\ttdefault
\usepackage[noendash]{mathastext}
\renewcommand\familydefault\sfdefault

\usepackage{graphicx}
\usepackage[dvipsnames]{xcolor}
\definecolor{joli}{RGB}{225,95,0}
\definecolor{JOLI}{RGB}{225,95,0}
\definecolor{BLUE}{RGB}{0,0,255}
%% \definecolor{niceone}{RGB}{38,128,192}% utilisé avant pour urlcolor
%% \colorlet{jfverbcolor}{yellow!5}
\colorlet{smallverbcolor}{magenta}% 1.6 release NavyBlue RoyalPurple
\colorlet{softwrapcolor}{blue}
\colorlet{digitscolor}{olive}% 1.6 {OrangeRed}

% transféré de xint-manual.tex (maintenant dans xint.dtx)
\DeclareFontFamily{U}{MdSymbolC}{}
\DeclareFontShape {U}{MdSymbolC}{m}{n}{<-> MdSymbolC-Regular}{}

\makeatletter
\newbox\cdbx@SoftWrapIcon
\def\cdbx@SetSoftWrapBox{%
    \setbox\cdbx@SoftWrapIcon\hb@xt@\z@
    {\hb@xt@\fontdimen2\font
        {\hss{\color{softwrapcolor}\usefont{U}{MdSymbolC}{m}{n}\char"97}\hss}%
     \hss}%
   }
\makeatother

\usepackage[french, main=english]{babel}

\usepackage{hyperref}
\hypersetup{%
%linktoc=all,%
breaklinks=true,%
colorlinks=true,%
urlcolor=Cerulean,% 1.6 ProcessBlue,%SkyBlue
linkcolor=PineGreen,%
pdfauthor={Jean-François Burnol},%
pdftitle={The bnumexpr package},%
pdfsubject={Arithmetic with TeX},%
pdfkeywords={Expansion, arithmetic, TeX},%
pdfstartview=FitH,%
pdfpagemode=UseOutlines}
\usepackage{bookmark}

% importé de xint 2021/05/14
\newcommand\RaisedLabel[2][6]{%
\vspace*{-#1\baselineskip}%
\begingroup
  \let\leavevmode\relax\phantomsection
  \label{#2}%
\endgroup
\vspace*{#1\baselineskip}%
}

%---- \verb, and verbatim like `environments'. \MicroFont et \MacroFont
\makeatletter
\def\MicroFont {\ttfamily\cdbx@SetSoftWrapBox\color{smallverbcolor}}
%\def\MicroFont {\ttfamily \color[named]{OrangeRed}\cdbx@SetSoftWrapBox }

% \MacroFont est utilisé par macrocode, mais sa définition est figée dans
% \macro@font au \begin{document}

\def\MacroFont {\ttfamily \baselineskip12pt\relax }

%--- November 4, 2014 making quotes straight. (maintenu pour *)
%
% there is no hook in \macrocode after \dospecials etc. Thus I will need to
% take the risk that some future evolution of doc.sty (or perhaps scrdoc)
% invalidates the following.
%
% Actually, I should not at all rely on the doc class, I should do it all by
% myself.

\def\macrocode{\macro@code
               \frenchspacing \@vobeyspaces
               \makestarlowast % \makequotesstraight (obsolète)
               \xmacro@code }

%--- lower * symbol in text

\def\lowast{\raisebox{-.25\height}{*}}
\catcode`* \active
\def\makestarlowast {\let*\lowast\catcode`\*\active}%
\catcode`* 12

\def\verb #1%
{%
  \relax\leavevmode\null
  \begingroup
   \MicroFont
   \def\@makeletter##1{\catcode`##1=11\relax}%
   % \scantokens will have a result of inserting a space after cs's.
   % hence the need to have the catcodes of things like _ right.
   % I also need < for > for code comments
   % \dospecials at begin document
   % \do\ \do\\\do\{\do\}\do\$\do\&\do\#\do\^\do\_\do\%\do\~\do\|
   \odef\dospecials{\dospecials\do\:\do\<\do\>\do\-\do\+}%
   % naturally won't work in footnotes though.
   % this code is truly not satisfying, but enough for my needs here.
   \catcode`\\ 11 \catcode`## 11
   % I don't do \catcode`\% 11 to avoid surprises in code comments
   % if my |...| has a linebreak
   \def\@jfverb ##1#1{\let\do\@makeletter \dospecials
   % lowering a bit the *
                      \makestarlowast
   %\let\do\do@noligs  \verbatim@nolig@list % not needed here
                      \@vobeyspaces\everyeof{\noexpand}%
                      \expandafter\@@jfverb\scantokens{##1}\relax}%
  \@jfverb
}%

% Pour v1.2 je laisse tomber les colorbox. À la place je change la couleur
% du texte. J'utilise le violet de xint. (mis dans \Microfont)
% \def\@@@jfverb #1{\ifcat\noexpand#1$% $
%                       \endgroup\else
% %      \penalty\z@
%        \discretionary{\copy\cdbx@SoftWrapIcon}{}{}%
% %      \colorbox{jfverbcolor}{\strut #1}% unfortunately, impacts pdf size
%        #1%
%        \expandafter\@@@jfverb\fi }

\def\@@jfverb #1{\ifcat\noexpand#1\relax\endgroup\@\else
       \discretionary{\copy\cdbx@SoftWrapIcon}{}{}%
       #1\expandafter\@@jfverb\fi }

\makeatother

% Lundi 17 mai 2021
% J'ajoute un export automatique des exemples qui servira de poor man test
% suite
\newwrite\tests
\immediate\openout\tests=\jobname-out.txt

% mardi 13 octobre 2015
% le | n'est pas encore actif ici!
\catcode`| \active
% first one uses \bnumexpr deliberately with no \bnethe to test 1.4.
    \def\bneshow #1%
    {|\bnumexpr#1\relax|%
     \edef\TEMP{\bnumexpr#1\relax}%
     \immediate\write\tests{\detokenize{\bnumexpr#1\relax}^^J\TEMP}%
     \[\color{digitscolor}\TEMP\]\ignorespaces}
    \def\bneshowthe #1%
    {|\bnethe\bnumexpr#1\relax|%
     \edef\TEMP{\bnethe\bnumexpr#1\relax}%
     \immediate\write\tests{\detokenize{\bnethe\bnumexpr#1\relax}^^J\TEMP}%
     \[\color{digitscolor}\TEMP\]\ignorespaces}
    \def\bneshoweval #1%
    {|\bnumeval|\unskip|{{#1}}|% problème de grab de mon \verb d'où {{..}}
     \edef\TEMP{\bnumeval{#1}}%
     \immediate\write\tests{\detokenize{\bnumeval{#1}}^^J\TEMP}%
     \[\color{digitscolor}\TEMP\]\ignorespaces}
% Ceci pour l'exemple avec " en output.  Ici on utilise un display
% donc en mode math, mais mathastext ne s'occupe pas de "...
{\catcode`"\active\gdef"{\texttt{\string"}}}
    \def\bneshowevaltohex #1%
    {|\bnumeval|\unskip|[h]{#1}|%
     \edef\TEMP{\bnumeval[h]{#1}}%
     \immediate\write\tests{\detokenize{\bnumeval[h]{#1}}^^J\TEMP}%
     \[\mathcode`"="8000 \color{digitscolor}\TEMP\]\ignorespaces}
    \def\bneshowevaltolowhex #1%
    {|\bnumeval|\unskip|[ha]{#1}|%
     \edef\TEMP{\bnumeval[ha]{#1}}%
     \immediate\write\tests{\detokenize{\bnumeval[ha]{#1}}^^J\TEMP}%
     \[\mathcode`"="8000 \color{digitscolor}\TEMP\]\ignorespaces}
    \def\bneshowevaltooct #1%
    {|\bnumeval|\unskip|[o]{#1}|%
     \edef\TEMP{\bnumeval[o]{#1}}%
     \immediate\write\tests{\detokenize{\bnumeval[o]{#1}}^^J\TEMP}%
     \[\color{digitscolor}\TEMP\]\ignorespaces}
    \def\bneshowevaltobin #1%
    {|\bnumeval|\unskip|[b]{#1}|%
     \edef\TEMP{\bnumeval[b]{#1}}%
     \immediate\write\tests{\detokenize{\bnumeval[b]{#1}}^^J\TEMP}%
     \[\color{digitscolor}\TEMP\]\ignorespaces}
\catcode`| 12
% Mardi 14 juin 2022
\def\bnedotest #1{\oodef\x{#1}\immediate\write\tests{\detokenize{#1}^^J\x}}

% toujours pénible de devoir se battre avec les espaces verticaux de TeX
\AtBeginDocument{%
% \message{ICI below: \the\belowdisplayskip^^J%
%              belowshort: \the\belowdisplayshortskip^^J%
%              above: \the\abovedisplayskip^^J%
%              aboveshor: \the\abovedisplayshortskip^^J%
% }%
   \belowdisplayskip\belowdisplayshortskip
   \abovedisplayskip\abovedisplayshortskip
}

\usepackage{xspace}

\def\bnumname
   {\href{http://www.ctan.org/pkg/bnumexpr}{bnumexpr}\xspace }%

\def\bnumnameimp
   {\texorpdfstring
          {\hyperref[sec:bnumexprcode]{{\color{smallverbcolor}\ttzfamily bnumexpr}}}
          {bnumexpr}%
    \xspace }%

\def\bnumnameuserdoc
   {\texorpdfstring
          {\hyperref[titlepage]{{\color{PineGreen}\ttzfamily bnumexpr}}}
          {bnumexpr}%
    \xspace }%

\def\xintkernelname
   {\href{http://www.ctan.org/pkg/xint}{xintkernel}\xspace }%
\def\xintcorename
   {\href{http://www.ctan.org/pkg/xint}{xintcore}\xspace }%
\def\xintname
   {\href{http://www.ctan.org/pkg/xint}{xint}\xspace }%
\def\xintfracname
   {\href{http://www.ctan.org/pkg/xint}{xintfrac}\xspace }%
\def\xintexprname
   {\href{http://www.ctan.org/pkg/xintexpr}{xintexpr}\xspace }%
\def\xintbinhexname
   {\href{http://www.ctan.org/pkg/xint}{xintbinhex}\xspace }%

\DeclareRobustCommand\ctanpackage[1]{\href{https://ctan.org/pkg/#1}{#1}}

%\frenchspacing
\usepackage{bnumexpr}
\usepackage{xintfrac}
\usepackage{etoc}
\usepackage{centeredline}
\usepackage{framed}
\makeatletter\let\check@percent\relax\makeatother

% 2022/06/15 -- comment updated 2025/09/07
%
% latex builds at home during preparation for the release are done
% using --translate-file=natural.tcx for the benefit of \ALERTE.
% The shipped to CTAN dtx will not contain any \ALERTE execution
% hence can be build using latex normally.
%
% AT LONG LAST LATEX HAS MADE THE UTF-8 MACROS PROTECTED
% so many years waiting for that...
{\catcode27=12 \gdef\babar{^^[}}
\def\ALERTE{\edef\foo{\noexpand\AtEndDocument
    {\immediate\write128{\babar[1;31mATTENTION METTRE À JOUR COMMENTAIRE EN LIGNE
    \the\inputlineno\babar[0m}}}%
\foo}

% 2025/09/07
% https://tex.stackexchange.com/a/62032
% Et je mets [2020/02/02] dans les \NeedsTeXFormat de bnumexpr{.tex,.dtx},
% pour être sûr
\ExplSyntaxOn
\NewDocumentCommand{\getenv}{om}
 {
  \sys_get_shell:nnN { kpsewhich ~ --var-value ~ #2 } { } \l_tmpa_tl
  \tl_trim_spaces:N \l_tmpa_tl
  \IfNoValueTF { #1 }
   {
    \tl_use:N \l_tmpa_tl
   }
   {
    \tl_set_eq:NN #1 \l_tmpa_tl
   }
 }
\ExplSyntaxOff
%
\getenv[\SHIPPINGNOW]{BNUMEXPR_RELEASING}
% \SHIPPINGNOW->\par si la variable n'est pas définie.
% L'expansion de \par débute par un \scan_stop: (au moins en 2025)
\if1\SHIPPINGNOW\def\ALERTE{\ERREUR}\fi

\usepackage[autolanguage,np]{numprint}
\usepackage{enumitem}

\newenvironment{TeXnote}{\par\footnotesize\medskip\noindent\textbf{\TeX-nical note:
  }}{\par\medskip}

\begin{document}
\thispagestyle{empty}
\ttzfamily
\pdfbookmark[1]{Title page}{TOP}

{%
\normalfont\Large\parindent0pt \parfillskip 0pt\relax
 \leftskip 2cm plus 1fil \rightskip 2cm plus 1fil
 The \bnumname package\par
}

\RaisedLabel{titlepage}

{\centering
  \textsc{Jean-François Burnol}\par
  \footnotesize
  jfbu (at) free (dot) fr\par
  Package version: \bneversion\ (\bnepackdate)\par
  {From source file \texttt{bnumexpr.dtx} of \texttt{\bnedtxtimestamp}}\par
}

\etocsetnexttocdepth{subsection}
\etocsettagdepth{code}{section}
\etocsettocstyle{}{}
\tableofcontents

\section{\cs{bnumeval}}
\label{sec:bnumexpr}
\label{sec:thebnumexpr}
\label{sec:bnumeval}

This \LaTeX\ package \bnumname provides |\bnumeval|, which is an expandable
parser of numerical expressions with big integers.

Recent \LaTeX\ has |\inteval|, which is a slim wrapper for \eTeX{}'s
\cs{numexpr} (embedded for twenty years in most \TeX-engines except
original Knuth's |tex|).
\begin{TeXnote}
  More precisely \cs{inteval}\marg{expression} is equivalent (up to how \TeX{}
  handles spaces located after in the source during tokenization, as
  tokenization of control sequences such as \cs{relax} causes \TeX{} to ignore
  space characters or end-of-line space after it) to:
  \centeredline{\cs{the}\cs{numexpr}\meta{expression}\cs{relax}} In an analogous way
  |\bnumeval|\marg{expression} has equivalent forms:
  \centeredline{|\bnethe\bnumexpr|\meta{expression}\cs{relax}}
  \centeredline{|\thebnumexpr|\meta{expression}\cs{relax}} For contexts where
  the alternative forms may be useful, refer to the \autoref{sec:advanced}.
  Everyday use needs only |\bnumeval|.
\end{TeXnote}

Here are the extra features from |\bnumeval| compared to \cs{inteval}:
\begin{enumerate}[noitemsep,label=\textbf{(\alph*)}]
\item It allows arbitrarily big integers, whereas \cs{inteval} is limited
  to a maximal input equal to $\the\numexpr"7FFFFFFF\relax$
  ($2^{31}-1$, or hexadecimal |7FFFFFFF|).
\item It recognizes |**| and |^| as infix operator for powers,
\item It recognizes |!| as postfix operator for the factorials,
\item The new operator |//| computes floored division with |/:| being the
      operator for the associated remainder (the operator |/| computes
      rounded division),
    \item In addition to the \TeX{} prefixes |'| and |"| for octal and
      hexadecimal, it recognizes |0b|, |0o| and |0x| for
      binary, octal, and hexadecimal,
\item Letters in lowercase can be used for hexadecimal input,
\item The space character is ignored%
%
\footnote{It is not completely ignored, |\count37<space>| will automatically
  be prefixed by \cs{number} and the space token delimits the integer indexing
  the count register. Also, devious inputs using nested braces around spaces
  may create unexpected internal situations and even break the parser.}
%
and can thus be used to separate in the
  source blocks of digits for better readability of long numbers,
\item Also the underscore |_| may be used as visual digit
  separator,
\item Braced material \string{...\string} encountered in the expression is automatically
  unbraced,
\item Comma separated expressions are allowed,
\item Some idiosyncrasies of \cs{numexpr} such as |\inteval{-(1)}| causing an
  error are avoided,
\item Syntax is fully customizable and extensible.
\end{enumerate}
%
Note the following about octal and binary inputs:
\begin{quote}
  When parsing an octal number, if digits |8| or |9| are encountered, or when
  parsing binary any digit larger than |1|, this does not cause an error but
  ends the parsing and inserts a tacit multiplication: |\bnumeval{'118+3}|
  computes $9\times 8 + 3$, hence obtains \bnumeval{'118+3}.
\end{quote}
%
Also note the following on precedences:
\begin{quote}
  As is explained with complete details in \autoref{sssec:prec}, the
  multiplication operator, as well as the division operators (including
  modulo) are all at the same precedence level and behave in a
  left-associative way.  Example: |\bnumeval{10*10//3, 10*10/:3}|
  evaluates to \textcolor{digitscolor}{33, 1}, not to
  \textcolor{digitscolor}{30, 10}.

  Also note that powers (which obviouly have
  a higher precedence) work in a right-associative way so that
  |\bnumeval{2^3^4}| will compute \textcolor{digitscolor}{$2^{81}$}, not
  \textcolor{digitscolor}{$8^{4}$}.

  The best among our users know how to set operator precedences to their own
  liking, as they have read all the fine print in \autoref{ssec:expert}.
\end{quote}
%
Furthermore, |\bnumeval| recognizes an optional argument |[b]|, |[o]|, |[h]|,
or |[ha]| which says to have the calculation result (or comma separated results)
be converted to respectively binary, octal, hexadecimal (uppercase) or
lowercase hexadecimal digits.

\section{Dependencies}

\bnumname is a \LaTeX{} package but it can also be used with Plain \TeX,
thanks to \ctanpackage{miniltx}.   Use for this
\cs{input} \ctanpackage{miniltx}|.tex|
and then \cs{input} \bnumname \unskip|.sty|.  Do not use \cs{input} but only
\cs{usepackage} to load the package with \LaTeX.

Addition, subtraction, multiplication, division(s), modulo operator, powers,
and factorials are all by default executed by macros provided by the
\xintcorename package.

Conversions between decimal, binary, octal and
hexadecimal bases are done using the macros from the \xintbinhexname package.

|\bnumeval| is a scaled-down variant of |\xintiieval| from package
\xintexprname, lacking support for nested structures, functions, variables,
booleans, sequence generators, etc... .
The \xintexprname package is NOT loaded, only as said previously
\xintcorename and \xintbinhexname.

\begin{TeXnote}
  Power users can use |\bnumsetup| to configure usage of alternative support
  macros of their own choosing.  Options can disable the loading of
  \xintcorename and/or \xintbinhexname.  But \xintkernelname is always loaded.
  See \autoref{sec:advanced}. Expert users can even add new operators to the
  syntax, even change the built-in ones or their precedences.  See
  \autoref{ssec:expert}.
\end{TeXnote}

\section{Examples}

Some of these examples use the ancient syntax
|\bnethe\bnumexpr...\relax| from the initial release (in 2014).  The
|\bnethe| prefix converts from some private format (using braces and
other things).  Some examples do not even have the |\bnethe| prefix to
|\bnumexpr| because it is allowed in typesetting context to omit it (but
in an \cs{edef} without it expansion gives the private format). For
details refer to \autoref{sec:advanced} on advanced topics.

Some further examples found in this documentation use the other ancient
syntax |\thebnumexpr...\relax| where |\thebnumexpr| is equivalent to
|\bnethe\bnumexpr|.

The recommended interface is |\bnumeval|, as it has optional
arguments to cause conversion to hexadecimal, octal or binary.  They
have no equivalent with |\bnethe\bnumexpr| or with |\thebnumexpr|.

Some inputs are weird (such as the first one with three minus signs)
because they served originally to check the syntax.

% NOTE 2026/09/07. After the 1.6 blunder which shipped with \bnumexpr not
% accepting Babel active contrarily to what announcement (but \bnumeval was
% ok, and it is the recommened interface) and code comments said, I modified
% the preamble to add "french" as language in order to test it here. This
% means though that ! and : will be catcode active throughout the document
% (even though French is used only locally) so this means my unit testing here
% is not anymore one for the standard context with default catcodes but well.
%
% The \bneshow and other macros not only display the values but also save
% input and output to an auxiliary file which is tested via git diff to check
% for changes and detect regressions.
%
% Even with French active, one should beware that in vertical mode it
% self-silences itself, and one can be fooled to think no error happened, if
% the \bnumeval or \bnumexpr is first thing in the paragraph.  But here the
% \bneshow and other macros will quit vertical mode prior to computing the
% expression, so that trap is avoided.
\noindent\bneshow {--- 1 208 637 867 * (2 187 917 891 - 3 109 197 072)}%
\bneshow { ( 13_8089_1090 - 300_1890_2902 ) * ( 1083_1908_3901 - 109_8290_3890 )}%
\bneshoweval {92_874_927_979 ** 5 - 31_9792_7979 ** 6}%
\foreignlanguage{french}{%
  \bneshowthe  {10!, 20!, 30!}% broken at 1.6... fixed at 1.6a
  Testing tacit multiplication elevated precedence\string:\newline
  \bneshoweval {30!/(21*22*23*24*25)(26*27*28*29*30), 20!}% worked as expected at 1.6
  \bneshoweval {13^50//12^50, 13^50/:12^50}% idem
}%
\bneshoweval {13^50/12^50, 12^50}%
\bneshoweval {(1^10+2^10+3^10+4^10+5^10+6^10+7^10+8^10+9^10)^3}%
\bneshoweval {100! /: 10^50}%
%
Let's check hexadecimal input:\newline
\bneshoweval {"0010 * "0100 * 0x1000 * 0xA0000, 16^(1+2+3+4)*10}%
\bneshoweval {"000_abc_def, 0xABCDEF, "abcdef + "543210 + 1, 0x10^6}%
And also hexadecimal output:\newline
\bneshowevaltohex{"_7f_fff_fff+1, 0x_0400^3, "aBcDeF*"0000fedcba,
  1234}
And also in lowercase\newline
\bneshowevaltolowhex{"abcdef, "ABCDEF, "999_999_999, 16^10-1, 167772160}
%
Let's make a few checks of octal and binary:\newline
\bneshowevaltooct {'75316420 * 0o44445555}
\bneshowevaltobin {'75316420 * 0o44445555}
\bneshowevaltobin {0xFFFF, 0o77, 0b1000001^3}
%
We end with some strange non-recommended things to check details
of how the parser expands the input:\newline
\bneshoweval {"0000\bnumeval[h]{00000012345678}FFFF, 000012345679*16**4-1}
%
\bneshowevaltooct{0b000\bnumeval[b]{'123456}, 0x\bnumeval[h]{0o00000123456}}

\section{Customizing how output is ``printed out''}

\subsection{Printing big numbers}
\label{sec:printing}
\TeX{} and \LaTeX{} will not split long numbers at the end of lines. I personally
often use helper macros (not in the package) of the following type:

\begin{verbatim}
\def\allowsplits #1{\ifx #1\relax \else #1\hskip 0pt plus 1pt\relax
                    \expandafter\allowsplits\fi}%
\def\printnumber #1{\expandafter\allowsplits \romannumeral-`0#1\relax }%
\end{verbatim}

\def\allowsplits #1{\ifx #1\relax \else #1\hskip 0pt plus 1pt\relax
                    \expandafter\allowsplits\fi}%
\def\printnumber #1{\expandafter\allowsplits \romannumeral-`0#1\relax }%

Here is an example of use and its output:
\begin{verbatim}
\noindent|\bnumeval{1000!} =|
\textcolor{digitscolor}{\printnumber{\bnumeval{1000!}}}
\end{verbatim}
\noindent|\bnumeval{1000!} =|\phantom{0}%
\textcolor{digitscolor}{\printnumber{\bnumeval{1000!}}}

\begin{TeXnote}
  |\bnumeval| is \emph{f}-expandable, so the |\romannumeral-`0| as used
  here in |\printnumber| causes its full expansion (even if for example
  the output contains multiple values, separated by commas).  So then
  |\printnumber|'s auxiliary can simply loop over the tokens.
\end{TeXnote}

\begin{TeXnote}
  Note that inside math mode, the inserted \cs{hskip}'s have no effect.
  There should be some \cs{allowbreak}'s.  By the way, we allow some
  stretch so that line endings match the actual linewidth.
\end{TeXnote}

\subsection{\cs{bnumprintone}, \cs{bnumprintonesep}}

The output values are each fetched to
|\bnumprintone| and separated by |\bnumprintonesep|.

Here are the default definitions (or rather some quasi equivalents in \LaTeX's
lingua):
\begin{verbatim}
\newcommand{\bnumprintone}[1]{#1}
\newcommand{\bnumprintonesep}{, }
\end{verbatim}

In other terms |\bnumprintone| produces its argument ``as is'', and multiple
values get separated by a comma and a space.

Let's say you want the output to be boxed. Doing
|\fbox{\bnumeval{...}}| will make one single frame even in case
of multiple values.  Redefining |\bnumprintone|
is the way to go:
\begingroup\RenewDocumentCommand{\bnumprintone}{m}{\fbox{#1}}
\begin{verbatim}
\RenewDocumentCommand{\bnumprintone}{m}{\fbox{#1}}
\bnumeval{2^10, 3^10, 5^10, 7^10}
\end{verbatim}
\textcolor{digitscolor}{\bnumeval{2^10, 3^10, 5^10, 7^10}}
\endgroup

It is important to have used |\RenewDocumentCommand| and not \cs{renewcommand}
here, because |\bnumprintone| and |\bnumprintonesep| have to be compatible
with expansion only context.
\begin{TeXnote}
  That means that |\bnumprintone| in an \cs{edef} should not give rise to any
  \cs{newcommand}, lower level \cs{def}, count or dimen assignments, etc....

  This constraint is due to the fact that |\bnumeval| wraps the final
  print-out inside of \cs{expanded}, for \TeX nical reasons.

  The simplest way for |\bnumprintone| (considering that
  its argument will already have been fully expanded to digit tokens) and
  |\bnumprintonesep| to be ``safe'' is that they do not expand at all in
  \cs{edef}.  This is the case if they are defined using
  \cs{RenewDocumentCommand}.
%
  With an older \LaTeX{}, or Plain \eTeX{}
  (but having some \cs{fbox} at our disposal), we would have used here
  |\protected\def\bnumprintone#1{\fbox{#1}}|.
\end{TeXnote}

A more common use case will be to have the outputs be typeset according to the
conventions of the document language.  This is easily done redefining
|\bnumprintone| in terms of (for example) the \cs{np} macro of package
\ctanpackage{numprint}.
\begingroup\RenewDocumentCommand{\bnumprintone}{m}{\np{#1}}
\renewcommand{\bnumprintonesep}{ --- }
\begin{verbatim}
\RenewDocumentCommand{\bnumprintone}{m}{\np{#1}}
\renewcommand{\bnumprintonesep}{ --- }
\bnumeval{2^10, 3^10, 5^10, 7^10}
\end{verbatim}
\textcolor{digitscolor}{\bnumeval{2^10, 3^10, 5^10, 7^10}}
\endgroup

\begin{TeXnote}
  Usage of \cs{RenewDocumentCommand} for |\bnumprintonesep| was not
  needed here, obviously its expansion could cause no trouble.
\end{TeXnote}

Let's give another use case.  Assume you are computing in one go multiple
large values, too large to fit on a line.  The simple-minded |\printnumber| of
the previous section will (due to some \TeX nicality) swallow the spaces
injected by |\bnumprintonesep|.  To fix this, the simplest is to redefine
|\bnumprintone| to execute |\printnumber|:
\begingroup\let\bnumprintone\printnumber
\begin{verbatim}
\renewcommand{\bnumprintone}[1]{\printnumber{#1}}
\bnumeval{2^100, 3^100, 5^100, 7^100}
\end{verbatim}
% je ne peux pas utiliser \bneshoweval car il passe ne mode mathématique
\textcolor{digitscolor}{\bnumeval{2^100, 3^100, 5^100}}
\endgroup

\begin{TeXnote}
  Our |\printnumber| belongs to this family of macros causing no damage
  if expanding in an \cs{edef}.  So, it was not needed to use
  \cs{RenewDocumentCommand}.
\end{TeXnote}

\subsection[\cs{bnumprintonehex}, \cs{bnumprintonelowerhex},
\cs{bnumprintoneoct},\texorpdfstring{\newline}{}
\cs{bnumprintonebin}]{\cs{bnumprintonehex}, \cs{bnumprintonelowerhex},
                      \cs{bnumprintoneoct}, \cs{bnumprintonebin}}

When |\bnumeval| is exerted with |[h]|, |[ha]|, |[o]| or |[b]|
it does not use |\bnumprintone| but one of |\bnumprintonehex|,
|\bnumprintonelowerhex|,
|\bnumprintoneoct| or |\bnumprintonebin|.
The same |\bnumprintonesep| is used as with decimal numbers.

The default definitions are as for |\bnumprintone| to ``print as is''.

\begingroup
\def\bnumprintonehex#1{0x#1}
\def\bnumprintonelowerhex#1{"#1}
To give an example of a custom
definition, one may want hexadecimal to use the |0x| prefix with
uppercase output or the |"| prefix with lowercase output.  This is
very easy:
\begin{verbatim}
\renewcommand{\bnumprintonehex}[1]{0x#1}
\renewcommand{\bnumprintonelowerhex}[1]{"#1}
\end{verbatim}
\bneshowevaltohex{7^30, 13^20, 20!}
\bneshowevaltolowhex{7^30, 13^20, 20!}
\endgroup

\begin{TeXnote}
  It was unneeded to use \cs{RenewDocumentCommand} here because prefixing
  with |0x| is obviously compatible with expansion-only context.
\end{TeXnote}

\section{Babel-active characters are not a problem!}

Some languages use active characters with PDF\LaTeX.  For example the
|babel-french| module turns the colon |:| and the exclamation mark |!|
into active characters (whose expansions would cause |\bnumeval| to
crash).  It used to be necessary to take preventive measures such as
either turning the activation off altogether or use in the input
|/\string:| and |\string!| as clumsy replacements of |/:| and |!|.

Those troubled times are gone!  With release |1.6| they will work fine
as is in |\bnumeval|.  The same applies to all other characters if
babel-active. There are miracles sometimes!

Warning: characters made active otherwise still need the \cs{string} or
other workaround to be usable as operators in the syntax.

\section{Fine print (not needed to read this for regular use)}
\label{sec:advanced}

\footnotesize

\subsection{Adding support for binomial coefficients}
\label{ssec:binomial}

As will be documented in the section for expert users, it is possible
to extend the syntax with one's own operators.

Let's turn the semicolon into an operator which computes binomial
coefficients: |a;b| will evaluate to ``|a| choose |b|''.  The precedence will
be chosen stronger than addition and multiplication but less than powers.
This only needs adding those two lines to the preamble:
\begin{verbatim}
\usepackage{xint}% as xintcore does not have \xintiiBinomial
\bnumdefinfix{;}{\xintiiBinomial}{15}{15}
\end{verbatim}
\foreignlanguage{french}{%
We can now test it:\newline
\bnumdefinfix{\string;}{\xintiiBinomial}{15}{15}%
\bneshoweval{100;50}
\bneshoweval{3^5;2^5}
}

\subsection{An expandable macro for computing large Fibonacci numbers}
\label{ssec:fibo}

% added 2025/09/22
We are going to define an expandable macro which takes on input a positive
integer $N$ (we did not bother about case of zero or negative input) and
computes the $N$\textup{th} Fibonacci number $F_N$.

The algorithm to compute $F_N$ is based upon the quadratic relations which
allow to go from $(F_n,F_{n-1})$ to either $(F_{2n}, F_{2n-1})$ (operation of
type A) or to $(F_{2n+1}, F_{2n})$ (operation of type B).  We first convert
$N$ into binary base, and read the digits from most significant to least
significant applying each time either type A or type B.  Each one of type A or
B will cost only 3 multiplications and a bunch of additions, which of course
are done using |\bnumexpr|.  In total the algorithm costs (because $3\log_2
10\approx 10$) about $10L$ multiplications (doubling is not counted as a
multiplication) where $L$ is the number of decimal digits of $N$.  Here is the
code:
\begin{verbatim}
\makeatletter
\newcommand\Fibonacci[1]{%
    \expandafter\Fibo@start
    \romannumeral0\xintdectobin{\the\numexpr#1\relax};%
}
% To chain \bnumexpr's easily we use the once-expanded form which is
% \romannumeral0\bnumexpro.  We could also have used \expanded and \bnumeval
% calls, that would have been simpler but old habits die hard.
%
% Gobble the first digit which is 1 (this is where we could have tested for a
% negative or zero input).  Prepare encapsulated 1 and 0.  Then repeatedly
% dispatch according to binary digits (which are strategically located at end
% of input stream to optimize), or the semicolon which signals completion.
%
% We use \csname-governed branching (because it is cool).  We could have
% used some \if test or even an \ifcase with an {-1} at end, or any other
% of the many possibilities opened up by TeX syntax.
\def\Fibo@start 1#1{%
    \csname Fibo@#1\expandafter\endcsname
        \romannumeral0\bnumexpro 1\expandafter\relax\expandafter;%
        \romannumeral0\bnumexpro 0\relax;%
}
\@namedef{Fibo@0}#1;#2;#3{%
    \csname Fibo@#3\expandafter\endcsname
       \romannumeral0\bnumexpro (#1+2*#2)*#1\expandafter\relax\expandafter;%
       \romannumeral0\bnumexpro #1*#1+#2*#2\relax;%
}
\@namedef{Fibo@1}#1;#2;#3{%
    \csname Fibo@#3\expandafter\endcsname   
        \romannumeral0\bnumexpro 2*(#1+#2)*#1+#2*#2\expandafter\relax\expandafter;%
        \romannumeral0\bnumexpro (#1+2*#2)*#1\relax;%
}
% #1 is F_N (and #2 is F_{N-1}) but in need of unpacking.
\@namedef{Fibo@;}#1;#2;{\bnethe#1}
\makeatother
\end{verbatim}

\makeatletter
\newcommand\Fibonacci[1]{%
    \expandafter\Fibo@start
    \romannumeral0\xintdectobin{\the\numexpr#1\relax};%
}
\def\Fibo@start 1#1{%
    \csname Fibo@#1\expandafter\endcsname
        \romannumeral0\bnumexpro 1\expandafter\relax\expandafter;%
        \romannumeral0\bnumexpro 0\relax;%
}
\@namedef{Fibo@0}#1;#2;#3{%
    \csname Fibo@#3\expandafter\endcsname
       \romannumeral0\bnumexpro (#1+2*#2)*#1\expandafter\relax\expandafter;%
       \romannumeral0\bnumexpro #1*#1+#2*#2\relax;%
}
\@namedef{Fibo@1}#1;#2;#3{%
    \csname Fibo@#3\expandafter\endcsname   
        \romannumeral0\bnumexpro 2*(#1+#2)*#1+#2*#2\expandafter\relax\expandafter;%
        \romannumeral0\bnumexpro (#1+2*#2)*#1\relax;%
}
\@namedef{Fibo@;}#1;#2;{\bnethe#1}
\makeatother

We can now compute for example $F_{10}$, $F_{100}$, $F_{1000}$ and $F_{10000}$:
\begin{verbatim}
\noindent\textcolor{cyan}{$F_{10}={}$}%
  \textcolor{digitscolor}{\Fibonacci{10}}\newline
\textcolor{cyan}{$F_{100}={}$}%
  \textcolor{digitscolor}{\Fibonacci{100}}\newline
\textcolor{cyan}{$F_{1000}={}$}%
  \textcolor{digitscolor}{\printnumber{\Fibonacci{1000}}}\newline
\textcolor{cyan}{$F_{10000} ={}$}%
  \textcolor{digitscolor}{\printnumber{\Fibonacci{10000}}}
\end{verbatim}
\noindent\textcolor{cyan}{$F_{10}={}$}%
  \textcolor{digitscolor}{\Fibonacci{10}}\newline
\textcolor{cyan}{$F_{100}={}$}%
  \textcolor{digitscolor}{\Fibonacci{100}}\newline
\textcolor{cyan}{$F_{1000}={}$}%
  \textcolor{digitscolor}{\printnumber{\Fibonacci{1000}}}\newline
\textcolor{cyan}{$F_{10000} ={}$}%
  \textcolor{digitscolor}{\printnumber{\Fibonacci{10000}}}
\begin{TeXnote}
  We did not use math mode because our |\printnumber| is a bit dumb and does
  not work in math mode.  See the comment at the end of \autoref{sec:printing}.
\end{TeXnote}
\begin{TeXnote}
  In the code above we used |;| as delimiter.  But the macro definitions are
  located in the body of this document, not in the preamble, and |;| is
  catcode active here due to French being among the languages used by this
  \LaTeX{} document.  This is not a problem in itself for the uses of the
  semicolon as delimiter for macro definitions.  It would be a problem if
  the semicolon had been used to terminate a \cs{numexpr} (we would then
  have injected a \cs{relax}).  One could also be
  very worried seeing |\@namedef{Fibo@;}| where |;| will expand in
  |\csname...\endcsname|, but \ctanpackage{babel} has a built-in protection
  for such things.  This all being said, it is safer to have such definitions
  located in the preamble of the \LaTeX{} document.  But in next section we
  make a modification which can only be done after the original |\Fibonacci|
  has already been used (as it changes its interface), so putting all things
  into the preamble is not always logical nor convenient.
\end{TeXnote}

\subsection{More fun with Fibonacci numbers}

% added 2025/09/27
Our |\Fibonacci|\marg{N} macro expands to compute $F_N$.  But what if we want
a whole sequence $F_N, F_{N+1}, \dots$? Then using directly the additive
recurrence $F_{n+2}=F_{n+1}+F_n$ will be more efficient than computing again
and again via our fancy multiplicative algorithm new values.  So it is a bit
sad that expansion of |\Fibonacci|\marg{N} actually computed both of $F_N$ and
$F_{N-1}$ but discarded the latter.  We will modify its ending to keep also
$F_{N-1}$.  And we switch to a down-to-earth style based on assignments. The
\xintname documentation provides a variant construct which obtains expandably
a sequence of braced items |{F_a}{F_{a+1}}...{F_b}|, but for this, expandable
loops from \ctanpackage{xinttools} or from \LaTeX3 are the appropriate tools,
and we then need extra tools to handle such output.
\begin{verbatim}
\makeatletter
% reuse \Fibonacci's earlier meaning, then make \Fibonacci undefined
% because \Fibo@; will be modified.
\let\Fibonacci@\Fibonacci
\let\Fibonacci\undefined
\newcommand\SetToFibonacciPair[3]{\Fibonacci@{#1}{#2}{#3}}
\@namedef{Fibo@;}#1;#2;#3#4{\def#3{#1}\def#4{#2}}
\makeatother
\end{verbatim}
\makeatletter
\let\Fibonacci@\Fibonacci
\let\Fibonacci\undefined
\newcommand\SetToFibonacciPair[3]{\Fibonacci@{#1}{#2}{#3}}
\@namedef{Fibo@;}#1;#2;#3#4{\def#3{#1}\def#4{#2}}
\makeatother
\SetToFibonacciPair{1001}{\FibB}{\FibA}

We add the above code to our document and now execute
%
\centeredline{|\SetToFibonacciPair{1001}{\FibB}{\FibA}|}
%
Now |\FibB| holds $F_{1001}$ and |\FibA| holds $F_{1000}$ (both being
|\bnumexpr|-encapsulated and needing |\bnethe| to get converted to explicit
digits).  We can use LaTeX's |\loop| to organize the iterative computation and
printing of new values.  But we first abstract the ``printing'' into a
\LaTeX{} ``command''.
\begin{verbatim}
\newcommand\PrintOneFib[2]{%
  \noindent\textcolor{cyan}{$F_{#1}={}$}\textcolor{digitscolor}{\printnumber{\bnethe#2}}%
}
\def\myN{1000}
\loop
  \PrintOneFib{\myN}{\FibA}\newline
  \edef\FibC{\bnumexpr \FibB + \FibA\relax}% compute and assign a new Fibonacci number
  \let\FibA\FibB \let\FibB\FibC % update the storage macros
\ifnum\myN<1010 % terminate the loop when F_{1010} has been printed
  \edef\myN{\the\numexpr\myN+1}%
\repeat
\end{verbatim}
\newcommand\PrintOneFib[2]{%
  \noindent\textcolor{cyan}{$F_{#1}={}$}%
  \textcolor{digitscolor}{\printnumber{\bnethe#2}}%
}
\def\myN{1000}
\loop
\PrintOneFib{\myN}{\FibA}\newline
\edef\FibC{\bnumexpr \FibB + \FibA\relax}%
\let\FibA\FibB
\let\FibB\FibC
\ifnum\myN<1010
\edef\myN{\the\numexpr\myN+1}%
\repeat

At this stage we have printed up to $F_{1010}$. On exit of the loop |\FibA|
holds $F_{1011}$ and |\FibB| holds $F_{1012}$.  We print them out too:
\begin{verbatim}
\PrintOneFib{\the\numexpr\myN+1}{\FibA}\newline
\PrintOneFib{\the\numexpr\myN+2}{\FibB}\par
\end{verbatim}
\PrintOneFib{\the\numexpr\myN+1}{\FibA}\newline
\PrintOneFib{\the\numexpr\myN+2}{\FibB}\par

\makeatletter
\@namedef{Fibo@;}#1;#2;{\bnethe#1}
\ifnum\pdfstrcmp{\bnethe\FibA}{\Fibonacci@{1011}}=0 \else\ERROR\fi
\ifnum\pdfstrcmp{\bnethe\FibB}{\Fibonacci@{1012}}=0 \else\ERROR\fi
\makeatother

\subsection{The \cs{bnumsetup} command}

Package \bnumname needs that some \emph{big integer engine} provides the macros doing
the actual computations.

By default, it loads package \xintcorename (a subset of \xintexprname) and
package \xintbinhexname.
\begin{verbatim}
  \usepackage{xintcore}
  \usepackage{xintbinhex}
\end{verbatim}

It then uses |\bnumsetup| in the following way (the final comma is optional,
and spaces around equal signs also; there can also be spaces before the commas
but the author dislikes such style a lot so they are not used here):
\begin{verbatim}
  \bnumsetup{%
    add = \xintiiAdd, sub = \xintiiSub, opp = \xintiiOpp,
    mul = \xintiiMul, pow = \xintiiPow, fac = \xintiiFac,
    div=\xintiiDivFloor, mod=\xintiiMod, divround=\xintiiDivRound,
    hextodec=\xintHexToDec, octtodec=\xintOctToDec, bintodec=\xintBinToDec,
    dectohex=\xintDecToHex, dectooct=\xintDecToOct, dectobin=\xintDecToBin,
  }%
\end{verbatim}

One can use |\bnumsetup| to map one, some, or all keys to macros of one's
own choosing.  Of course it is then up to user to load the suitable packages.

If one has alternatives for all of the above \xintcorename macros, so that
this package is not needed at all, one can pass option |customcore| to
\bnumname at loading time:
\centeredline{|\usepackage[customcore]|\{\bnumname\}} This tells to not load
\xintcorename.

Similarly there is an option |custombinhex| to not load \xintbinhexname.
Make sure then to provide suitable replacements to all base conversion macros!

Option |custom| means doing both of |customcore| and |custombinhex|.
Even under this option package \xintkernelname will always be loaded.

Here are the conditions that the custom macros must obey:
\begin{enumerate}[noitemsep]
\item They all must be \emph{f}-expandable, with some exceptions
  \begin{enumerate}[noitemsep]
  \item The macro for computing factorials only has to be \emph{x}-expandable.
  \item If |\bnumprintonehex| (or |\bnumprintoneoct|, or |\bnumprintonebin|)
    is redeinfed as a \cs{protected}, then conversion to hexadecimal
    (resp. octal, resp. binary) only has to be \emph{x}-expandable, except
    that
    |[ha]| option will break if the |dectohex| macro is not \emph{f}-expandable.
  \end{enumerate}
  Note that any \emph{x}-expandable macro can be wrapped into an
    \emph{f}-expandable one, using \cs{expanded}.
\item It is sufficient for them to be able to handle arguments in \emph{raw
    normalized form}, i.e., sequences of explicit decimal (or hexadecimal for
  the macro associated with key |hextodec|) digits, no leading zeros, with at
  most one minus sign and no plus sign.
\item Their output format is limited only by the fact that it should be
  acceptable input to all the other operators, as well as to the user optional
  re-definition of |\bnumprintone|. If one cares about hexadecimal (et al.)
  output one must ensure the macros output format is suitable input for those
  macros actually doing the conversion from decimal to other bases.
\item \emph{Important}: hence if only some macros among those associated to
  operators (i.e. those by default originating in \xintcorename), or to
  conversions into decimal, are custom, their output \textbf{must} be produced
  in \emph{raw normalized form}, as this is the format required by the
  \xintcorename macros and by the \xintbinhexname macros converting from
  decimal to other bases.  However if one does not care about producing output
  in binary, octal or hexadecimal (as is the case in the next section),
  and if one has replaced all \xintcorename macros, the output format can be
  as one likes.
\item  If |dectohex| key is used and the associated custom macro produces
  \emph{lowercase} hexadecimal, then |\bnumeval[h]{...}| produces as
  instructed hexadecimal in lowercase.  Mind that the |[ha]| option is now
  broken because its internals expect the input to be in uppercase
  hexadecimal, and this input is computed using the macro assigned to the
  |dectohex| key.  This is of course unimportant because |[h]| now has
  replaced |[ha]|, but I figured I could mention it here.
\end{enumerate}

\subsection{Let's handle fractions!}

I will show how to transform |\bnumeval| into a calculator with fractions! We
will use the \xintfracname macros, but coerce them into always producing
fractions in lowest terms (except for powers). For optimization we use
the |[0]| post-fix which speeds-up the input parsing by the \xintfracname
macros. We remove it on output via a custom |\bnumprintone|.

Note that the |/| operator is associated to |divround| key but of course here
the used macro will simply do an exact division of fractions, not a rounded-to-%
an integer division. This is the whole point of using a macro of our own
choosing!
\begin{verbatim}
\usepackage{xintfrac}
\newcommand\myIrrAdd[2]{\xintIrr{\xintAdd{#1}{#2}}[0]}
\newcommand\myIrrSub[2]{\xintIrr{\xintSub{#1}{#2}}[0]}
\newcommand\myIrrMul[2]{\xintIrr{\xintMul{#1}{#2}}[0]}
\newcommand\myDiv[2]{\xintIrr{\xintDiv{#1}{#2}}[0]}
\newcommand\myDivFloor[2]{\xintDivFloor{#1}{#2}[0]}
\newcommand\myMod[2]{\xintIrr{\xintMod{#1}{#2}}[0]}
\newcommand\myPow[2]{\xintPow{#1}{#2}}% will have already postfix [0]
\newcommand\myFac[1]{\xintFac{#1}}%     will have already postfix [0]
\makeatletter
\def\myRemovePostFix#1{\@myRemovePostFix#1[0]\relax}%
\def\@myRemovePostFix#1[0]#2\relax{#1}
\makeatother
\let\bnumprintone\myRemovePostFix
\bnumsetup{add=\myIrrAdd, sub=\myIrrSub, mul=\myIrrMul,
           divround=\myDiv, div=\myDivFloor,
           mod=\myMod, pow=\myPow, fac=\myFac}%
\end{verbatim}
\begingroup
\newcommand\myIrrAdd[2]{\xintIrr{\xintAdd{#1}{#2}}[0]}
\newcommand\myIrrSub[2]{\xintIrr{\xintSub{#1}{#2}}[0]}
\newcommand\myIrrMul[2]{\xintIrr{\xintMul{#1}{#2}}[0]}
\newcommand\myDiv[2]{\xintIrr{\xintDiv{#1}{#2}}[0]}
\newcommand\myDivFloor[2]{\xintDivFloor{#1}{#2}[0]}
\newcommand\myMod[2]{\xintIrr{\xintMod{#1}{#2}}[0]}
\newcommand\myPow[2]{\xintPow{#1}{#2}}% will have already postfix [0]
\newcommand\myFac[1]{\xintFac{#1}}%     will have already postfix [0]
\makeatletter
\def\myRemovePostFix#1{\@myRemovePostFix#1[0]\relax}%
\def\@myRemovePostFix#1[0]#2\relax{#1}
\makeatother
\let\bnumprintone\myRemovePostFix
\bnumsetup{add=\myIrrAdd, sub=\myIrrSub, mul=\myIrrMul,
           divround=\myDiv, div=\myDivFloor,
           mod=\myMod, pow=\myPow, fac=\myFac}%
\bneshoweval{1000000*(1/100+1/2^7-20/5^4)/(1/3-5/7+9/11)^2}
\bneshoweval{(1-1/2)(1-1/3)(1-1/4)(1-1/5)(1-1/6)(1-1/7)}
\bneshoweval{(1-1/3+1/9-1/27-1/81+1/243-1/729+1/2187)^5}
\bneshoweval{(1+1/10)^10 /: (1-1/10)^10}
\bneshoweval{2^-3^4}
\endgroup

Computations with fractions quickly give birth to big results, see
\autoref{sec:printing} on how to modify |\bnumprintone| to coerce \TeX\ into
wrapping numbers too long for the available width.

\subsection{For the expert user: expression syntax and its customizability}
\label{ssec:expert}

\subsubsection{Significant differences between \cs{bnumexpr} and \cs{numexpr}}
\label{sec:differences}

Apart from the extension to big integers and the added operators, there
are a number of important differences between |\bnumexpr| and
|\numexpr|:

\begin{enumerate}[noitemsep]
\item Contrarily to \cs{numexpr}, the |\bnumexpr| parser stops only after having
  found (and swallowed) a mandatory ending \cs{relax} token (it can arise from expansion),

\item In particular note that spaces between digits do not stop |\bnumexpr|, in contrast
  with \cs{numexpr}:

  |\the\numexpr 3 5+79\relax| expands (in one step) to \expandafter|\the\numexpr 3 5+79\relax|

  |\thebnumexpr 3 5+79\relax| expands (in two steps) to
  \expandafter\expandafter\expandafter|\thebnumexpr 3 5+79\relax|

\item With |\edef\myvariable{\bnumexpr 1+2\relax}|, the computation is
  done at time of the \cs{edef}. It prepares |\myvariable| as a
  self-contained pre-computed unit which is recognized as such when
  inserted in a \bnumname expressions.  It triggers tacit
  multiplication: |7\myvariable| is like |7*\myvariable|.  This is
  different from what would happen if we had used
  |\edef\myvariable{\bnethe\bnumexpr...}| which would simply have
  |\myvariable| expand to digit tokens so |7\myvariable| then constructs
  a number with |7| as first digit.

  \begin{quote}
    Let's give an example.  Note that \cs{edef} has the effect of
    pre-evaluating. With \cs{def} the outputs would be the same, but the
    computations would be delayed to |\bnumeval| execution.

    % attention mon \verb incapable de gérer %
    \noindent|\edef\x{\bnumexpr 3^10\relax}|%
    \% precomputes but keeps private format\newline
    \edef\x{\bnumexpr 3^10\relax}\bneshoweval{10000\x}

    \noindent|\edef\y{\bnethe\bnumexpr 3^10\relax}|%
    \% evaluates to explicit digits\newline
    \edef\y{\bnethe\bnumexpr 3^10\relax}\bneshoweval{10000\y}

    In the example with |\x|, tacit multiplication applied, whereas in the
    example with |\y| it is as if the digits had been input by hand in place
    of |\y|.
    %
    Note that the tacit multiplication behaves as expected relative to
    powers:\newline \bneshoweval{10^10\x} And we certainly do no want to try
    |10^10\y| which is like |10^10|\expandafter|\y|.
  \end{quote}

  There is no analog with \cs{numexpr}:
  \begin{enumerate}[noitemsep]
  \item |\edef\foo{\numexpr1+2\relax}| will define |\foo| as
    |\numexpr1+2\relax| where the calculation is not yet done.
  \item Inserting the |\foo| as is in the document text causes an error.
  \item Trying |\the\numexpr 7\foo\relax| with such a |\foo| causes an
    error.  One must use the multiplication sign |*| explicitly.
  \end{enumerate}

\item Expressions may be comma separated. On input, spaces are ignored,
  and on output the values are comma separated with a space after
  each comma,

\item |\thebnumexpr -(1+1)\relax| is legal contrarily to |\the\numexpr
  -(1+1)\relax| which errors (also with |-(1)| but |-(1+1)| has a more
  legitimate use of parentheses), and one has to employ workarounds such as
  |\the\numexpr 0-(1+1)\relax|,

\item |\the\numexpr 2+-(1+1)\relax| (or with |-+|) errors (but |+-| and |-+|
  if not followed by parentheses do work), whereas |\bnumexpr| has no such
  counter-intuitive limitation,

\item |\the\numexpr 2\cnta\relax| is illegal (with \cs{cnta} a
  \cs{count}-variable) (note that |\the\dimexpr 2\mydimen\relax| or
  |\the\dimexpr2\dimen0\relax| do work; even |\the\dimexpr 1\count0\relax|
  does work, but |\the\dimexpr \count0\relax| errors!). On the other hand
  |\thebnumexpr 2\cnta\relax| is perfectly legal and does the tacit
  multiplication; also |\thebnumexpr 2\mydimen\relax| works, with |\mydimen|
  getting replaced by its value in scaled points,

\item |\the\numexpr3(4+5)\relax| stops at the parenthesis, i.e.\@ it expands to
  |3(4+5)\relax|, whereas of course |\thebnumexpr 3(4+5)\relax| does the
  computation and expands to
  |27|,

\item |\the\numexpr(4+5)3\relax| expands to |93\relax| whereas
  |\thebnumexpr(4+5)3\relax| does the computation and expands to |27|,

\item |\thebnumexpr (4+5)\count0\relax| expands to nine time the current
  value of |\count0| register, but with |\the\numexpr| there would be a
  \emph{Missing number} error,

\item Generally speaking |\numexpr| does not implement the tacit
  multiplication which |\bnumexpr| does in front of parentheses and
  sub |\numexpr...\relax| expression: or after parentheses, or in-between
  two parenthesized expressions such as happens with |(1+2)(3+4)|,

\item The underscore |_| is accepted within the digits composing a number and
  is silently ignored by |\bnumexpr|.

\end{enumerate}

Regarding constructs such as |\edef\myvariable{\bnumexpr 1+2\relax}|, it
was explained |\myvariable| behaves then in a special way in another
|\bnumexpr| expression (or |\bnumeval|).  It is also worth mentioning
that it can be used directly in the typesetting stream.  But if written
to an external file it will expand to some internal format which is not
documented as it may vary in future.

One can NOT use a |\myvariable| as above in an \cs{ifnum} test,
even if representing a single small integer.  It will work with syntax
such as |\ifnum\bnethe\myvariable=7 ...|.

A point of note is that |\bnethe\myvariable| or
|\bnethe\bnumexpr...\relax| expand to explicit digits so (assuming here
there no other comma separated value computed),
\begin{verbatim}
\ifnum 3>\bnethe\bnumexpr...\relax
...
\fi
\end{verbatim}
is dangerous, because the integer is not properly terminated.
Here one could reverse the order, but the simplest way is simply
to use |\bnumeval|:
\begin{verbatim}
\ifnum 3>\bnumeval{...}
...
\fi
\end{verbatim}
Now, the end of line space injected by \TeX{} will terminate the integer
and make the \cs{ifnum} test safe.

\subsubsection{Expression syntax}
The implemented syntax is the expected one with infix operators and
parentheses, the recognized operators being |+|, |-|, |*|, |/| (rounded
division), |^| (power), |**| (power), |//| (by default floored division), |/:|
(the associated modulo) and |!| (factorial).

One can input hexadecimal numbers as familiar from the \TeX\ number
assignments syntax, i.e.\@ using the |"| prefix.  But also lowercase letters
|abcdef| are accepted in addition to uppercase |ABCDEF| (feature added at
|1.7|).  Release |1.6| added support for the |0x| prefix.  It also added
support for octal input via either |'| or |0o| prefixes, and for binary input
via |0b| prefix.

Commas separating multiple expressions are allowed. The whole expression is
handled token by token, any component (digit, operator, parentheses... even
the ending \cs{relax}) may arise on the spot from macro expansions.  The
underscore |_| can be used to separate digits in long numbers, for readability
of the input.

The precedence rules are as expected and detailed in the next section.
Operators on the same level of precedence (like |*|, |/|, |//|, |/:|) behave
in a left-associative way, and these examples behave as e.g.\@ with Python
analogous operators:\newline \bneshoweval{100//3*4, 100*4//3, 100/:3*4,
  100*4/:3, 100//3/:5}

At |1.5| a change was made to the power operators which became
right-associative. Again, this matches the behaviour e.g.\@ of Python:\newline
\bneshoweval{2^3^4, 2^(3^4)}

It is possible to customize completely the behaviour of the parser, in two ways:
\begin{itemize}
\item via |\bnumsetup| which has a simple interface to replace the macros
  associated with |+|, |-|, |*|, |/|, |//|, |/:|, |**|, |^| and |!| by
  custom macros,
\item or even more completely via |\bnumdefinfix| and |\bnumdefpostfix| which
  allow to add new operators to the syntax! They can also be used to replace
  the built-in ones or modify their precedence levels.
\end{itemize}

\subsubsection{Precedences}
\label{sssec:prec}

The parser implements precedence rules based on concepts which are summarized
below. I am providing them for users who will use the customizing macros.
\begin{itemize}
\item an infix operator has two associated precedence levels, say |L| and
  |R|,
\item the parser proceeds from left to right, pausing each time it has found a
  new number and an operator following it,
\item the parser compares the left-precedence |L| of the new found operator to
  the right-precedence |R_last| of the last delayed operation (which already
  has one argument and would like to know if it can use the new found one): if
  |L| is at most equal to it, the delayed operation is now executed, else the
  new-found operation is kept around to be executed first, once it will have
  gathered its arguments, of which only one is known at this stage.
\end{itemize}

Although there is thus internally all the needed room for sophistication, the
implemented table of precedences simply puts all of multiplication and
division related operations at the same level, which means that left
associativity will apply with these operators. I could see that Python behaves
the same way for its analogous operators.

Here is the default table of precedences as implemented by the package:
\begin{center}
\DeleteShortVerb{\|}%
  \begin{tabular}{|c|c|c|}
    \multicolumn3{c}{Table of precedences}\\\hline
\MakeShortVerb{\|}%
    operator& left &right\\\hline
    |+|,|-|& 12&12\\
    |*|,|/|,|//|,|/:|& 14&14\\
    tacit |*|&16&14\\
    |**|, |^|& 18&17\\
    |!| &20&n/a\\\hline
  \end{tabular}
\end{center}
Tacit multiplication applies in front of parentheses, and after them, also in
front of count variables or registers. As shown in the table it has an
elevated precedence compared to multiplication explicitly induced by |*|, so
|100/4(9)| is computed as |100/36| and not as |25*9|:\newline
\bneshoweval{100/4(9), (100/4)9, 1000 // (100/4) 9 (1+1) * 13}
More generally |A/B(C)(D)(E)*F| will compute |(A/(B*C*D*E))*F|.%
%
\footnote{The |B(C)(D)(E)| product will be computed as |B*(C*(D*E))| because
  the right-precedence of tacit multiplication is |14| but its left-precedence
  is |16|, creating right associativity.  As the underlying mathematical
  operation is associative this is irrelevant to final result.}

The unary |-|, as prefix, has a special behaviour: after an infix operator it
will acquire a right-precedence which is the minimum of |12| (i.e.\@ the
precedence of addition and subtraction) and of the right-precedence of the
infix operator.  For example |2^-3^4| will be parsed as |2^(-(3^4))|, raising
an error because the parser is by default integer only, but see the section
about |\bnumsetup| which explains how to let |\bnumeval| compute fractions!


\subsubsection{\cs{bnumdefinfix}}

It is possible to define infix binary operators of one's own choosing.%
%
\footnote{The effect of |\bnumdefinfix| is global if under |\xintglobaldefstrue| setting.}

For an example see the \autoref{ssec:binomial} on adding |;| as operator
computing binomial coefficients.  Other examples will also be given here.

The syntax is
\centeredline{|\bnumdefinfix|\marg{operator}\marg{\string\macro}\marg{L-prec}\marg{R-prec}}
\begin{description}
\item[\marg{operator}] The characters for the operator, they may be letters or
  non-letters. Digits are not allowed to be first or last in \meta{operator}.
  The following characters are not allowed at all: |\|,
  \textcolor{smallverbcolor}{\{}, \textcolor{smallverbcolor}{\}}, and
  {\catcode`\% 12 |%|}.
  Spaces get removed.

\begin{quote}
  \textbf{Warning:} \meta{operator} will be expanded in an \cs{edef} and then
  further processed.  If it is (or contains) a Babel-active character chances
  are that the further processing by |\bnumdefinfix| will give breakage.
  There will be no problem if |\bnumdefinfix| is used in the document
  preamble.  Else, use |\string|, for example:
  |\bnumdefinfix{\string;}{\xintiiBinomial}{15}{15}|.
\end{quote}

  The underscore and the hash-tag both are allowed, but come with their
  specific provisos:

  \begin{quote}
    The |_| character can be used, but not as first character of the operator,
    as it would be mis-construed on usage as part of the previous number, and
    ignored as such.  Although |\bnumdefinfix{_}...| will not complain, it
    will remain ineffective. There must be some other character first.
  \end{quote}

  \begin{quote}
    The |#| character can be used as an operator name or a character in such a
    name but the definition with |\bnumdefinfix| must be done either with
    |\string#| or with |####| (or even double that if the definition is done
    inside the replacement text of a macro; doubling also applies with the
    |\string| method, one will need |\string##| there) or while having
    temporarily set the catcode to for example letter or other.

      Usage can be done without any special precaution (but as usual |#|
      may need doubling if in the replacement body of some macro definition).
  \end{quote}

\item[\marg{\string\macro}] The expandable macro (expecting two mandatory
  arguments) which is to assign to the infix operator. This macro must be
  \emph{f}-expandable. Also it must (if the default package configuration is
  not modified for the core operators) produce integers in the ``strict''
  format which is expected by the \xintcorename macros for arithmetic: no
  leading zeros, at most one minus sign, no plus sign, no spaces.
\item[\marg{L-prec}] An integer, minimal |4|, maximal |22|, which governs
  the left-\hskip0pt precedence of the infix operator.
\item[\marg{R-prec}] An integer, minimal |4|, maximal |22|, which governs the
  right-\hskip0pt precedence of the infix operator.
\end{description}
Generally, the two precedences are set to the same value.

Once a multi-character operator is defined, the first characters of its name
can be used if no ambiguity.  In case of ambiguity, it is the earliest defined
shortcut which prevails, except for the full name. So for example if |$abc|
operator is defined, and |$ab| is defined next, then |$| and |$a| will still
serve as shortcuts to the original |$abc|, but |$ab| will refer to the newly
defined operator.

Fully qualified names are never ambiguous, and a shortcut once defined will
change meaning only under two circumstances:
\begin{itemize}
\item it is re-defined as the full name of a new operator,
\item the original operator to which the shortcut refers is defined again; then
  the shortcut is automatically updated to point to the new meaning.
\end{itemize}
\begin{verbatim}
\def\equals#1#2{\ifnum\pdfstrcmp{#1}{#2}=0 \expandafter1\else
                                             \expandafter0\fi}
% or:
\def\equals#1#2{\expanded{\ifnum\pdfstrcmp{#1}{#2}=0 1\else0\fi}}
\def\differ#1#2{\expanded{\ifnum\pdfstrcmp{#1}{#2}=0 0\else1\fi}}
\bnumdefinfix{==}{\equals}{10}{10}
\bnumdefinfix{!=}{\differ}{10}{10}
\bnumdefinfix{times}{\xintiiMul}{14}{14}
\bnumdefinfix{++}{\xintiiAdd}{19}{19}
\end{verbatim}
%\def\equals#1#2{\ifnum\pdfstrcmp{#1}{#2}=0 \expandafter1\else\expandafter0\fi}
\def\equals#1#2{\expanded{\ifnum\pdfstrcmp{#1}{#2}=0 1\else0\fi}}
\def\differ#1#2{\expanded{\ifnum\pdfstrcmp{#1}{#2}=0 0\else1\fi}}
\bnumdefinfix{==}{\equals}{10}{10}
\bnumdefinfix{!=}{\differ}{10}{10}
\bnumdefinfix{times}{\xintiiMul}{14}{14}
\bnumdefinfix{++}{\xintiiAdd}{19}{19}
%
\bneshoweval{2 + 3! = 5, 2 + (3!) == 8} Notice in the |2+3! = 5| example that
the existence of |!=| prevails on applying the factorial, so this is test
whether |2+3| and |5| differ; it is not a matter of precedence here, but of
input parsing ignoring spaces. And |2+3! == 8| would create an error as after
having found the |!=| operator and now expecting a digit (as there is no |!==|
operator) the parser would find an unexpected |=| and report an error. Hence
the usage of parentheses in the input.%
%
\footnote{With \xintexprname, whose \cs{xinteval} has a |!=| operator,
  |2+3!==8| is interpreted automatically as |2+(3!)==8|, thanks to internal
  work-around added at |1.4g|.  This has not been backported to \bnumname as
  it does not per default support operators such as |!=| or |==| and only has
  generic support for adding multi-character operators.

  Regarding |2 + 3! = 5|, trying to let this be interpreted as |2+(3!)=5|
  makes sense only if a |=| operator has been defined. If no |!=| operator
  exists, the magic will be automatic. If however both |=| and |!=| exist,
  then it would need special overhead to the parser dealings when finding |!|
  to avoid the |!=| interpretation.  One could imagine distinguishing |! =|
  from |!=| but the swallowing of spaces is deeply coded in the parser.  As
  \bnumname default infix operators do not include one starting with |!|, it is not
  worth it to include in the package extra overhead to solve such issues when
  extending the syntax. At the level of \xintexprname, there is no issue
  because there is no |=| operator.}
% RAPPEL j'ai dû utiliser à un moment \pagebreak[3] ici.  Encore un
% comportement inattendu et sous-optimal de TeX/LaTeX.
\newline
\noindent\bneshoweval{2^5 == 4 times 8, 11 t 14}
\bneshoweval{100 ++ -10 ^ 3, (100 - 10)^3, 2 ^ 5 ++ 3, 2^(5+3)}


\subsubsection{\cs{bnumdefpostfix}}

It is possible to define postfix unary operators of one's own choosing.%
%
\footnote{The effect of |\bnumdefpostfix| is global if under |\xintglobaldefstrue| setting.}
%
The syntax is
\centeredline{|\bnumdefpostfix|\marg{operator}\marg{\string\macro}\marg{L-prec}}

\begin{description}
\item[\marg{operator}] The characters for the operator name: same
  conditions as for |\bnumdefinfix|. Postfix and infix operators share the
  same name-space, regarding abbreviated names.
\begin{quote}
  \textbf{Warning:} \meta{operator} will be expanded in an \cs{edef} and then
  further processed.  If it is (or contains) a Babel-active character chances
  are that the further processing by |\bnumdefpostfix| will give breakage.
  There will be no problem if |\bnumdefpostfix| is used in the document
  preamble.
\end{quote}

\item[\marg{\string\macro}] The one argument expandable macro to assign to
  the postfix operator. This macro only needs to be \emph{x}-expandable.
\item[\marg{L-prec}] An integer, minimal |4|, maximal |22|, which governs the
  left-\hskip0pt precedence of the infix operator.
\end{description}

Examples below which use the maximal precedence are typical of what is
expected of a ``function'' (and I even used |.len()| notation with parentheses
in one example, the parentheses are part of the postfix operator name).  And
indeed such postfix operators are thus a way to implement functions in
disguise, circumventing the fact that the \bnumname parser will never be
extended to work with functional syntax (for this, see \xintexprname).  With
the convention (followed in some examples) that such postfix operators start
with a full stop, but never contain another one, we can chain simply by using
concatenation (no need for parentheses), as there will be no
ambiguity.
\begin{verbatim}
\usepackage{xint}% for \xintiiSum, \xintiiSqrt
\def\myRev#1{\xintNum{\xintReverseOrder{#1}}}% reverse and trim leading zeros
\bnumdefpostfix{$}{\myRev}{22}%     the $ will have top precedence
\bnumdefpostfix{:}{\myRev}{4}%      the : will have lowest precedence
\bnumdefpostfix{::}{\xintiiSqr}{4}% the :: is a completely different operator
\bnumdefpostfix{.len()}{\xintLength}{22}% () for fun but a single . will be enough!
\bnumdefpostfix{.sumdigits}{\xintiiSum}{22}% .s will abbreviate
\bnumdefpostfix{.sqrt}{\xintiiSqrt}{22}%     .sq will be unambiguous (but confusing)
\bnumdefpostfix{.rep}{\xintReplicate3}{22}%  .r will be unambiguous
\end{verbatim}
\def\myRev#1{\xintNum{\xintReverseOrder{#1}}}% reverse digits and trim leading zeros
\bnumdefpostfix{$}{\myRev}{22}% $
\bnumdefpostfix{:}{\myRev}{4}%
\bnumdefpostfix{::}{\xintiiSqr}{4}%
\bnumdefpostfix{.len()}{\xintLength}{22}%
\bnumdefpostfix{.sumdigits}{\xintiiSum}{22}%
\bnumdefpostfix{.sqrt}{\xintiiSqrt}{22}%
\bnumdefpostfix{.rep}{\xintReplicate3}{22}%
\bneshoweval{(2^31).len(), (2^31)., 2^31$, 2^31:, (2^31)$}
\bneshoweval{(2^31).sqrt, 100000000.sq.sq}
\bneshoweval{(2^31).sumdigits, 123456789.s, 123456789.s.s, 123456789.s.s.s}
\bneshoweval{10^10+10000+2000+300+40+5:}
\bneshoweval{1+2+3+4+5+6+7+8+9+10 :: +1 :: *2 :: :: :}
\bneshoweval{123456789.r}\par\vspace*{-\baselineskip}% Toujours LaTeX avec ses
                                % espaces verticaux TROP GRANDS DEPUIS 30 ANS
\begin{verbatim}
\bnumdefpostfix{.rep}{\xintReplicate5}{22}% .rep modified --> .r too
\end{verbatim}
\bnumdefpostfix{.rep}{\xintReplicate5}{22}%
\bneshoweval{123456789.r}


\normalsize

\section{Changes}

\begin{description}
\input bnumexprchanges.tex
\end{description}

\section{License}

\begingroup\makeatletter
\baselineskip10pt
\begin{verbatim}
Copyright © 2014-2022, 2025 Jean-François Burnol

| This Work may be distributed and/or modified under the
| conditions of the LaTeX Project Public License 1.3c.
| This version of this license is in

>   <http://www.latex-project.org/lppl/lppl-1-3c.txt>

| and version 1.3 or later is part of all distributions of
| LaTeX version 2005/12/01 or later.

This Work has the LPPL maintenance status "author-maintained".

The Author and Maintainer of this Work is Jean-François Burnol.

This Work consists of the main source file and its derived files

    bnumexpr.dtx, bnumexpr.sty, bnumexpr.pdf, bnumexpr.tex,
    bnumexprchanges.tex, README.md
\end{verbatim}
\endgroup

\normalsize
\StopEventually{\end{document}\endinput}

% workaround
% https://github.com/latex3/latex2e/issues/563
\makeatletter
\AddToHook{env/macrocode/begin}{\partopsep0pt\relax}
\AddToHook{env/macrocode/after}{\@nobreakfalse}
\makeatother
\newgeometry{hscale=0.75,vscale=0.75}% ATTENTION \newgeometry fait
                                % un reset de vscale si on ne le
                                % précise pas ici !!!

\MakePercentIgnore
\AddToHook{env/macrocode/begin}{\bfseries}
%
% \catcode`\<=0 \catcode`\>=11 \catcode`\*=11 \catcode`\/=11
% \let</dtx>\relax
% \def<*package>{\catcode`\<=12 \catcode`\>=12 \catcode`\*=12 \catcode`\/=12 }
%</dtx>
%<*package>
% \etocdepthtag.toc{code}
% \section{Commented source code}
% \label{sec:bnumexprcode}
% \etocdefaultlines
% \etocignoredepthtags
% \etocsetnexttocdepth{subsection}
% \localtableofcontents
% \etocmarkbothnouc{\bnumnameuserdoc \hyperref[sec:bnumexprcode]{implementation}}
%
% At |1.7|, support for lowercase hexadecimal was added.
%
% At |1.6|, |\bnumeval| requires the |1.4n| release of \xintcorename and
% \xintbinhexname (or at least of \xintkernelname if option |custom| is used).
% It adds |0b|, |0o|, |'|, and |0x| to the syntax, and
% admits optional parameters |[b]|, |[o]|, and |[h]| to produce the
% output converted to binary, octal, or hexadecimal.
%
% It is amusing that implementing the support for the optional argument
% had the un-anticipated corollary that Babel active characters (such as
% |!| with French) are auto-taming.  See the code comments.
%
% A problem with |_| if upfront in numbers was fixed.
%
% There was some refactoring, relative to extending |\bnumsetup| with new keys
% related to base conversion macros and this lead to the removal of
% \cs{bnumprintonetohex} and \cs{bnumhextodec}.
%
% At |1.5|, right-associativity was enforced for powers in anticipation of
% upstream \xintexprname |1.4g 2021/05/25|, and the customizability and
% extendibility of the package is made total via added |\bnumdefinfix| and
% |\bnumdefpostfix|.
%
%
% \footnotesize
%
% Older comments at time of |1.4| and |1.4a| releases:
%
% I transferred mid-May 2021 from \xintexprname its \cs{expanded} based
% infra-structure from its own |1.4| release of January 2020 and bumped
% version to |1.4|. Also I added support for hexadecimal input and output, via
% \xintbinhexname.
%
% A few comments added here at |1.4a|:
% \begin{itemize}
% \item It looked a bit costly and probably would have been mostly useless to
%   end users to integrate in \bnumname support for nested structures via
%   square brackets [, ], which is in \xintexprname since its January 2020
%   |1.4| release.  But some of the related architecture remains here; we
%   could make some gains probably but diverging from upstream code
%   would make maintenance a nightmare.
% \item Formerly, the |\csname...\endcsname| encapsulation technique had the
%   after-effect to allow the macros supporting the infix operators to be only
%   \emph{x}-expandable.  At |1.4|, I could have still allowed support macros
%   being only \emph{x}-expandable, but, keeping in sync with upstream, I have
%   used only a \cs{romannumeral} trigger and did not insert an \cs{expanded}, so
%   now the support macros must be \emph{f}-expandable.  The |1.4a| release
%   fixes the related user documentation of |\bnumsetup| which was not
%   updated at |1.4|.  The support macro for the factorial however needs only
%   be \emph{x}-expandable.
% \item Also, I simply do not understand why the legacy (|1.2e|) user documentation
%   said that the support macros were supposed to \emph{f}-expand their arguments, as
%   they are used only with arguments being explicit digit tokens (and
%   optional minus sign).
% \item The |\bnumexpr\relax| syntax creating an empty ople is by itself now
%   legal, and can be injected (comma separated) in an expression, keeping it
%   invariant, however |\bnumeval{}| ends in a
%   |Paragraph ended before \BNE_print_c was complete|
%   error because |\BNEprint| makes the tacit requirement
%   that the 1D ople to output has at least one item.
% \end{itemize}
%
% \normalsize
% \subsection{Package identification}
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}%
\ProvidesPackage{bnumexpr}[2025/09/27 v1.7b Expressions with big integers (JFB)]%
%    \end{macrocode}
% \subsection{Load \xintkernelname}
% At |1.6|, in order to make the base conversion macros also customizable,
% hence not mandate loading of \xintbinhexname, we only load unconditionally
% \xintkernelname.
%
% We then switch to the familiar catcode regime of the \xintexprname sources.
%    \begin{macrocode}
\RequirePackage{xintkernel}[2025/09/05]%
%    \end{macrocode}
% \subsection{Save catcode regime and switch to our own}
%    \begin{macrocode}
\edef\BNErestorecatcodesendinput{\XINTrestorecatcodes\noexpand\endinput}%
\XINTsetcatcodes%
%    \end{macrocode}
% \subsection{Load optionally \xintcorename and \xintbinhexname}
% |1.6| adds |customcore| as alias of legacy |custom|.  It adds |custombinhex|
% to add possibility of not loading \xintbinhexname either.
% Option |custom| now means both of |customcore| and |custombinhex|.
%
% But who on Earth isn't going to use with delight both my \xintcorename and
% \xintbinhexname?
%    \begin{macrocode}
\def\BNE_tmpa{1}\def\BNE_tmpb{1}%
\DeclareOption{custom}{\def\BNE_tmpa{0}\def\BNE_tmpb{0}}%
\DeclareOption{customcore}{\def\BNE_tmpa{0}}%
\DeclareOption{custombinhex}{\def\BNE_tmpb{0}}%
\ProcessOptions\relax
\if1\BNE_tmpa\RequirePackage{xintcore}[2025/09/05]\fi
\if1\BNE_tmpb\RequirePackage{xintbinhex}[2025/09/05]\fi
%    \end{macrocode}
% \subsection{\cs{bnumsetup}}
% |\bnumsetup| is the new name at |1.5| of \cs{bnumexprsetup}.
% The old name was kept as an alias at |1.5|, and deleted at |1.6|.
%
% Note that a final comma will cause no harm.
%    \begin{macrocode}
\catcode`! 3
\def\bnumsetup #1{\BNE_parsekeys #1,=!,}%
\def\BNE_parsekeys #1=#2#3,%
  {%
    \ifx!#2\expandafter\BNE_parsedone\fi
  \XINT_global
    \expandafter
    \let\csname BNE_Op_\xint_zapspaces #1 \xint_gobble_i\endcsname%
    =#2%
  \ifxintverbose
    \PackageInfo{bnumexpr}{assigned
    \ifxintglobaldefs globally \fi
     \string#2 to \xint_zapspaces #1 \xint_gobble_i\MessageBreak
%    \end{macrocode}
% Workaround for the space inserted by \cs{on@line}.
%    \begin{macrocode}
     \expandafter\xint_firstofone}%
  \fi
  \BNE_parsekeys
  }%
\def\BNE_parsedone #1\BNE_parsekeys {}%
\catcode`! 12
%    \end{macrocode}
% Final comma and spaces are only to check if it does work.  But I will
% NOT insert spaces before commas, even though they are allowed!
%
% |1.6| also handles base conversion macros here.  Prior to |1.6| this
% |\bnumsetup| configuration was not executed if package received option
% |custom| (now |customcore|). But as the user is then responsible for
% redefining all keys, why bother.
%    \begin{macrocode}
\bnumsetup{%
  add = \xintiiAdd, sub = \xintiiSub, opp = \xintiiOpp,
  mul = \xintiiMul, pow = \xintiiPow, fac = \xintiiFac,
  div = \xintiiDivFloor, mod = \xintiiMod, divround = \xintiiDivRound,
  hextodec=\xintHexToDec, octtodec=\xintOctToDec, bintodec=\xintBinToDec,
  dectohex=\xintDecToHex, dectooct=\xintDecToOct, dectobin=\xintDecToBin,
}%
%    \end{macrocode}
% By the way the keys should have been |Add|, |Sub|, \dots, not |add|, |sub|,
% \dots, so internally |\BNE_Op_Add| etc\dots\ would have been the macros
% defined by |\bnumsetup| and used in the code, not |\BNE_Op_add| (et al.)
% whose casing does not match my naming conventions.
%
% \subsection{Some extra constants needed for user defined precedences}
%
% For the mechanism of |\bnumdefinfix| we need precedence levels to be
% available as \cs{chardef}'s. \xintkernelname already provides 0-10, 12, 14,
% 16, 17, 18, 20, 22.
%
% Left levels need to be represented by one token; right levels are
% hard-coded into |checkp_<op>| macros and could have been there explicit
% digit tokens but we will use the |\xint_c_...| \cs{char}-tokens.
%    \begin{macrocode}
\chardef\xint_c_xi   11
\chardef\xint_c_xiii 13
\chardef\xint_c_xv   15
\chardef\xint_c_xix  19
\chardef\xint_c_xxi  21
%    \end{macrocode}
% \subsection{\cs{bnumexpr}, \cs{bnethe}, \cs{bnumeval}}
%
% |\XINTfstop| has to be the same as defined in \xintexprname, in order for a
% subexpression |\xintiiexpr...\relax| to get recognized in |\bnumeval| or
% conversely for |\bnumexpr...\relax| to possibly serve inside an
% |\xinteval|. But why use \bnumname then? Besides a sub \xintexprname-ession
% will break |\bnumeval| if it is anything else than a 1D flat sequence. And
% even then it can work only if internal storage format are kept in sync.
%
% |1.6| deprecates \cs{evaltohex} (and |1.7a| removed it) and has
% |\bnumeval||[h]| as equivalent replacement.
%
% The \cs{protected} |\BNEprint| will survive to |\bnumexpr| being expanded in
% a \cs{write} or \cs{edef}.  But its expansion will be forced by the
% \cs{expanded} from |\bnethe|.
%
% I now really dislike |\thebnumexpr| macro name and at some point had
% replaced it with |\bnumtheexpr| but this got reverted.
%
% |1.6a| uses the strange |\csname| in place of directly |\BNE_wrap|
% in order to fix the |1.6| blunder which had done a similar thing,
% but too late.  This is to tame Babel active characters.  The |\bnumeval|
% was OK, though.  Sadly the blunder was first done in \xintexprname,
% after I had reverted perfectly valid implementation there, having
% thought I could apply a shortcut, which was simply a brain fault.
% And I backported it here... alas...
%    \begin{macrocode}
\def\XINTfstop {\noexpand\XINTfstop}%
\def\bnumexpr  {\romannumeral0\bnumexpro}%
\def\bnumexpro {\csname BNE_wrap\expandafter\endcsname
                \romannumeral0\BNE_bareeval}%
%    \end{macrocode}
% While preparing |1.6| I wondered why the ``|.|'' after |\BNEprint| in
% |\BNE_wrap| which is then gobbled by |\BNEprint|.  It was clear it came from
% \xintexprname, but why was it kept here? 
%
% The reason is to support having a sub |\bnumexpr...\relax| inside
% |\bnumeval| or |\xinteval|.  Indeed such a sub-expression is identified via
% the presence of the |\XINTfstop| after its expansion, and the code inside
% \bnumname handling this is inherited from \xintexprname, so it expects the
% structure |\XINTfstop| then a ``print'' macro, then possibly some stuff
% delimited by a full stop (this is related to the implementation of the
% optional arguments of |\xintfloateval| and |\xintieval|).
%
% As we keep this stuff handled the same way we must inject the seemingly
% silly full stop here for |\bnumexpr...\relax| (or a macro defined from it
% via an \cs{edef}) to be usable inside |\bnumval| or another
% |\bnumexpr...\relax|.
%    \begin{macrocode}
\def\BNE_wrap  {\XINTfstop\BNEprint.}%
%    \end{macrocode}
% It is important to keep in mind that |#1| has the structure
% \{|{...}{...}...{...}|\} with an external brace pair, which here gets
% removed. ^^A attention que mon |...| va retirer une paire d'accolades.
% In the replacement the external \{|...|\} are for \cs{expanded}.
%
% We also define a non \cs{protected} variant without the strange extra full
% stop, it will serve for |\bnumeval| (and |\thebnumexpr|) and thus does not
% need it.
%    \begin{macrocode}
\protected\def\BNEprint.#1{{\BNE_unpack#1.}}%
\def\BNEprint_#1{{\BNE_unpack#1.}}%
%    \end{macrocode}
% |\bnethe| removes the |\XINTfstop| and activates the printing via
% |\BNEprint|.
%
% Attention that prior to |1.6| |\bnethe| grabbed a |#1|, hence would
% work to print a braced |\bnumexpr...\relax|, but I don't see the reason
% for doing that.  Removed.
%
% |1.6a| modifies |\thebnumexpr| here for the Babel active thing.
%    \begin{macrocode}
\def\bnethe{\expanded\expandafter\xint_gobble_i\romannumeral`&&@}%
\def\thebnumexpr{\expanded\csname BNEprint_\expandafter\endcsname
                 \romannumeral0\BNE_bareeval}%
%    \end{macrocode}
% At |1.6| after implementing the |[h]| optional argument of |\bnumeval|,
% there was the un-anticipated result that this tamed Babel active characters.
% This is explained by the expansion happening while a \cs{csname} is not yet
% closed.  And by the fact that during its expansion |\bnumeval| does not
% use delimited macros, for example to fetch up to a closing parenthesis.
%
% There used to be here a |\BNE_start| but it got replaced by its expansion.
%
% The |\BNE_check| is defined in the section \hyperref[ssec:span]{``Expansion
% spanning''}.
%
% Prior to |1.6| |\BNE_bareeval| was named \cs{bnebareeval}, but this was
% outside of the package namespace (it should have been \cs{bnumbareeval}, or
% \cs{bnumexprbareeval}).  Upstream has |\xintbareeval| without underscores
% for legacy reasons.
%    \begin{macrocode}
\def\BNE_bareeval{\expandafter\BNE_check\romannumeral`&&@\BNE_getnext}%
%    \end{macrocode}
% These next are not \cs{protected} because they are only used with
% |\bnumeval|, there is no analog of the private format which |\bnumexpr|
% expands to.  This also spares us having to define macros with names which
% can be written to an external file and re-read using the standard catcodes.
%
% MEMO: |\BNEprint_| (with the trailing underscore) will be used in case of
% absence of optional argument and has been defined already.  It is important
% for compatibility with the others here that it did not use the strange full
% stop in its parameter pattern.  It is also important that it is \emph{not}
% |\protected|, as we want |\bnumeval| to expand fully in an |\edef|.
%    \begin{macrocode}
\expandafter\def\csname BNEprint_[h]\endcsname#1{{\BNE_unpack_tohex#1.}}%
\expandafter\def\csname BNEprint_[ha]\endcsname#1{{\BNE_unpack_tolowhex#1.}}%
\expandafter\def\csname BNEprint_[o]\endcsname#1{{\BNE_unpack_tooct#1.}}%
\expandafter\def\csname BNEprint_[b]\endcsname#1{{\BNE_unpack_tobin#1.}}%
\expandafter\let\csname BNEprint_[]\endcsname\BNEprint_
%    \end{macrocode}
% |[b]|, |[o]| and |[h]| added at |1.6|.
%
% We break here the \xintname legacy pattern to always use |\romannumeral0|
% trigger first, via a CamelCase/lowercase pair of macros.
%
% The |\expanded| will \emph{not} control the actual computation, only launch
% it via propagation across the |\csname...\endcsname| to a |\romannumeral0|
% for the \emph{f}-expansion of |\BNE_bareeval|.  After computation is done
% the |\BNEprint_[...]| will present the braced result (maybe containing
% multiple items) to this |\expanded|, and the unpacking is thus done via an
% \cs{edef}-like expansion.  This does not mean that all helpers doing
% conversion to the binary bases only have to be \emph{x}-expandable, because
% their full expansion must be achieved prior to the ``printone'' macros
% activate.  If the latter are |\protected|, then conversions do only need
% \emph{x}-expandability. Except for the support of the |[ha]| option which
% needs |\BNE_Op_dectohex| to be \emph{f}-expandable as the helper doing
% conversion to lowercase must act on a fully prepared uppercase hexadecimal
% input.
%    \begin{macrocode}
\def\bnumeval #1#{\expanded\bnumeval_a{#1}}%
\def\bnumeval_a#1#2{%
   \csname BNEprint_\xint_zapspaces #1 \xint_gobble_i\expandafter
   \endcsname\romannumeral0\BNE_bareeval#2\relax
}%
%    \end{macrocode}
% This code is more compact at |1.6| than at |1.5|.  Various renamings at
% |1.7| and addition of the |[ha]| optional argument.
%    \begin{macrocode}
\def\BNE_unpack#1{%
   \bnumprintone{#1}\expandafter\BNE_unpack_a\string
}%
\def\BNE_unpack_a#1{%
   \if#1.\BNE_allitemsdone\fi\bnumprintonesep
   \expandafter\BNE_unpack\expandafter{\iffalse}\fi
}%
\def\BNE_allitemsdone\fi#1\fi{\fi}%
%    \end{macrocode}
% There is a breaking change at |1.6| as formerly there was a
% \cs{bnumprintonetohex}.  Now, the decimal to hexadecimal conversion is done
% always, and the customizable wrapper was thus renamed to |\bnumprintonehex|.
%    \begin{macrocode}
\def\BNE_unpack_tohex#1{%
    \expandafter\bnumprintonehex
    \expandafter{\romannumeral`&&@\BNE_Op_dectohex{#1}}%
    \expandafter\BNE_unpack_tohex_a\string
}%
\def\BNE_unpack_tohex_a#1{%
    \if#1.\BNE_allitemsdone\fi\bnumprintonesep
    \expandafter\BNE_unpack_tohex\expandafter{\iffalse}\fi
}%
%    \end{macrocode}
% Conversion to lowercase hexadecimal added at |1.7|.  By the way |[ha]|
% option is broken (but also is not needed) if the |\BNE_Op_dectohex| is
% customized to produce lowercase, not uppercase as |\xintiiDecToHex|.
%
% As we inject ending pattern already here, to skip one ``grab argument''
% step, we do not provide a wrapper which could be configured via
% |\bnumsetup| with something such as |dectolowhex=\macro|.
%    \begin{macrocode}
\def\BNE_unpack_tolowhex#1{%
    \expandafter\bnumprintonelowerhex
    \expanded{{\BNE_dectolowhex_a{#1}\xint_bye23456789\xint_bye\endcsname}}%
    \expandafter\BNE_unpack_tolowhex_a\string
}%
\def\BNE_unpack_tolowhex_a#1{%
    \if#1.\BNE_allitemsdone\fi\bnumprintonesep
    \expandafter\BNE_unpack_tolowhex\expandafter{\iffalse}\fi
}%
%    \end{macrocode}
% So the |dectohex| must be \emph{f}-expandable: even with a
% |\bnumprintonelowerhex| redefined |\protected|, the conversion
% to lowercase needs the |\BNE_Op_dectohex| action to be complete
% before |\BNE_dectolowhex_b| kicks in.
%    \begin{macrocode}
\def\BNE_dectolowhex_a{%
    \expandafter\BNE_dectolowhex_b\romannumeral`&&@\BNE_Op_dectohex
}%
%    \end{macrocode}
% When preparing the |1.7| release I hesitated about testing the token to
% check if a digit or a letter (which could have been done on the basis of the
% catcode only, but would have then put a constraint on the catcodes used by
% the |\BNE_Op_dectohex| macro), and let digits go through ``as is''.  But I
% was not willing to think too much and the advantage of the approach chosen
% is the elegance of its termination.  I did not do comparative efficiency
% tests, which would have cost me at least one or two hours in setting them up
% and comparing with alternative implementation, besides I would have had to
% decide if I base decision on behavior of very long inputs or of the short
% ones.  All of that for something perhaps nobody on Earth will ever use
% anyhow.
%    \begin{macrocode}
\def\BNE_dectolowhex_b #1#2#3#4#5#6#7#8#9{%
    \csname BNE_lower #1\endcsname
    \csname BNE_lower #2\endcsname
    \csname BNE_lower #3\endcsname
    \csname BNE_lower #4\endcsname
    \csname BNE_lower #5\endcsname
    \csname BNE_lower #6\endcsname
    \csname BNE_lower #7\endcsname
    \csname BNE_lower #8\endcsname
    \csname BNE_lower #9\endcsname
    \BNE_dectolowhex_b
}%
\expandafter\let\csname BNE_lower \endcsname\empty
\expandafter\def\csname BNE_lower 0\endcsname{0}%
\expandafter\def\csname BNE_lower 1\endcsname{1}%
\expandafter\def\csname BNE_lower 2\endcsname{2}%
\expandafter\def\csname BNE_lower 3\endcsname{3}%
\expandafter\def\csname BNE_lower 4\endcsname{4}%
\expandafter\def\csname BNE_lower 5\endcsname{5}%
\expandafter\def\csname BNE_lower 6\endcsname{6}%
\expandafter\def\csname BNE_lower 7\endcsname{7}%
\expandafter\def\csname BNE_lower 8\endcsname{8}%
\expandafter\def\csname BNE_lower 9\endcsname{9}%
\expandafter\def\csname BNE_lower A\endcsname{a}%
\expandafter\def\csname BNE_lower B\endcsname{b}%
\expandafter\def\csname BNE_lower C\endcsname{c}%
\expandafter\def\csname BNE_lower D\endcsname{d}%
\expandafter\def\csname BNE_lower E\endcsname{e}%
\expandafter\def\csname BNE_lower F\endcsname{f}%
%    \end{macrocode}
% Octal and binary added at |1.6|.
%    \begin{macrocode}
\def\BNE_unpack_tooct#1{%
    \expandafter\bnumprintoneoct
    \expandafter{\romannumeral`&&@\BNE_Op_dectooct{#1}}%
    \expandafter\BNE_unpack_tooct_a\string
}%
\def\BNE_unpack_tooct_a#1{%
    \if#1.\BNE_allitemsdone\fi\bnumprintonesep
    \expandafter\BNE_unpack_tooct\expandafter{\iffalse}\fi
}%
\def\BNE_unpack_tobin#1{%
    \expandafter\bnumprintonebin
    \expandafter{\romannumeral`&&@\BNE_Op_dectobin{#1}}%
    \expandafter\BNE_unpack_tobin_a\string
}%
\def\BNE_unpack_tobin_a#1{%
    \if#1.\BNE_allitemsdone\fi\bnumprintonesep
    \expandafter\BNE_unpack_tobin\expandafter{\iffalse}\fi
}%
\let\bnumprintone   \xint_firstofone
\let\bnumprintonehex\xint_firstofone
\let\bnumprintonelowerhex\xint_firstofone
\let\bnumprintoneoct\xint_firstofone
\let\bnumprintonebin\xint_firstofone
\def\bnumprintonesep{, }%
%    \end{macrocode}
% \subsection{\cs{BNE_getnext}}
% The upstream |\BNE_put_op_first| has a string of included \cs{expandafter},
% which was imported here at |1.4| and |1.4a| but they serve nothing in our
% context. Removed this useless overhead at |1.5|.
%
% This |\BNE_getnext| token is injected via "start" macros associated to
% operators or similar syntax elements, as will be seen later on.  It tries
% to get next operand.
%    \begin{macrocode}
\def\BNE_getnext #1%
{%
    \expandafter\BNE_put_op_first\romannumeral`&&@%
    \expandafter\BNE_getnext_a\romannumeral`&&@#1%
}%
\def\BNE_put_op_first #1#2#3{#2#3{#1}}%
\def\BNE_getnext_a #1%
{%
    \ifx\relax #1\xint_dothis\BNE_foundprematureend\fi
    \ifx\XINTfstop#1\xint_dothis\BNE_subexpr\fi
    \ifcat\relax#1\xint_dothis\BNE_countetc\fi
    \xint_orthat{}\BNE_getnextfork #1%
}%
%    \end{macrocode}
% This is executed when the parser was trying to find an operand but ended up
% hitting the \cs{relax} end-marker.  This happens if the expression is empty
% for example.  It trickled down here from \xintexprname, but |\xinteval| has
% a notion of empty value.  This is not the case of the \bnumname parser. So
% let's at |1.7a| allow |\bnumeval{}| or |\bnumeval{_}|.  Indeed
% |\bnumeval{"}| and |\bnumeval{"_}| which use an other codebranch worked
% already.  This goes via replacing |{{}}| ^^A attention my verb
% by |{{0}}|.
%    \begin{macrocode}
\def\BNE_foundprematureend\BNE_getnextfork #1{{0}\xint_c_\relax}%
\def\BNE_subexpr #1.#2%
{%
    \expanded{\unexpanded{{#2}}\expandafter}\romannumeral`&&@\BNE_getop
}%
%    \end{macrocode}
% At |1.6| this also filters for \cs{catcode} (as per \xintname |1.4g|
% |2021/05/25|).
%    \begin{macrocode}
\def\BNE_countetc\BNE_getnextfork#1%
{%
    \if0\ifx\count#11\fi
        \ifx\numexpr#11\fi
        \ifx\catcode#11\fi
        \ifx\dimen#11\fi
        \ifx\dimexpr#11\fi
        \ifx\skip#11\fi
        \ifx\glueexpr#11\fi
        \ifx\fontdimen#11\fi
        \ifx\ht#11\fi
        \ifx\dp#11\fi
        \ifx\wd#11\fi
        \ifx\fontcharht#11\fi
        \ifx\fontcharwd#11\fi
        \ifx\fontchardp#11\fi
        \ifx\fontcharic#11\fi
       0\expandafter\BNE_fetch_as_number\fi
   \expandafter\BNE_getnext_a\number #1%
}%
\def\BNE_fetch_as_number
    \expandafter\BNE_getnext_a\number #1%
{%
    \expanded{{{\number#1}}\expandafter}\romannumeral`&&@\BNE_getop
}%
%    \end{macrocode}
% In the case of hitting a |(|, previous release inserted directly a
% |\BNE_oparen|.  But the expansion architecture imported from upstream
% |\xintiiexpr| has been refactored, and the |..._oparen| meaning and
% usage evolved.  We stick with |{}\xint_c_ii^v (| from upstream.
%
% Also, at |1.6|, slight refactoring to handle digit tokens and opening
% parenthesis a bit faster (but this is only first token...); and to ignore an
% underscore as first character (rather than raise an error in this case).
%
% This merges former |\BNE_getnextfork| and |\BNE_scan_number|.
%    \begin{macrocode}
\def\BNE_getnextfork #1{%
    \if#1-\xint_dothis {{}{}-}\fi
    \if#1(\xint_dothis {{}\xint_c_ii^v (}\fi
    \ifnum\xint_c_ix<1\string#1 \xint_dothis {\BNE_startint#1}\fi
    \xint_orthat {\BNE_getnextfork_a #1}%
}%
%    \end{macrocode}
% Prior to |1.7a|, the |_| was handled as is a |+| encountered while looking
% for a value, i.e.\@ was simply ignored and expansion continued with
% |\BNE_get_next_a|.  This made |_^| raise an ``unexpected token'' error when
% hitting the |^|.  But in contrast |"_^| does work without complaining and is
% the same as |0^|.  So at |1.7a| we jump to |\BNE_startint|.  Mind that the
% latter differs from |\BNE_startoct| and |\BNE_starthex| and expects to grab
% an already expanded argument, and naturally we inject a zero.  As a
% corollary of this |1.7a| change, also |_x|, |_o| and |_b| can serve as
% prefixes for hexadecimal, octal and binary.
%    \begin{macrocode}
\def\BNE_getnextfork_a #1{%
    \if#1_\xint_dothis {\BNE_startint 0}\fi
    \if#1+\xint_dothis \BNE_getnext_a \fi
    \if#1'\xint_dothis \BNE_startoct\fi
    \if#1"\xint_dothis \BNE_starthex\fi
    \xint_orthat {\BNE_unexpected #1}%
}%
%    \end{macrocode}
% If user employs |\bnumdefinfix| with |\string#|, and then tries |100##3|,
% the first |#| will be interpreted as operator (assuming no
% operator starting with |##| has actually been defined) and the error
% "message" (which is not using \cs{message} or a \cs{write})
% will then be
% \centeredline{|! xint error: Unexpected token `##'. Ignoring.|}
% because the parser is actually looking for a digit but finds the second |#|,
% and TeX displays it doubled. This is doubly
% confusing, but well, let's not dwell on that.
%
% |\BNE_unexpected| replaced here |\BNE_notadigit| at |1.6|.
%    \begin{macrocode}
\def\BNE_unexpected#1%
{%
    \XINT_expandableerror{Unexpected token `#1'. Ignoring.}\BNE_getnext_a
}%
%    \end{macrocode}
% \subsection{Parsing decimal, hexadecimal, octal, and binary}
%
% Somewhat refactored at |1.6| compared to upstream |1.4m|, on the occasion of
% adding support for prefixes beyond only |"| as earlier.
%
% Ironically, |1.6| fixed the case of an underscore |_| as first character in
% decimal input, but caused a regression for |"| prefixed hexadecimal, which
% ceased to allow a |_| right after the |"|. Fixed at |1.7|.
%
% But |1.7| introduced a regression causing |"0| input to now break... pfff...
% hence |1.7a|.  A secondary (longstanding) issue is that the test suite was
% limited to examples as shown in the docs (which used a special mark-up to
% check automatically for regressions), but this way made inconvenient to test
% for special inputs such as an hexadecimal prefix only followed with zeros
% and underscores, which is what got broken in a too speedy refactoring at
% |1.7|.  The development sources now have a better way of checking against
% regression prior to releases.
%
% \subsubsection{Prefix dispatch}
%    \begin{macrocode}
\def\BNE_startint #1%
{%
    \if #10\expandafter\BNE_scanint_gobz_a\else\expandafter\BNE_scanint_a\fi #1%
}%
\def\BNE_wrapint_after{\iffalse{{{\fi}}}}%
\def\BNE_scanint_gobz_a #1#2{%
     \expandafter\BNE_scanint_gobz_b\romannumeral`&&@#2%
}%
%    \end{macrocode}
% It is important in case of |x|, |o|, or |b| to jump to
% |\BNE_starthex| (et al.) and not for example to |\BNE_scanhex_a|
% because the latter expects an \emph{f}-expansion to have been
% applied already to what comes next (this comment is half-obsolete
% at |1.7| which has no |\BNE_scanhex_a| anymore).
%
% Besides, we do want to trim out
% leading zeroes after the |0b|, |0o|, or |0x| prefix: although the
% macros of \xintbinhexname do accept leading zeros on input, they may
% then produce decimal output with leading zeros, and the ``|ii|''
% macros of \xintcorename consider that an input is vanishing as soon
% as the first digit is |0|.
%    \begin{macrocode}
\def\BNE_scanint_gobz_b #1%
{%
    \ifx b#1\xint_dothis \BNE_startbin \fi
    \ifx o#1\xint_dothis \BNE_startoct \fi
    \ifx x#1\xint_dothis \BNE_starthex \fi
    \xint_orthat {\BNE_scanint_gobz_c #1}%
}%
\def\BNE_scanint_gobz_c #1%
{%
    \expandafter{\expanded{{\iffalse}}}\fi
    \BNE_scanint_gobz_main#1%
}%
%    \end{macrocode}
% \subsubsection{Decimal}
% No |\BNE_wrapint_before|, it has been inlined here at |1.7| (and in
% |\BNE_scanint_gobz_c|).
%    \begin{macrocode}
\def\BNE_scanint_a #1#2{%
     \expandafter{\expanded{{\iffalse}}}\fi #1%
     \expandafter\BNE_scanint_main\romannumeral`&&@#2%
}%
\def\BNE_scanint_main #1%
{%
    \ifcat \relax #1\expandafter\BNE_scanint_hit_cs \fi
    \ifnum\xint_c_ix<1\string#1 \else\expandafter\BNE_scanint_checkagain\fi
    #1\BNE_scanint_again
}%
\def\BNE_scanint_again #1%
{%
    \expandafter\BNE_scanint_main\romannumeral`&&@#1%
}%
%    \end{macrocode}
% Upstream (at |1.4f|) has |_getop| here, but let's jump directly to |BNE_getop_a|.
%    \begin{macrocode}
\def\BNE_scanint_hit_cs \ifnum#1\fi#2\BNE_scanint_again
{%
    \expandafter\BNE_wrapint_after\romannumeral`&&@\BNE_getop_a#2%
}%
\def\BNE_scanint_checkagain #1\BNE_scanint_again
{%
    \if _#1\BNE_scanint_checkagain_skip\fi
    \expandafter\BNE_wrapint_after\romannumeral`&&@\BNE_getop_a#1%
}%
%    \end{macrocode}
% |#1| is \cs{fi}.
%    \begin{macrocode}
\def\BNE_scanint_checkagain_skip#1#2\BNE_getop_a#3{#1\BNE_scanint_again}%
\def\BNE_scanint_gobz_main #1%
{%
    \ifcat \relax #1\expandafter\BNE_scanint_gobz_hit_cs\fi
    \ifnum\xint_c_x<1\string#1 \else\expandafter\BNE_scanint_gobz_checkagain\fi
    #1\BNE_scanint_again
}%
%    \end{macrocode}
% Upstream (at |1.4f|) has |_getop| here, but let's jump directly to
% |BNE_getop_a|.  The |#2| has been grabbed already and f-expanded.
% But this means one less brace-stripping.
%    \begin{macrocode}
\def\BNE_scanint_gobz_hit_cs\ifnum#1\fi#2\BNE_scanint_again
{%
    0\expandafter\BNE_wrapint_after\romannumeral`&&@\BNE_getop_a#2%
}%
%    \end{macrocode}
% Fix at |1.6| for when an underscore is used as first character followed by
% digits.  No need to worry about being very efficient here.
%    \begin{macrocode}
\def\BNE_scanint_gobz_checkagain #1\BNE_scanint_again
{%
    \if    _#1\xint_dothis\BNE_scanint_gobz_again\fi
    \if    0#1\xint_dothis\BNE_scanint_gobz_again\fi
    \xint_orthat
    {0\expandafter\BNE_wrapint_after\romannumeral`&&@\BNE_getop_a#1}%
}%
\def\BNE_scanint_gobz_again #1%
{%
    \expandafter\BNE_scanint_gobz_main\romannumeral`&&@#1%
}%
%    \end{macrocode}
% \subsubsection{Hexadecimal}
%
% At |1.6| the code here is refactored to follow closely the |scanint| one,
% rather than downscaling upstream \xintexprname which also has to handle
% fractional input.  This avoids gathering the hexadecimal digits then
% grabbing them again as a whole via a delimited macro.
%
% Removing leading zeros and leading underscores
% is the job of |\BNE_scanhex_gobz_main|.
%
% Things could be done better if |\BNE_Op_hextodec| was only required to be
% \emph{x}-expandable.  Already now we could inline the |\BNE_wraphex_before|
% sparing its use of |\iffalse..\fi| to move closing braces to the one after
% |\expanded|.  But we would still have three |\expandafter|'s in total.
%    \begin{macrocode}
\def\BNE_starthex #1%
{%
    \expandafter\BNE_wraphex_before
    \expanded{{\iffalse}}\fi
    \expandafter\BNE_scanhex_gobz_main\romannumeral`&&@#1%
}%
\def\BNE_wraphex_before{\expandafter{\expandafter{%
                        \romannumeral`&&@\iffalse}}\fi\BNE_Op_hextodec}%
\def\BNE_wraphex_after{\iffalse{{{{\fi}}}}}%
%    \end{macrocode}
% At |1.6| we apply exact same scheme as for the |scanint| code.  The sole
% difference is the more complicated test for recognizing a digit.
%
% At |1.7| the code evolved to support |a..f| as hexadecimal input.
% As |\bnumeval| supports no functions or user variables, there is
% no breaking change with tacit multiplication as would be the case
% for |\xinteval| if we did the same addition to it.
%
% The expansion is |\expanded|-governed which spares us quite
% some annoyances with exiting from \TeX{} nested conditionals.
%
% Hesitation whether to check for lowercase hexadecimal
% before or after checking for the underscore.
%    \begin{macrocode}
\def\BNE_scanhex_main #1%
{%
    \ifcat \relax #1\BNE_scanhex_done_hit_cs #1\fi
    \if\ifnum`#1>`/
       \ifnum`#1>`9
       \ifnum`#1>`@
       \ifnum`#1>`F
       0\else1\fi\else0\fi\else1\fi\else0\fi 1%
    #1%
    \else
      \if _#1\else
        \if\ifnum`#1>``
           \ifnum`#1>`f 0\else1\fi\else0\fi 1%
           \csname BNE_upper #1\endcsname
        \else
           \BNE_scanhex_done #1%
        \fi
      \fi
    \fi
    \BNE_scanhex_again
}%
\expandafter\def\csname BNE_upper a\endcsname{A}%
\expandafter\def\csname BNE_upper b\endcsname{B}%
\expandafter\def\csname BNE_upper c\endcsname{C}%
\expandafter\def\csname BNE_upper d\endcsname{D}%
\expandafter\def\csname BNE_upper e\endcsname{E}%
\expandafter\def\csname BNE_upper f\endcsname{F}%
%    \end{macrocode}
% |#2| is \cs{fi}.
%    \begin{macrocode}
\def\BNE_scanhex_done_hit_cs #1#2#3\BNE_scanhex_again
{%
    #2\expandafter\BNE_wraphex_after\romannumeral`&&@\BNE_getop_a#1%
}%
%    \end{macrocode}
% |#2| is \cs{fi}\cs{fi}\cs{fi} or \cs{fi}\cs{fi}\cs{fi}\cs{fi} (if called
% from the |gobz| variant).
%    \begin{macrocode}
\def\BNE_scanhex_done #1#2\BNE_scanhex_again
{%
    #2\expandafter\BNE_wraphex_after\romannumeral`&&@\BNE_getop_a#1%
}%
\def\BNE_scanhex_again #1%
{%
    \expandafter\BNE_scanhex_main\romannumeral`&&@#1%
}%
%    \end{macrocode}
% Initial scanning which skips leading zeros and underscores.  Priority in
% efficiency is when there are none. Slight refactoring to check for lowercase
% hexadecimal earlier at |1.7a|.  But the |\BNE_scanhex_main| will then again
% check for underscores prior to them.
%
% Also |1.7a| fixes the unfortunate |1.7| regression when only zeros (or
% underscores) are found after the hexadecimal (or other) prefixes.
%    \begin{macrocode}
\def\BNE_scanhex_gobz_main #1%
{%
    \ifcat \relax #1%
        0\BNE_scanhex_done_hit_cs #1\fi
    \if\ifnum`#1>`0
       \ifnum`#1>`9
       \ifnum`#1>`@
       \ifnum`#1>`F
       0\else1\fi\else0\fi\else1\fi\else0\fi 1%
    #1%
    \else
      \if\ifnum`#1>`` \ifnum`#1>`f 0\else1\fi\else0\fi 1%
        \csname BNE_upper #1\endcsname
      \else
        \if 0#1\BNE_scanhex_gobzero\else
        \if _#1\BNE_scanhex_gobunderscore\else
           0\BNE_scanhex_done #1%
        \fi\fi
      \fi
    \fi
    \BNE_scanhex_again
}%
\def\BNE_scanhex_gobzero #1\BNE_scanhex_again #2%
{%
    \fi\fi\fi
    \expandafter\BNE_scanhex_gobz_main\romannumeral`&&@#2%
}%
\def\BNE_scanhex_gobunderscore #1\BNE_scanhex_again #2%
{%
    \fi\fi\fi\fi
    \expandafter\BNE_scanhex_gobz_main\romannumeral`&&@#2%
}%
%    \end{macrocode}
% \subsubsection{Octal}
%
% Added at |1.6|.  Leading
% zeros are removed.
%
% At |1.7| the code for hexadecimal was a bit refactored and
% the one here was changed to follow same pattern.
%    \begin{macrocode}
\def\BNE_startoct #1%
{%
    \expandafter\BNE_wrapoct_before
    \expanded{{\iffalse}}\fi
    \expandafter\BNE_scanoct_gobz_main\romannumeral`&&@#1%
}%
\def\BNE_wrapoct_before{\expandafter{\expandafter{%
                        \romannumeral`&&@\iffalse}}\fi\BNE_Op_octtodec}%
\def\BNE_wrapoct_after{\iffalse{{{{\fi}}}}}%
\def\BNE_scanoct_main #1%
{%
    \ifcat \relax #1\expandafter\BNE_scanoct_done_hit_cs #1\fi
    \if\ifnum`#1>`/ \ifnum`#1>`7 0\else1\fi\else0\fi 1%
    #1%
    \else
      \if _#1\else
        \BNE_scanoct_done #1%
      \fi
    \fi
    \BNE_scanoct_again
}%
%    \end{macrocode}
% |#2| is \cs{fi}.
%    \begin{macrocode}
\def\BNE_scanoct_done_hit_cs #1#2#3\BNE_scanoct_again
{%
    #2\expandafter\BNE_wrapoct_after\romannumeral`&&@\BNE_getop_a#1%
}%
%    \end{macrocode}
% |#2| is \cs{fi}\cs{fi} or \cs{fi}\cs{fi}\cs{fi} (if called
% from the |gobz| variant).
%    \begin{macrocode}
\def\BNE_scanoct_done #1#2\BNE_scanoct_again
{%
    #2\expandafter\BNE_wrapoct_after\romannumeral`&&@\BNE_getop_a#1%
}%
\def\BNE_scanoct_again #1%
{%
    \expandafter\BNE_scanoct_main\romannumeral`&&@#1%
}%
\def\BNE_scanoct_gobz_main #1%
{%
    \ifcat \relax #1%
      0\BNE_scanoct_done_hit_cs #1\fi
    \if\ifnum`#1>`0 \ifnum`#1>`7 0\else1\fi\else0\fi 1%
    #1%
    \else
      \if 0#1\BNE_scanoct_gobzero\else
      \if _#1\BNE_scanoct_gobunderscore\else
        0\BNE_scanoct_done #1%
      \fi\fi
    \fi
    \BNE_scanoct_again
}%
\def\BNE_scanoct_gobzero #1\BNE_scanoct_again #2%
{%
    \fi\fi
    \expandafter\BNE_scanoct_gobz_main\romannumeral`&&@#2%
}%
\def\BNE_scanoct_gobunderscore #1\BNE_scanoct_again #2%
{%
    \fi\fi\fi
    \expandafter\BNE_scanoct_gobz_main\romannumeral`&&@#2%
}%
%    \end{macrocode}
% \subsubsection{Binary}
%
% Added at |1.6|.  Same code skeleton as for octal and hexadecimal.
%    \begin{macrocode}
\def\BNE_startbin #1%
{%
    \expandafter\BNE_wrapbin_before
    \expanded{{\iffalse}}\fi
    \expandafter\BNE_scanbin_gobz_main\romannumeral`&&@#1%
}%
\def\BNE_wrapbin_before{\expandafter{\expandafter{%
                        \romannumeral`&&@\iffalse}}\fi\BNE_Op_bintodec}%
\def\BNE_wrapbin_after{\iffalse{{{{\fi}}}}}%
\def\BNE_scanbin_main #1%
{%
    \ifcat \relax #1\expandafter\BNE_scanbin_done_hit_cs #1\fi
    \if \if0#11\else\if1#11\else0\fi\fi 1%
    #1%
    \else
      \if _#1\else
        \BNE_scanbin_done #1%
      \fi
    \fi
    \BNE_scanbin_again
}%
%    \end{macrocode}
% |#2| is \cs{fi}.
%    \begin{macrocode}
\def\BNE_scanbin_done_hit_cs #1#2#3\BNE_scanbin_again
{%
    #2\expandafter\BNE_wrapbin_after\romannumeral`&&@\BNE_getop_a#1%
}%
%    \end{macrocode}
% |#2| is \cs{fi}\cs{fi} or \cs{fi}\cs{fi}\cs{fi} (if called
% from the |gobz| variant).
%    \begin{macrocode}
\def\BNE_scanbin_done #1#2\BNE_scanbin_again
{%
    #2\expandafter\BNE_wrapbin_after\romannumeral`&&@\BNE_getop_a#1%
}%
\def\BNE_scanbin_again #1%
{%
    \expandafter\BNE_scanbin_main\romannumeral`&&@#1%
}%
\def\BNE_scanbin_gobz_main #1%
{%
    \ifcat \relax #1%
      0\BNE_scanbin_done_hit_cs #1\fi
    \if \if0#11\else\if1#11\else0\fi\fi 1%
    #1%
    \else
      \if 0#1\BNE_scanbin_gobzero\else
      \if _#1\BNE_scanbin_gobunderscore\else
        0\BNE_scanbin_done #1%
      \fi\fi
    \fi
    \BNE_scanbin_again
}%
\def\BNE_scanbin_gobzero #1\BNE_scanbin_again #2%
{%
    \fi\fi
    \expandafter\BNE_scanbin_gobz_main\romannumeral`&&@#2%
}%
\def\BNE_scanbin_gobunderscore #1\BNE_scanbin_again #2%
{%
    \fi\fi\fi
    \expandafter\BNE_scanbin_gobz_main\romannumeral`&&@#2%
}%
%    \end{macrocode}
% \subsection{\cs{BNE_getop}}
% The upstream analog to |\BNE_getop_a| applies \cs{string} to |#1| in its
% thirdofthree branch before handing over to analog of |\BNE_scanop_a|, but I
% see no reason for doing it here (and I do have to check if upstream has
% any valid reason to do it). Removed. First branch was a |\BNE_foundend|,
% used only here, and expanding to |\xint_c_\relax|, let's move the
% |#1| (which will be \cs{relax}) last and simply insert |\xint_c_|.
%
% The |_scanop| macros have been refactored at upstream and here |1.5|.
%    \begin{macrocode}
\def\BNE_getop #1%
{%
    \expandafter\BNE_getop_a\romannumeral`&&@#1%
}%
\catcode`* 11
\def\BNE_getop_a #1%
{%
    \ifx   \relax #1\xint_dothis\xint_firstofthree\fi
    \ifcat \relax #1\xint_dothis\xint_secondofthree\fi
    \ifnum\xint_c_ix<1\string#1 \xint_dothis\xint_secondofthree\fi
    \if    (#1\xint_dothis      \xint_secondofthree\fi %)
    \xint_orthat \xint_thirdofthree
    \xint_c_
    {\BNE_prec_tacit *}%
    \BNE_scanop_a
    #1%
}%
\catcode`* 12
\def\BNE_scanop_a #1#2%
{%
    \expandafter\BNE_scanop_b\expandafter#1\romannumeral`&&@#2%
}%
\def\BNE_scanop_b #1#2%
{%
    \unless\ifcat#2\relax
           \ifcsname BNE_itself_#1#2\endcsname
           \BNE_scanop_c
    \fi\fi
    \BNE_foundop_a #1#2%
}%
\def\BNE_scanop_c #1#2#3#4#5% #1#2=\fi\fi
{%
    #1#2%
    \expandafter\BNE_scanop_d\csname BNE_itself_#4#5\expandafter\endcsname
    \romannumeral`&&@%
}%
\def\BNE_scanop_d #1#2%
{%
    \unless\ifcat#2\relax
           \ifcsname BNE_itself_#1#2\endcsname
           \BNE_scanop_c
    \fi\fi
    \BNE_foundop #1#2%
}%
%    \end{macrocode}
% If a postfix say |?s| is defined and |?r| is encountered the |?| will have
% been interpreted as a shortcut to |?s| and then the |r| will be found with
% the parser (after having executed the already found postfix) now looking for
% another operator so the error message will be |Operator? (got `r')| which is
% doubly confusing... well, let's not dwell on that.
%
% Update 2021/05/22, I have changed the message, as part of a systematic
% removal of |I<something>| invites, in part because |xint 1.4g| changed its
% expandable error method and now has a nice message saying \xintname will try
% to recover by itself. And now I have about 55 characters available for the
% message.
%    \begin{macrocode}
\def\BNE_foundop_a #1%
{%
    \ifcsname BNE_precedence_#1\endcsname
        \csname BNE_precedence_#1\expandafter\endcsname
        \expandafter #1%
    \else
        \expandafter\BNE_getop_a\romannumeral`&&@%
        \xint_afterfi{\XINT_expandableerror
        {Expected an operator but got `#1'. Ignoring.}}%
     \fi
}%
\def\BNE_foundop #1{\csname BNE_precedence_#1\endcsname #1}%
%    \end{macrocode}
% \subsection{Expansion spanning; opening and closing parentheses}
% \label{ssec:span}
% There was refactoring of expandable error messages at \xintname |1.4g| and I
% can now use up to 55 characters, but should not really invite user to
% |I|nsert something as it does not fit well with generic message saying
% \xintname will go ahead "hoping repair was complete".
%
% At |1.6|, we removed the |\BNE_start|, current |\BNE_bareeval| has its
% meaning rather than expanding to it as formerly.
%
% Also here macros are defined one by one so that it is easier to understand
% what is happening.  Formerly |\BNE_tmpa| defined all of them in one go
% (as is still the case in upstream \xintexprname).
%    \begin{macrocode}
\def\BNE_tmpa#1{%
    \def\BNE_check##1%
    {%
        \xint_UDsignfork
          ##1{\expandafter\BNE_checkp\romannumeral`&&@#1}%
            -{\BNE_checkp##1}%
        \krof
    }%
}\expandafter\BNE_tmpa\csname BNE_op_-xii\endcsname
\def\BNE_tmpa#1{%
    \def\BNE_checkp##1##2%
    {%
        \ifcase ##1%
           \expandafter\BNE_done
        \or\expandafter#1%
        \else
           \expandafter\BNE_checkp
           \romannumeral`&&@\csname BNE_op_##2\expandafter\endcsname
        \fi
    }%
}\expandafter\BNE_tmpa\csname BNE_extra_)\endcsname
\expandafter\def\csname BNE_extra_)\endcsname{%
        \XINT_expandableerror
        {An extra ) was removed. Hit <return>, fingers crossed.}%
        \expandafter\BNE_check\romannumeral`&&@\expandafter\BNE_put_op_first
        \romannumeral`&&@\BNE_getop_legacy
}%
\let\BNE_done\space
\def\BNE_getop_legacy #1%
{%
    \expanded{\unexpanded{{#1}}\expandafter}\romannumeral`&&@\BNE_getop
}%
%    \end{macrocode}
% Code style left untouched at |1.6|.
%    \begin{macrocode}
\catcode`) 11
\def\BNE_tmpa #1#2#3#4#5#6%
{%
    \def #1##1% op_(
    {%
        \expandafter #4\romannumeral`&&@\BNE_getnext
    }%
    \def #2##1% op_)
    {%
        \expanded{\unexpanded{\BNE_put_op_first{##1}}\expandafter}%
        \romannumeral`&&@\BNE_getop
    }%
    \def #3% oparen
    {%
        \expandafter #4\romannumeral`&&@\BNE_getnext
    }%
    \def #4##1% check-
    {%
        \xint_UDsignfork
            ##1{\expandafter#5\romannumeral`&&@#6}%
              -{#5##1}%
        \krof
    }%
    \def #5##1##2% checkp
    {%
        \ifcase ##1\expandafter\BNE_missing_)
        \or \csname BNE_op_##2\expandafter\endcsname
        \else
          \expandafter #5\romannumeral`&&@\csname BNE_op_##2\expandafter\endcsname
        \fi
    }%
}%
\expandafter\BNE_tmpa
    \csname BNE_op_(\expandafter\endcsname
    \csname BNE_op_)\expandafter\endcsname
    \csname BNE_oparen\expandafter\endcsname
    \csname BNE_check-_)\expandafter\endcsname
    \csname BNE_checkp_)\expandafter\endcsname
    \csname BNE_op_-xii\endcsname
\let\BNE_precedence_)\xint_c_i
\def\BNE_missing_)
   {\XINT_expandableerror{Missing ). Hit <return> to proceed.}%
    \xint_c_ \BNE_done }%
\catcode`) 12
%    \end{macrocode}
% \subsection{The comma as binary operator}
% At |1.4|, it is simply a union operator for 1D oples. Inserting directly
% here a |<comma><space>| separator (as in earlier releases) in accumulated
% result would avoid having to do it on output but to the cost of diverging
% from \xintexprname upstream code, and to have to let the ``|unpack|''
% (new name at |1.7|)
% routines handle comma separated values rather than braced values.
%
% Note bene: contiguous commas |,,| will cause the parser to raise an
% ``unexpected token'' error on the second comma, contrarily to |\xinteval|
% which silently ignores it since its |1.4| release (see the first subsection
% about basic terminology in \emph{Oples and nutples: the 1.4 terminology} in
% |xint.pdf|).
%
% It does work to use |\bnumdefinfix{,,}{\macro}{N}{N}|. Then |,,| acts as a
% custom infix operator without breaking the use of a single comma to
% separate inputs.
%    \begin{macrocode}
\def\BNE_tmpa #1#2#3#4#5%
{%
    \def #1##1% \BNE_op_,
    {%
      \expanded{\unexpanded{#2{##1}}\expandafter}%
      \romannumeral`&&@\expandafter#3\romannumeral`&&@\BNE_getnext
    }%
    \def #2##1##2##3##4{##2##3{##1##4}}% \BNE_exec_,
    \def #3##1% \BNE_check-_,
    {%
      \xint_UDsignfork
        ##1{\expandafter#4\romannumeral`&&@#5}%
          -{#4##1}%
      \krof
    }%
    \def #4##1##2% \BNE_checkp_,
    {%
      \ifnum ##1>\xint_c_iii
        \expandafter#4%
           \romannumeral`&&@\csname BNE_op_##2\expandafter\endcsname
      \else
        \expandafter##1\expandafter##2%
      \fi
    }%
}%
\expandafter\BNE_tmpa
    \csname BNE_op_,\expandafter\endcsname
    \csname BNE_exec_,\expandafter\endcsname
    \csname BNE_check-_,\expandafter\endcsname
    \csname BNE_checkp_,\expandafter\endcsname
    \csname BNE_op_-xii\endcsname
\expandafter\let\csname BNE_precedence_,\endcsname\xint_c_iii
%    \end{macrocode}
% \subsection{The minus as prefix operator of variable precedence level}
% This |\BNE_Op_opp| caused trouble at |1.4| as it must be \emph{f}-expandable,
% whereas earlier it expanded inside |\csname...\endcsname| context, so I
% could define it as \centeredline{|\if-#1\else\if0#10\else-#1\fi\fi|} where
% |#1| was the
% first token of unbraced argument but this meant at |1.4| an added
% |\xint_firstofone| here. Well let's return to sanity at |1.4a| and not add
% the |\xint_firstofone| and simply default |\BNE_Op_opp| to |\xintiiOpp|,
% which it should have been all along! And on this occasion let's trim user
% documentation of complications.
%
% The package used to need to define unary minus operator with precedences 12,
% 14, and 18.  It also defined it at level 16 but this was unneedeed actually,
% no operator possibly generating usage of an |op_-xvi|.
%
% At |1.5| the right precedence of powers was lowered to 17, so we now need
% here only 12, 14, and 17.
%
% Due to |\bnumdefinfix| it is needed to support also, perhaps, the other
% levels 13, 15, 16, 18, ....  This will be done only if necessary and is the
% reason why the macros |\BNE_defminus_a| and |\BNE_defminus_b| are given
% permanent names. In fact it is now |\BNE_defbin_b| which will decide to invoke
% or not the |\BNE_defminus_a|, and we activate it here only for the base
% precedence 12.
%
% The |\XINT_global|'s are absent from upstream \xintexprname as it does not
% incorporate yet some analog to |\bnumdefinfix/\bnumdefpostfix|.
%    \begin{macrocode}
\def\BNE_defminus_b #1#2#3#4#5%
{%
    \XINT_global\def #1% \BNE_op_-<level>
    {%
      \expandafter #2\romannumeral`&&@\expandafter#3%
      \romannumeral`&&@\BNE_getnext
    }%
    \XINT_global\def #2##1##2##3% \BNE_exec_-<level>
    {%
      \expandafter ##1\expandafter ##2\expandafter
       {\expandafter{\romannumeral`&&@\BNE_Op_opp##3}}%
    }%
    \XINT_global\def #3##1% \BNE_check-_-<level>
    {%
      \xint_UDsignfork
        ##1{\expandafter #4\romannumeral`&&@#1}%
          -{#4##1}%
      \krof
    }%
    \XINT_global\def #4##1##2% \BNE_checkp_-<level>
    {%
      \ifnum ##1>#5%
        \expandafter #4%
        \romannumeral`&&@\csname BNE_op_##2\expandafter\endcsname
      \else
        \expandafter ##1\expandafter ##2%
      \fi
    }%
}%
\def\BNE_defminus_a #1%
{%
    \expandafter\BNE_defminus_b
    \csname BNE_op_-#1\expandafter\endcsname
    \csname BNE_exec_-#1\expandafter\endcsname
    \csname BNE_check-_-#1\expandafter\endcsname
    \csname BNE_checkp_-#1\expandafter\endcsname
    \csname xint_c_#1\endcsname
}%
\BNE_defminus_a {xii}%
%    \end{macrocode}
% \subsection{The infix operators.}
% I could have at the |1.4| refactoring injected usage of \cs{expanded} here,
% but kept in sync with upstream \xintexprname code. Any \emph{x}-expandable
% macro can easily be converted into an \emph{f}-expandable one using
% \cs{expanded}, so this is no serious limitation.
%
% Macro names are somewhat bad and there is much risk of confusion in future
% maintenance of |\BNE_Op_| prefix (used for |\BNE_Op_add| etc...; besides
% this should have been |\BNE_Op_Add|) and |\BNE_op_| prefix (used for
% |\BNE_op_+| etc...).
%
% At |1.5| decision is made to anticipate the announced upstream change to let
% the power operators be right associative, matching Python behaviour. This
% change is simply implemented by hardcoding in |\BNE_checkp_<op>| the right
% precedence which so far, for such operators, had been identical with the
% left precedence (upstream has examples of direct coding without
% formalization). In fact the right precedence existed already as argument to
% |\BNE_defbin_b| as the precedence to assign to unary minus following |<op>|.
%
% Note1: although it is easy to change the left precedence at user level, the
% right precedence is now more inaccessible. But on the other hand \bnumname
% provides |\bnumdefinfix| so all is customizable at user level.
%
% Note2: Tacit multiplication is not really a separate operator, it is
% the |*| with an elevated left precedence, which costs nothing to create and
% this precedence is stored in chardef token |\BNE_prec_tacit|.
%
% Compared to upstream, we use here numbers as arguments to |\BNE_defbin_b|,
% and convert to roman numerals internally, also the operator macro is passed
% as a control sequence not as its name (and |#6| and |#7| are permuted in
% |\BNE_defbin_c|).
%    \begin{macrocode}
\def\BNE_defbin_c #1#2#3#4#5#6#7%
{%
  \XINT_global\def #1##1% \BNE_op_<op>
  {%
    \expanded{\unexpanded{#2{##1}}\expandafter}%
    \romannumeral`&&@\expandafter#3\romannumeral`&&@\BNE_getnext
  }%
  \XINT_global\def #2##1##2##3##4% \BNE_exec_<op>
  {%
    \expandafter##2\expandafter##3\expandafter
      {\expandafter{\romannumeral`&&@#7##1##4}}%
  }%
  \XINT_global\def #3##1% \BNE_check-_<op>
  {%
    \xint_UDsignfork
      ##1{\expandafter#4\romannumeral`&&@#5}%
        -{#4##1}%
    \krof
  }%
  \XINT_global\def #4##1##2% \BNE_checkp_<op>
  {%
    \ifnum ##1>#6%
      \expandafter#4%
      \romannumeral`&&@\csname BNE_op_##2\expandafter\endcsname
    \else
      \expandafter ##1\expandafter ##2%
    \fi
  }%
}%
\def\BNE_defbin_b #1#2#3#4%
{%
    \expandafter\BNE_defbin_c
    \csname BNE_op_#1\expandafter\endcsname
    \csname BNE_exec_#1\expandafter\endcsname
    \csname BNE_check-_#1\expandafter\endcsname
    \csname BNE_checkp_#1\expandafter\endcsname
    \csname BNE_op_-\romannumeral\ifnum#3>12 #3\else 12\fi
            \expandafter\endcsname
    \csname xint_c_\romannumeral#3\endcsname #4%
  \XINT_global
    \expandafter
    \let\csname BNE_precedence_#1\expandafter\endcsname
        \csname xint_c_\romannumeral#2\endcsname
    \unless
    \ifcsname BNE_exec_-\romannumeral\ifnum#3>12 #3\else 12\fi\endcsname
%    \end{macrocode}
% This will execute only for |#3>12| as |\BNE_exec_-xii| exists.
%    \begin{macrocode}
     \expandafter\BNE_defminus_a\expandafter{\romannumeral#3}%
    \fi
}%
\BNE_defbin_b  +   {12} {12}  \BNE_Op_add
\BNE_defbin_b  -   {12} {12}  \BNE_Op_sub
\BNE_defbin_b  *   {14} {14}  \BNE_Op_mul
\BNE_defbin_b  /   {14} {14}  \BNE_Op_divround
\BNE_defbin_b {//} {14} {14}  \BNE_Op_div
\BNE_defbin_b {/:} {14} {14}  \BNE_Op_mod
\BNE_defbin_b  ^   {18} {17}  \BNE_Op_pow
%    \end{macrocode}
% \xintexprname uses shortcut
% \centeredline{|\expandafter\def\csname XINT_expr_itself_**\endcsname {^}|}
% But doing it would mean that any redefinition of |^| propagates to |**|.
% And it
% creates a special case which would need consideration by
% |\BNE_dotheitselves|, or special restrictions to add to user documentation.
% Better to simply handle |**| as a full operator.
%    \begin{macrocode}
\BNE_defbin_b {**} {18} {17}  \BNE_Op_pow
\expandafter\def\csname BNE_itself_**\endcsname {**}%
\expandafter\def\csname BNE_itself_//\endcsname {//}%
\expandafter\def\csname BNE_itself_/:\endcsname {/:}%
\let\BNE_prec_tacit\xint_c_xvi
%    \end{macrocode}
% \subsection{Extending the syntax: \cs{bnumdefinfix}, \cs{bnumdefpostfix}}
% \subsubsection{\cs{bnumdefinfix}}
% |#1| gives the operator characters, |#2| the associated macro, |#3| its
% left-precedence and |#4| its right precedence (as integers).
%
% The "itself" definitions are done in such a way that unambiguous abbreviations
% work; but in case of ambiguity the first defined operator is used.
%
% However, if for example operator |$a| was defined after |$ab|, then although
% |$| will use |$ab| which was defined first, |$a| will use as expected the
% second defined operator.
%
% The mismatch |\BNE_defminus_a| vs |\BNE_defbin_b| is inherited from
% upstream, I keep it to simplify maintenance.
%    \begin{macrocode}
\def\bnumdefinfix #1#2#3#4%
{%
    \edef\BNE_tmpa{#1}%
    \edef\BNE_tmpa{\xint_zapspaces_o\BNE_tmpa}%
    \edef\BNE_tmpL{\the\numexpr#3\relax}%
    \edef\BNE_tmpL{\ifnum\BNE_tmpL<4 4\else\ifnum\BNE_tmpL<23 \BNE_tmpL\else 22\fi\fi}%
    \edef\BNE_tmpR{\the\numexpr#4\relax}%
    \edef\BNE_tmpR{\ifnum\BNE_tmpR<4 4\else\ifnum\BNE_tmpR<23 \BNE_tmpR\else 22\fi\fi}%
    \BNE_defbin_b \BNE_tmpa\BNE_tmpL\BNE_tmpR #2%
    \expandafter\BNE_dotheitselves\BNE_tmpa\relax
  \ifxintverbose
    \PackageInfo{bnumexpr}{infix operator \BNE_tmpa\space
    \ifxintglobaldefs globally \fi
        does
        \unexpanded{#2}\MessageBreak with precedences \BNE_tmpL, \BNE_tmpR;}%
  \fi
}%
\def\BNE_dotheitselves#1#2%
{%
    \if#2\relax\expandafter\xint_gobble_ii
    \else
  \XINT_global
      \expandafter\edef\csname BNE_itself_#1#2\endcsname{#1#2}%
      \unless\ifcsname BNE_precedence_#1\endcsname
  \XINT_global
        \expandafter\edef\csname BNE_precedence_#1\endcsname
                         {\csname BNE_precedence_\BNE_tmpa\endcsname}%
  \XINT_global
        \expandafter\odef\csname BNE_op_#1\endcsname
                         {\csname BNE_op_\BNE_tmpa\endcsname}%
      \fi
    \fi
    \BNE_dotheitselves{#1#2}%
}%
%    \end{macrocode}
% \subsubsection{\cs{bnumdefpostfix}}
% Support macros for postfix operators only need to be \emph{x}-expandable.
%    \begin{macrocode}
\def\bnumdefpostfix #1#2#3%
{%
    \edef\BNE_tmpa{#1}%
    \edef\BNE_tmpa{\xint_zapspaces_o\BNE_tmpa}%
    \edef\BNE_tmpL{\the\numexpr#3\relax}%
    \edef\BNE_tmpL{\ifnum\BNE_tmpL<4 4\else\ifnum\BNE_tmpL<23 \BNE_tmpL\else 22\fi\fi}%
  \XINT_global
    \expandafter\let\csname BNE_precedence_\BNE_tmpa\expandafter\endcsname
                    \csname xint_c_\romannumeral\BNE_tmpL\endcsname
  \XINT_global
    \expandafter\def\csname BNE_op_\BNE_tmpa\endcsname ##1%
    {%
        \expandafter\BNE_put_op_first
        \expanded{{{#2##1}}\expandafter}\romannumeral`&&@\BNE_getop
    }%
    \expandafter\BNE_dotheitselves\BNE_tmpa\relax
  \ifxintverbose
    \PackageInfo{bnumexpr}{postfix operator \BNE_tmpa\space
    \ifxintglobaldefs globally \fi
        does \unexpanded{#2}\MessageBreak
        with precedence \BNE_tmpL;}%
  \fi
}%
%    \end{macrocode}
% \subsection{! as postfix factorial operator}
%    \begin{macrocode}
\bnumdefpostfix{!}{\BNE_Op_fac}{20}%
%    \end{macrocode}
% \subsection{Cleanup}
%    \begin{macrocode}
\let\BNE_tmpa\relax \let\BNE_tmpb\relax \let\BNE_tmpc\relax
\let\BNE_tmpR\relax \let\BNE_tmpL\relax
\BNErestorecatcodesendinput%
%    \end{macrocode}
% \MakePercentComment
%</package>
%<*dtx>
\CheckSum {1670}%
\makeatletter\check@checksum\makeatother%
\Finale%
%% End of file xint.dtx
