54

TUGboat, Volume 41 (2020), No. 1
The fewerfloatpages package∗Frank Mittelbach
Abstract
LATEX’s float algorithm has the tendency to produce fairly empty float pages, i.e., pages
containing only floats but with a lot of free space remaining that could easily be filledwith nearby text. There are good reasons for this behavior; nevertheless, the results
look unappealing and in many cases documents are unnecessarily enlarged.
The fewerfloatpages package provides an extended algorithm that improves on thisbehavior without the need for manual intervention by the user.
Contents
1

Introduction
1.1 A quick overview of LATEX’s float algorithm . . . . . . . . . . . . . .
1.2 The typical float page and its problems . . . . . . . . . . . . . . . .
Improvements to the float page algorithm
2.1 Details on the extended algorithm . . . . . . . . . . . . . . . . . . .
2.2 Possible pitfalls and how to avoid them . . . . . . . . . . . . . . . .
2.3 Tracing the algorithm . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4 Local (manual) adjustments . . . . . . . . . . . . . . . . . . . . . . .
The implementation
3.1 Option handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Tracing code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 User-level interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4 Patching the LATEX kernel commands . . . . . . . . . . . . . . . . .
3.5 Internal helper commands and parameters . . . . . . . . . . . . . .

2

3

1

54
54
55
55
56
57
58
60
60
60
61
61
62
66

Introduction

We start by giving a quick overview of LATEX’s float algorithm and the problems that
result from the approach used.
We then look in some detail into possible alterations and improvements to that algorithmand discuss possible issues that need to be resolved. In this section we also describe allconfiguration possibilities of the extended algorithm.
The final section then documents the code changes that are necessary to LATEX kernel
macros to implement the extension.
1.1

A quick overview of LATEX’s float algorithm

LATEX’s output routine uses a greedy algorithm to place floats near to their call-outsin the source document. The decision of how to place a float is made when the float isfirst encountered. If possible it is placed onto the current page, either in mid-text, on
top or into the bottom area, depending on what is allowed for the float and how many
floats are already placed into those areas.
If the float can’t be placed immediately, it goes into a defer list, and in order to notaccumulate too many unplaced floats LATEX tries to empty that list whenever there isa chance. This chance comes after the next page break: LATEX then starts a special“float page” algorithm in which it examines the defer list and from it forms float pages(i.e., pages that contain only floats). If necessary, it generates several float pages andonly stops if there are no floats waiting to be placed, or there are too few floats to∗ The current package version is v1.0a dated 2020/02/14.

Frank Mittelbach

TUGboat, Volume 41 (2020), No. 1

55

form a float page, or there are only floats left that are for one or another reason notallowed to be placed in this way.
Finally LATEX looks at the remaining floats and tries to place as many of them as
possible into the top and bottom area of the next page. Then it continues to processfurther text to fill the text part of that page. Details on the exact behavior of thealgorithm are discussed in [1].
1.2

Tinkering with theparameter settings
will usually produce
unwanted effects

The typical float page and its problems

LATEX considers a float page to be successfully built if its floats take up more than
\floatpagefraction of the whole page. By default this parameter is set to .5 whichmeans that such float pages may end up being half empty.
Many users think that this is not a good value and try to improve on it by enforcing ahigher percentage (such as 80%) only to find that this prevents LATEX in many cases
from successfully generating any float page, with the effect that all floats are suddenly
piling up at the end of the document.
Why is this the case? In a nutshell, because a higher percentage makes it much more
likely that a float can’t be placed, because it is not big enough to be used on its own
and no other nearby floats can be combined with it, because their combination violates
some other restriction, e.g., together they are bigger than a page, not all of them
are allowed to go on float pages, etc. The moment that happens this float prevents
the placement of all later floats of the same class too (i.e., all figures) and disaster isensured. In most cases these floats will then never get placed, because they need afloat of the right size from a different class to appear, which may in theory happen but
is, unfortunately, unlikely.
Thus, while tempting, tinkering with this parameter by making it larger is usually nota good idea, unless you are prepared to place most if not all of your floats manually,by overwriting the placement algorithm on the level of individual floats (e.g., using !
syntax and/or shifting its position in the source document).
Why does the current algorithm have these problems? To some extent, because it
offers only global parameters that need to fit different scenarios and thus settings thatare suitable when many floats need to be placed result in sub-optimal paginations in
document parts that contain only a few floats, and vice versa. To overcome this problem,
either one can try to develop algorithms with many more configurable parameters thatact differently in different scenarios or one can let the algorithm follow a main strategy,
configurable with only a few parameters (like today), but monitor the process andmake more local adjustments and corrections depending on the actual outcome of thatbase strategy and additional knowledge of the actual situation in a given document
part. This is the approach taken by the extension implemented in this package.
2

Improvements to the float page algorithm

A simple way to improve on the existing algorithm, without compromising its main
goal of placing the floats as fast as possible and as close as possible to their call-outs,
is the following: as long as there are many floats waiting to be placed, generate float
pages as necessary to get them placed (using the current algorithm and its parameters).
Once we are unable to build further float pages, do some level of backtracking by
checking if we have actually succeeded in placing all floats. If there are still floats
waiting to be placed then assume that what has been done so far is the best possible
way to place as many floats as possible (which it probably is). However, if we have beenable to place all floats onto float pages then check if the last float page is sufficiently
full; if not, undo that float page and instead redistribute its floats into the top andbottom area of the next upcoming page. This way the floats will be combined withfurther text and we avoid a possible half-empty float page.
The fewerfloatpages package

56

A typical case where
we don’t reallywant LATEX to
make a float page

TUGboat, Volume 41 (2020), No. 1
This approach will not resolve all the problematic scenarios where we find that LATEX
has decided to favor fairly empty float pages over some tighter type of placement. It
will, however, help to improve typical cases that do not involve too many floats. Fora example, if a single (larger) float appears near the end of a page, then using thestandard algorithm it can’t be immediately placed (because there isn’t enough freespace on the current page). It is therefore moved to the defer list and at the page
break it is then placed onto a float page (possibly by itself, if it is large enough to allow
for that) even though it could perfectly well go into the top or bottom area of the next
page and thus be combined with textual material on that page.
With the new algorithm this float page is reexamined and unless it is pretty much filled
up already, it is unraveled and its floats are redistributed into the top and bottom
areas of the next page. If, however, we have many floats waiting on the defer list, thenormal float page algorithm will first place as many of them as possible into float pages
and only the last of these pages will be subject to a closer inspection and a possible
unraveling.
An extension of this idea (and the one that we actually implement) is to monitor thewhole float page generation process and instead of just considering the last float pagein the sequence for unraveling, we look at each prospective float page in turn andbased on the current situation (e.g., number of floats still being unplaced, free spaceon the float page, etc.) decide whether this float page should be produced or whether
we should stop making float pages and instead place the pending floats into top andbottom areas of the upcoming page.
2.1

Don’t unravel a
float page if there
are too many floats
on the defer list

floatpagedeferlimit

Details on the extended algorithm

The main idea of the extended algorithm is to avoid unnecessary cases of float pages
especially if those float pages are fairly empty. Natural candidates are single float
pages, but even in cases where the current LATEX algorithm produces several sequential
float pages the extended algorithm may decide to replace them by normal pages undercertain conditions. However, the main goal is and should remain to place as many
floats as soon as possible and so generating float pages when many floats are waiting is
usually essential.
\setcounter{floatpagedeferlimit}{hnumber i}

Whether or not unraveling for a float page is considered at all is guided by the counter
floatpagedeferlimit. As long as there are more floats waiting on the defer list than
this number, float pages are not considered for unraveling. The default is 3 whichcorresponds to the default value for totalnumber, i.e., with that setting the unravelingof a floating page has a fighting chance to place all floats into the top and bottom
areas on the current page. It would also resolve cases for up to three floats, each larger
than \floatpagefraction, where the standard LATEX algorithm would produce three
individual float pages.
If you set the counter to 1 then only the last float page in a sequence is considered,and only if it contains only a single float and if there are no other floats that are still
waiting to be placed. If you set it to 0, then the extension is disabled, because float
pages are produced only if there was at least one float on the defer list.
Don’t unravel Even if we set floatpagedeferlimit to a fairly high value, we may not want to
if the float page unravel float pages that contain many floats. To support this case there is a second
contains many floats counter that guides the algorithm in this respect.

Frank Mittelbach

TUGboat, Volume 41 (2020), No. 1
floatpagekeeplimit

Don’t unravel if thefloat page contains
at least one [p] float

Don’t unravel
if the float pageis nearly filled57\setcounter{floatpagekeeplimit}{hnumber i}

Whenever the float page contains at least floatpagekeeplimit floats it will not be
unraveled. The default is also 3 so that float pages with three or more floats are nottouched. Obviously the counter can have any effect only if it has a value less than orequal to floatpagedeferlimit because this is tested first.
There are, however, a number of other situations in which we shouldn’t unravel a float
page even if the above checks for the size of the defer list were passed successfully. The
most important one is the case when the float page contains at least one float thatis allowed only on float pages (i.e., has a [p] argument). Such a float would not be
placeable in a top/bottom area on any page and thus would be repeatedly sent backto the defer list (possibly forever).
The other case where unraveling would normally be counterproductive is when theparticular float page is nearly or completely filled up with floats. If we unravel it, thenit is certain that we can place only some of the floats into the top or bottom area of
the next page, while some would end up on the defer list. That in turn means thatthese deferred floats float even further away from their call-out positions than need be.

\floatpagekeepfraction\renewcommand\floatpagekeepfraction{hdecimal i}

So what is a good way to determine if a float page is “full enough”? A possible answer
is that if the remaining free space on that page is less than \textfraction we consider
it full enough to stay. \textfraction defines the minimum amount of space that has
to be occupied by text on a normal page, thus if all floats together need so much spacethat this amount of text could not fit, then trying to place all floats onto a normal
page can’t succeed and some of them would get deferred for sure. To allow for further
flexibility the algorithm uses the variable \floatpagekeepfraction (defaulting to\textfraction) so if desired a lower (or even a higher) boundary can be set.
The above parameters give some reasonable configuration possibilities to guide thealgorithm as to when and when not to unravel a possible float page and instead produce
further normal pages. It should be noted, however, that except for the case of setting
floatpagedeferlimit to 1, there is always a chance that floats drift further awayfrom their call-outs, because they may not be immediately placeable due to otherparameter settings of the float algorithm. For example, the counter topnumber (default
value 2) limits the number of floats that can be placed in the top area on a normal
page and if more remain after unraveling only two can immediately go in this area.
2.2

Possible pitfalls and how to avoid them

The algorithm detects if a float is allowed only on float pages (i.e., is given in the sourceas [p]) and it will ensure that float pages containing such floats are not unraveled.
However, if you have a float with the default specifier [tbp] whose size is larger
than the allowed size of the top or bottom area (e.g., larger than \topfraction ×\textheight), then this effectively means it can only be placed on a float page.
However, according to the specifier the float is allowed to go into the top or bottom
area, so the algorithm, as explained so far, would be allowed to unravel and when thatfloat later is considered for top or bottom placement it will get again deferred and thus
move from one page to the next, most likely messing up the whole float placement.
checktb (option) There are two possible ways to improve the algorithm to avoid this disaster. One
way would be to check the float size when it is initially encountered and remove any
specifier that is technically not possible because of the parameter settings and the float
size. A possible disadvantage is that this determination will be done once and any
later (temporary) change to the float parameters will have no effect. This is currently
the package default. It can be explicitly selected by specifying the option checktb. In
this case you might see warnings like
The fewerfloatpages package

58

TUGboat, Volume 41 (2020), No. 1
LaTeX Warning: Float too large for top area: t changed to p on line ...

addbang (option) Another possibility is that we automatically add a ! specifier to all floats during
unraveling, i.e., when we send them back for reevaluation. This way such floats become
placeable into top and bottom areas regardless of their size. This may result in fewer
pages at the cost of violating the area size restrictions once in a while. It is specified
with the option addbang.
nocheck (option) If you prefer no automatic adjustment of the specifiers, add the option nocheck. In
this case you might find that floats of certain sizes are unplaceable and thus get delayed
to the end of the document. If that happens, the remedy is either to explicitly specify[p] or [hp] for such a float (to ensure that they aren’t subject to unraveling) or to
manually add an exclamation specifier, e.g., [!tp] so that LATEX doesn’t use the size
restrictions in its algorithm.
2.3

Tracing the algorithm

trace (option) The package offers the option trace, which if used, will result in messages such as[1]
fewerfloatpages: PAGE: trying to make a float pagefewerfloatpages: ----- \@deferlist: \bx@B \bx@D
fewerfloatpages: starting with \bx@B
fewerfloatpages: --> success: \bx@B \bx@D
fewerfloatpages: ----- current float page unraveled(free space 192.50336pt > 109.99832pt)[2]
which means that the algorithm is trying to make a float page from the defer list
which at that point contained two floats (the float boxes \bx@B and \bx@D), that it wasable to produce a float page containing just \bx@B and \bx@D, and that the extended
algorithm then decided to unravel that float page, because it has an unused space of
192.5pt, i.e., roughly 16 text lines. With the current \floatpagekeepfraction thatis too much empty space on the page.
Or it might say
fewerfloatpages: PAGE: trying to make a float pagefewerfloatpages: ----- \@deferlist: \bx@D \bx@F \bx@G \bx@H \bx@I
fewerfloatpages: starting with \bx@D
fewerfloatpages: --> success: \bx@D \bx@F
fewerfloatpages: ----- too many deferred floats for unraveling (5 > 3)[3]
which means that the algorithm made a float page out of the first two floats fromthe defer list (i.e., 3 remained). That page was kept regardless of the amount of freespace it contained because we have a total of 5 floats on the defer list and the counter
floatpagedeferlimit has its default value of 3.
The above tracing messages are both from the same test document. What they also
(implicitly) show is that the unraveling that happened after page 1 resulted in only
one float (\bx@B) being placed on page 2, because we see the second one (\bx@D)
reappearing in the defer list after page 2 got finished. In other words it was moved one
page further away from its call-out: the price for getting a nicely filled page 2 insteadof a fairly empty float page with roughly 200 points left empty. The final part of thattest document then exhibits another type of message:
fewerfloatpages: PAGE: trying to make a float pagefewerfloatpages: ----- \@deferlist: \bx@G \bx@H \bx@I
fewerfloatpages: starting with \bx@G
fewerfloatpages: --> success: \bx@G \bx@H \bx@I
Frank Mittelbach

TUGboat, Volume 41 (2020), No. 1

59

fewerfloatpages: ----- all floats placed on float page(s)fewerfloatpages: ----- current float page kept, full enough(free space 38.99496pt < 109.99832pt)[4]This means that the remaining floats (that were left unplaced after float page 3 got
constructed) formed a float page and that float page was the last in sequence (i.e., allfloats have been placed). However, this time the algorithm decided not to unravel it,
because it is nicely full: there are only 39 points of free space left on that page.
Three other possible messages are shown in this sequence of tracing lines from a second
test document (which is using some uncommon settings: floatpagedeferlimit is 10
and floatpagekeeplimit is 5):[1]
fewerfloatpages: PAGE: trying to make a float pagefewerfloatpages: ----- \@deferlist: \bx@B \bx@C \bx@D \bx@E \bx@F \bx@G \bx@H
fewerfloatpages: starting with \bx@B
fewerfloatpages: --> success: \bx@B \bx@C \bx@D \bx@E \bx@F \bx@G \bx@H
fewerfloatpages: ----- current float page kept (contains at least 5 floats)[2] [3]In this case 7 floats have been waiting on the defer list and the algorithm was able toconstruct a float page using all of them. The algorithm then keeps that page because
it has 5 or more floats in it (the value of the floatpagekeeplimit counter).
The next message in that test document shows what happens when there are not enoughfloats waiting or they are simply too small (to even get past the \floatpagefraction
limit):
fewerfloatpages: PAGE: trying to make a float pagefewerfloatpages: ----- \@deferlist: \bx@I \bx@J
fewerfloatpages: starting with \bx@I
fewerfloatpages: --> failfewerfloatpages: starting with \bx@J
fewerfloatpages: --> failfewerfloatpages: --> fail: no float page made[4]
So no float page was made, but for some reason (that becomes clear later) the two
floats also didn’t got distributed into the top or bottom area of the next page. Instead
they remained on the defer list and during processing of page 4 one more float was
found so that after that page the defer list had grown to length 3:
fewerfloatpages: PAGE: trying to make a float pagefewerfloatpages: ----- \@deferlist: \bx@I \bx@J \bx@K
fewerfloatpages: starting with \bx@I
fewerfloatpages: --> success: \bx@I \bx@J \bx@K
fewerfloatpages: ----- current float page kept, contains a float
fewerfloatpages:
with p but no t or b specifier[5]This time all floats could be placed, but again the float page wasn’t unraveled (eventhough in the test document it contained a lot of white space) because of the factthat one of its floats (in fact the first though that can only be deduced implicitly) was
specified as a “float page only” float. This explains why on page 4 \bx@I couldn’t beplaced into the top or bottom area and then all following floats of the same class (thetest document contained only figure floats) couldn’t be placed either.
Detailed tracing If you want detailed tracing of the complete algorithm, also load the fltrace packageof the complete and enable the tracing with \tracefloats anywhere in your document. Note, however,algorithm that the resulting output is very detailed but rather low-level and unpolished.
The fewerfloatpages package

60

TUGboat, Volume 41 (2020), No. 1
2.4

Local (manual) adjustments

If the extended algorithm is used you will get fewer float pages that contain a noticeable
amount of white space. By adjusting \floatpagekeepfraction and the counters
floatpagekeeplimit and floatpagedeferlimit you can direct the algorithm tounravel more or fewer of the otherwise generated float pages. However, in some cases
it might happen that redistribution of the floats into the top and bottom areas of thenext page(s) may result in some of them drifting too far away from their call-outs. If
that happens, you can either try to change the general parameters or you could help
the algorithm along by using the optional argument of individual float environments.
The two main tools at your disposal are• using the [!..] notation to allow the float to go into the top or bottom area even
if it would be normally prevented by other restrictions;• using [p] to force a float into a float page as that prevents the algorithm fromunravelling the float page which contains that float.
As an alternative you can, of course, temporarily alter the definition of the command\floatpagekeepfraction or the values of two counters in mid-document, but remember that they are not looked at when a float is encountered in the source but when weare at a page break and LATEX attempts to empty the defer list, which is usually laterand unfortunately somewhat asynchronous, i.e., not easy to predict.
3

The implementation

We start off with the package announcement. Requiring a fairly new LATEX kernel is
not absolutely necessary but it will help to ensure that we patch what we think wepatch and in the future it means that will can be assured that the rollback functionalityof the kernel is available in case will need to support several releases of the package.
1
2

h*packagei\NeedsTeXFormat{LaTeX2e}[2018-04-01]\ProvidesPackage{fewerfloatpages}[\fewerfloatpagesdate\space \fewerfloatpagesversion\space5
improve float page generation (FMi)]
3
4

3.1

Option handlingThis release of the package has four options: trace for tracing the algorithm, addbang
and checktb to handle cases where the float size in combination with the float specifiers
makes it difficult if not impossible to place the floats, and nocheck to not makeadjustments for that case.
The option trace enables tracing of the algorithm and is implemented by giving thecommand \fl@trace (which is also used by the fltrace package) a suitable definition.
To handle the case that the fltrace package is loaded first, we use \providecommand, sothat its definition is not overwritten, but used if it is already available. If the package
is loaded later everything works fine because it unconditionally defines \fl@trace, i.e.,
overwrites whatever fewerfloatpages has defined.
\DeclareOption{trace}{\providecommand\fl@trace[1]%8{{\let\@elt\@empty\typeout{fewerfloatpages: #1}}}}
6
7

The other three options are mutually exclusive so we number them 0 to 2 in thecommand \fp@strategy to ensure that only one is ever active. Option nocheck doesnothing, with the cost that some floats may float to the end of the document. Option
addbang adds a ! to floats that are sent back for reevaluation when a float page getsunraveled. Option checktb implements a different approach to handling problematic
floats: the vertical size of a float is checked, and if it is too large to be allowed intoFrank Mittelbach

TUGboat, Volume 41 (2020), No. 1

61

the top or the bottom area, any t or b specifier is replaced by p (or dropped if p isalready specified).
\def\fp@strategy{0}%\DeclareOption{nocheck}{\def\fp@strategy{0}}11 \DeclareOption{addbang}{\def\fp@strategy{1}}12 \DeclareOption{checktb}{\def\fp@strategy{2}}910% better name?

The actual implementation is done later. The default is currently checktb but thismay change to addbang based on user feedback.
13
14\ExecuteOptions{checktb}\ProcessOptions

3.2\fl@trace

Tracing code

The command \fl@trace is used to output tracing information. By default the tracingof the algorithm is turned off, so \fl@trace will simply swallow its argument. But if
fltrace is loaded or the option trace is given then the command already has a definition
so we don’t change it here.
15\providecommand\fl@trace[1]{}(End definition for \fl@trace.)

3.3

User-level interfaces

For the most part the packages provides internal code that extends the float algorithmof LATEX. There are, however, also three new parameters that guide this algorithm;
they are defined in this section.
\floatpagekeepfraction The fraction that the algorithm uses to decide whether a given float page is so fullthat it would be pointless to unravel it for the reasons outlined above. The default iswhatever fraction has been chosen as the minimum amount of text that needs to be on
a normal page (i.e., \textfraction).
16\newcommand\floatpagekeepfraction{\textfraction}(End definition for \floatpagekeepfraction. This variable is documented on page 57.)

floatpagedeferlimit The algorithm will only consider unraveling float pages if there are not too many\c@floatpagedeferlimit floats on the defer list. The definition of “too many” is provided through the counter

floatpagedeferlimit if there are more floats waiting to be placed; float pages are
generated until their number falls below this level. Thus, a value of 0 will disable thewhole algorithm and a value of 1 means that only float pages with a single float might
get unraveled and only if there aren’t others still waiting to be placed.
17\newcounter{floatpagedeferlimit}\setcounter{floatpagedeferlimit}{3}(End definition for floatpagedeferlimit and \c@floatpagedeferlimit. These variables are documented on page 56.)

floatpagekeeplimit A float page that contains at least this number of floats will also be kept. The default\c@floatpagekeeplimit is 3 but if you have a lot of small floats it might be better to set this to a higher value.
18\newcounter{floatpagekeeplimit}\setcounter{floatpagekeeplimit}{3}(End definition for floatpagekeeplimit and \c@floatpagekeeplimit. These variables are documented
on page 57.)

The fewerfloatpages package

62

TUGboat, Volume 41 (2020), No. 1
3.4\@tryfcolumn

Patching the LATEX kernel commands

The main macro we have to patch to extend LATEX’s algorithm is \@tryfcolumn. That
command is changed when fltrace gets loaded, so we make our definition as late as
possible to ensure that it will survive.
19\AtBeginDocument{%\def \@tryfcolumn #1{%
\global \@fcolmadefalse
22\ifx #1\@empty
23
\else
24
\fl@trace{PAGE: trying to make a float
25\if@twocolumn column/page\else page\fi}%26\fl@trace{----- \string #1: #1}%
27
\xdef\@trylist{#1}%28\global \let \@failedlist \@empty
29\begingroup30\let \@elt \@xtryfc \@trylist
31\endgroup
20
21

Up to this point the definition is the same as in the original algorithm. At this point
the switch \if@fcolmade tells us if making a float page was successful and the originalalgorithm then called \@vtryfc and removed the floats used for this float page fromthe defer list.
In the extended algorithm this is the place where things start to differ as we may notwant that float page to actually come into existence.
32\if@fcolmade

As a first step we count the number of floats in the defer list and save the result in\fp@candidates.
33
34
35
36

\fp@candidates\z@\def\@elt##1{\advance\fp@candidates\@ne}%#1%\let\@elt\relax

Now we compare this number with the values of the counter floatpagedeferlimit
and if it is higher we definitely want to keep the float page. The rationale is that if we
unravel now, then all floats from the defer list need to go into the top/bottom areas(or get deferred again but to a later page) and so a high number means the defer list
will not get shortened very much and too many floats will get delayed further.
37
38
39
40

\ifnum \fp@candidates >\c@floatpagedeferlimit\fl@trace{----- too many deferred floats for unraveling(\the\fp@candidates\space> \the\c@floatpagedeferlimit)}%\else

Otherwise we do a bit more testing. First we set \if@fcolmade back to false; after all
our goal is to not keep the float page. If during the tests we decide otherwise we set itback to true, which then signals that it should stay.
We also count the floats on the float page, reusing \fp@candidates for that, which is
why we initialize it to zero.
41
42\global\@fcolmadefalse\fp@candidates\z@

The actual checking is done with \fp@analyse@floats@for@unraveling and it loops
over \@flsucceed, i.e., the floats for that float page. This checks if any float for thatpage has only a [p] specifier and if so it sets \if@fcolmade back to true and as aside effect it also does the counting for us. Furthermore, it also changes the switch totrue if it finds at least floatpagekeeplimit floats on that page.
Frank Mittelbach

TUGboat, Volume 41 (2020), No. 1
43
44
45

63

\let\@elt\fp@analyse@floats@for@unraveling\@flsucceed\let\@elt\relax

Now we recheck the state of the switch and if it still says false, all tests so far indicatethat we don’t want the float page.
46\if@fcolmade\else

But we aren’t done yet: the float page might be nicely filled, in which case it would bea shame to unravel it. During the above loop we also measured the free space on the
float page and stored it in \fp@unused@space (see \@xtryfc below). We now comparethat to the maximum free space that we consider to be still okay and if there is more
we finally do the unraveling.
47
48
49
50
51

\@tempdima\floatpagekeepfraction\@colht\ifdim \fp@unused@space >\@tempdima\fl@trace{----- current float page unraveled^^J%\@spaces\@spaces\@spaces\space\space\space(free space \fp@unused@space\space > \the\@tempdima)}%

For this we basically return all floats back to the defer list. The switch is still false
so it doesn’t need changing.
52\xdef #1{\@failedlist\@flsucceed\@flfail}%

However, we may also want to add a ! specifier to each of the floats (if the addbang
option was given) so we loop over all the floats once more to get this done.1
53
54
55
56

\let\@elt\fp@maybe@add@bang\@flsucceed\let\@elt\relax\else

But if we want to keep the float page after all, we have to set the switch back to true
so that the rest of the algorithm proceeds correctly.
57
58
59
60
61
62
63

\global \@fcolmadetrue\fl@trace{----- current float page kept, full enough^^J%\@spaces\@spaces\@spaces\space\space\space(free space \fp@unused@space\space < \the\@tempdima)}%
\fi
\fi
\fi

The next \else matches the first \if@fcolmade, i.e., the case that the algorithm
wasn’t able to make any float page. If we are tracing the algorithm, we want to tell
the user about this.
64
65
66

\else
\fl@trace{ --> fail: no float page made}%\fi

Finally, at this point we are back in the original algorithm. Now the switch tells thetruth about whether or not we want to make a float page, and if so, we go ahead andproduce it.
\if@fcolmade\@vtryfc #1%
69
\fi
70
\fi}%
71 }% -- END of \AtBeginDocument
67

68(End definition for \@tryfcolumn.)
1 This could have been integrated with \fp@analyse@floats@for@unraveling but there is notmuch gain if any and by keeping it separate the processing logic seems clearer to me.

The fewerfloatpages package

64

TUGboat, Volume 41 (2020), No. 1\@makefcolumn

In contrast to \@tryfcolumn this macro will always make float pages out of the deferred
floats. It is used by \clearpage when we really need the floats to get out because
there is no further text coming up. Thus, in that case we should not unravel the float
pages. That would happen with the kernel definition of \@makefcolumn as that calls\@tryfcolumn which we just changed above. We therefore modify its definition to
include the original code for \@tryfcolumn instead of calling our updated version.
Again this change is made at \begin{document} so that it is not overwritten in casefltrace is loaded afterwards.
\AtBeginDocument{%\def\@makefcolumn #1{%
74
\begingroup75\@fpmin -\maxdimen
76\let \@testfp \@gobble
72
73

At this point the original definition called \@tryfcolumn and the lines above ensured
that it was always succeeding in making a float page. However, since we have changed
that command to do unraveling we had better not use it any more. Instead we replace
it by its original definition (with the addition of two tracing lines).
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

\global \@fcolmadefalse\ifx #1\@empty\else\fl@trace{PAGE: trying to make a float\if@twocolumn column/page\else page\fi}%\fl@trace{----- \string #1: #1}%
\xdef\@trylist{#1}%\global \let \@failedlist \@empty\begingroup\let \@elt \@xtryfc \@trylist\endgroup\if@fcolmade\@vtryfc #1%\fi\fi\endgroup}%}% -- END of \AtBeginDocument(End definition for \@makefcolumn.)\@xtryfc

The only change to \@xtryfc is the addition of the \fl@trace calls. But this extra
tracing info is generally useful and should also be done in the fltrace package.
The macro initiates a float page trial starting with the first float in \@trylist. Moredetailed explanations can be found in the documented sources of the LATEX kernel [2].
\def\@xtryfc #1{%
\fl@trace{ starting with \string#1}%
97\@next\reserved@a\@trylist{}{}%
98\@currtype \count #1%
99\divide\@currtype\@xxxii
100\multiply\@currtype\@xxxii
101\@bitor \@currtype \@failedlist
102\@testfp #1%
103\@testwrongwidth #1%
104\ifdim \ht #1>\@colht
105\@testtrue
106
\fi
107
\if@test
108\@cons\@failedlist #1%
95
96

Frank Mittelbach

TUGboat, Volume 41 (2020), No. 1\fl@trace{ --> fail}%\else\@ytryfc #1%
\fi

109
110
111
112
113

65

}%

(End definition for \@xtryfc.)\@ytryfc

The command \@ytryfc, which is also part of the code in the kernel, loops throughthe defer list and tries to build a float page starting with the float passed to it in#1. If it succeeds, the floats that are part of the float page are listed in \@flsucceed
and the switch \if@fcolmade is set to true. Also of interest to us is that inside thecode \@tempdima holds the size taken up by the floats, so we can use this to calculate
the unused space on the float page and store it in \fp@unused@space for use in our
extended algorithm.
\def\@ytryfc #1{%
\begingroup116\gdef\@flsucceed{\@elt #1}%
117
\global\let\@flfail\@empty
118\@tempdima\ht #1%
119\let\@elt\@ztryfc
120\@trylist
121\ifdim \@tempdima >\@fpmin
122\global\@fcolmadetrue
114
115

This branch is executed when the floats together are big enough to form a float page.
Thus, this is the right place to calculate the free space by subtracting the used spacefrom the column height (which may not be the full height if there are spanning floats
in two column mode).
123
124
125

\@tempdimb\@colht\advance\@tempdimb-\@tempdima\xdef\fp@unused@space{\the\@tempdimb}%

The remaining code is again unchanged except that we added two additional tracing
lines (though those should be added to the fltrace package too one of these days).
126
127
128
129
130
131
132
133
134

\else
\@cons\@failedlist #1%\fl@trace{ --> fail}%\fi\endgroup\if@fcolmade\let\@elt\@gobble\fl@trace{ --> success: \@flsucceed}%\fi}(End definition for \@ytryfc.)\@largefloatcheck

The final kernel macro we need to patch is \@largefloatcheck. This is called when
a float box is constructed and it checks if that box is larger than the available\textheight, which would mean it could never be placed anywhere, not even on a
float page. The code therefore reduces the box size as necessary and issues a warning.
This macro is therefore a natural candidate to also check if the float size is too large
for the float to go into top or bottom areas (if the option checktb is used).
\def \@largefloatcheck{%\ifdim \ht\@currbox>\textheight137\@tempdima -\textheight138\advance \@tempdima \ht\@currbox
135
136

The fewerfloatpages package

66

TUGboat, Volume 41 (2020), No. 1\@latex@warning {Float too large for page by \the\@tempdima}%\ht\@currbox \textheight
\fi

139
140
141

The \fp@maybe@check@tb does the checking (or nothing if the option is not given).
\fp@maybe@check@tb

142
143

}

(End definition for \@largefloatcheck.)

3.5\fp@candidates

Internal helper commands and parameters

We use an internal counter to count the number of floats in the defer list and on a
float page under construction.
144\newcount\fp@candidates(End definition for \fp@candidates.)\fp@unused@space

In \fp@unused@space we store the amount of free space on the current float page.
145\def\fp@unused@space{}(End definition for \fp@unused@space.)\fp@analyse@floats@for@unraveling With \fp@analyse@floats@for@unraveling we loop over the floats on the float page,i.e., #1 will be one such float.
One of its tasks is to count the floats (in \fp@candidates) and check if there are atleast floatpagekeeplimit of them (which means the float page should definitely bekept).
Its most important task, however, is to check if one of the floats has only a p specifier
but no other. In that case it is essential that we not unravel the float page becausesuch a float would then only go back onto the defer list as it has no place to go except
a float page.
\def\fp@analyse@floats@for@unraveling#1{%\advance\fp@candidates\@ne148\ifnum \fp@candidates <\c@floatpagekeeplimit
146
147

So far we haven’t got enough floats to know that this float page should be kept so wecheck the given float specifiers.
The test may look a little weird,2 but what we want to know is this: is there a p (thirdbit) but neither a b (second bit) nor a t (first bit). We don’t care about h or ! which
are the next two bits in the float counter nor any of its higher bits (which encode
the type of float). So we divide the integer number by 8, which drops the two leastsignificant bits (think of the integer represented in binary format), and then multiply
it again by 8. As a result the first two bits are zeroed out. We then compare the resultwith the original value and if the two values are the same then the b and t bits must
both have been zero from the start. And since the float was on a float page we also
know that it had a p specifier.
149
150
151
152

\@tempcntb\count#1%\divide\@tempcntb 8\relax\multiply\@tempcntb 8\relax\ifnum \count#1=\@tempcntb

2 “Little” might be an understatement. Encoding a lot of information in individual bits of thecounter value associated with a float was a great way in the early days of LATEX to preserve macro
space (and absolutely essential back then), but these days . . . Anyway, it is the way it is and thatpart can’t really be changed without breaking a lot of packages.

Frank Mittelbach

TUGboat, Volume 41 (2020), No. 1

67

In that case we set \if@fcolmade to true to signal that this float page should be kept,
generate a tracing message and change \@elt to become \@gobble to quickly jumpover any remaining floats in the loop without doing further tests or generate further
tracing messages.
\global \@fcolmadetrue\fl@trace{----- current float page kept, contains a float}%\fl@trace{\@spaces\space\space with p but no t or b specifier}%\let\@elt\@gobble
\fi

153
154
155
156
157

On the other hand, if we have seen enough floats we also know that the float pageshould be kept, so change the switch, give some tracing info and stop checking:\else\global \@fcolmadetrue\fl@trace{----- current float page kept
(contains at least \the\fp@candidates\space floats)}%\let\@elt\@gobble
\fi

158
159
160
161
162
163
164

}

(End definition for \fp@analyse@floats@for@unraveling.)\fp@maybe@add@bang

The helper \fp@maybe@add@bang is used to loop through all of the floats of a float
page (receiving each as #1 in turn) and add a ! specifier if there wasn’t one before.
However, we only define it if we implement strategy 1 which is option addbang.
165
166\ifnum\fp@strategy=1\def\fp@maybe@add@bang#1{%

Find out if the fourth bit is set (which means no !) and if so subtract 16 from the float
counter which means setting it to zero.
167
168
169
170

\@boxfpsbit #1\sixt@@n\ifodd \@tempcnta\global\advance\count#1-\sixt@@n
\fi

}
\else
173
\let\fp@maybe@add@bang\@gobble
174 \fi
171
172(End definition for \fp@maybe@add@bang.)\fp@maybe@check@tb

The code in \fp@maybe@check@tb is used in \@largefloatcheck to test if the float
has a t or b specifier but is too large to fit into the respective area. This test is notmade by default but only if the option checktb is used, i.e., strategy 2.
175
176\ifnum\fp@strategy=2\def\fp@maybe@check@tb{%

Again this is a case of looking at various bits in the float counter value in binary
notation. If the specifier contained a ! we are ok and it would be wrong to change thespecifier, because in that case size restrictions for areas do not apply. For this we haveto test the fourth bit which means dividing by 16 and then checking if the result is
odd or even (odd means there was no !).3 The kernel \@getfpsbit does this for us
and stores the result in \@tempcnta so we can test this with \ifodd to see if the bit
was set.
3 I’m sure we had good reasons to implement it this way in 1992 — we probably saved a few bytes
which was important back then. But it is certainly odd that for ! a value of zero means that it was
specified on the float while for all other specifiers a value of 1 indicates that the specifier was given.

The fewerfloatpages package

68

TUGboat, Volume 41 (2020), No. 1\@getfpsbit \sixt@@n\ifodd \@tempcnta

177
178

If there was no ! we check if the height of the float is too large to fit into the top area.
\ifdim \ht\@currbox>\topfraction\textheight179

If that is the case we also check the first bit of the float counter to see if a t was
specified. For this we use \@getfpsbit again but this time with 2 as the argument
since we test the first bit.
\@getfpsbit \tw@\ifodd \@tempcnta

180
181

If t was specified we need to remove it (next line) and add (if not already present) a p
instead. This is done by \fp@add@p@bit. Finally we add a warning for the user about
the change.
\global\advance\count\@currbox -\tw@\fp@add@p@bit\@latex@warning {Float too large for top area: t changed to p}%
\fi
\fi

182
183
184
185
186

A similar test and action is needed for bottom floats; here we need to look at and zeroout the second bit (i.e., using 4 as a value).
\ifdim \ht\@currbox>\bottomfraction\textheight\@getfpsbit 4\relax\ifodd \@tempcnta\global\advance\count\@currbox -4\relax\fp@add@p@bit\@latex@warning {Float too large for bottom area:
b changed to p}%
\fi
\fi
\fi

187
188
189
190
191
192
193
194
195
196
197

}

In all other cases \fp@maybe@check@tb does nothing.
198\else \let\fp@maybe@check@tb\relax \fi(End definition for \fp@maybe@check@tb.)\fp@add@p@bit

The command \fp@add@p@bit adds the p specifier which means checking the third bit
and if not set, adding 8 to the float counter.
\def\fp@add@p@bit{%\@getfpsbit 8\relax
201\ifodd \@tempcnta \else \global\advance\count\@currbox 8\relax \fi}
199
200

(End definition for \fp@add@p@bit.)202

h*packagei

References[1] Frank Mittelbach. How to influence the position of float environments like figure
and table in LATEX? TUGboat 35:3, 2014.
https://www.latex-project.org/publications/indexbytopic/2e-floats/[2] LATEX Project Team. The LATEX 2ε Sources (660+ pages), 2020.
https://www.latex-project.org/help/documentation
 Frank Mittelbach
Mainz, Germany
https://www.latex-project.org
https://ctan.org/pkg/fewerfloatpages

Frank Mittelbach