# Calculating pagination bounds for display

Status: Done
Confidence: Certain

tl;dr: How do you calculate the limits for a pagination widget like “« 4 5 6 7 »”? Number the pages starting from 0. Then the first page to display $startPage$ and one page after the last page to display $endPage$ (4 and 8 in the example above) are given by

$startPage = [ (currentPage - \lceil window/2 \rceil + 1) \ \downarrow \ Npages - window ] \uparrow 0$

$endPage = [ (currentPage + \lfloor window/2 \rfloor + 1) \ \uparrow \ window] \downarrow Npages - 1$

where

• $currentPage$ is the currently displayed page.
• $window$ is the number of page numbers to display in the widget.
• $Npages$ is the total number of pages.
• $\lceil \cdot \rceil$ is the ceiling function.
• $\lfloor \cdot \rfloor$ is the floor function.
• $a \uparrow b$ is the maximum of $a$ and $b$.
• $a \downarrow b$ is the minimum of $a$ and $b$.

That’s the short version. Now how do we derive that?

First, a warning. Don’t use page numbers as primitive objects unless you’ll only have one item per page. If you’re going to list multiple items per page, tracking page numbers instead of the first item to display on a page will make your code unbelievably complicated when you have to change the number of items displayed per page, or when you want to show a list of items beginning with a specific index, rather than a particular page.

So label your items from 0 to $N$, and let $currentItem$ be the index of the first item to be displayed on the current page. If each page is to show at most $pageSize$ entries (“at most” because there may not be enough items to fill the last page to exactly $pageSize$), then $currentPage = \lfloor currentItem / pageSize \rfloor$, and the $i$th page will start at $i \cdot pageSize$. There will be $Npages = \lceil N / pageSize \rceil$ pages in all.

Now let’s move on to deriving the expressions for the actual pagination range. What, precisely, is the problem? For a particular integer between $0$ and $Npages$, we want to find a range of numbers of a fixed size which also fall in $[0, Npages]$ and are as centered as possible around $currentPage$. As usual, we’ll describe the range as a half open interval by the first page in the range $startPage$ and one after the last page in the range $endPage$. For example, in “« 4 5 6 7 »”, $startPage = 4$ and $endPage = 8$. Let the size of the desired range be $window$.

$endPage - startPage = window$, but we need a second constraint to center $currentPage$ as much as possible in this range. If $window$ is odd, then we want the same number of entries on both sides of the current page number. For example, if we are on page 3 and $window = 5$, We should display “« 1 2 3 4 5 »”. If $window$ is even, we must make a choice: do we want to display more page numbers after the current one, or more before? I chose to show more numbers after on the theory that a user is more interested in stuff he has not yet reached. This constraint translates to

$currentPage - startPage = endPage - currentPage - 1 - (1 - window \mathbf{mod} 2)$

So, centering the current page in the control, $currentPage - startPage$ should be one less than $endPage - currentPage$ since $startPage$ is the first element and $endPage$ is one after the last element. So if $window$ isn odd, $currentPage - startPage = endPage - currentPage - 1$. If $window$ is odd, since I chose to show more following pages, it is $currentPage - startPage = endPage - currentPage - 2$. We can combine all these expressions to get

$\begin{eqnarray} currentPage - startPage & = & endPage - currentPage - 1 - (1 - window \mathbf{mod} 2) \\ window & = & endPage - startPage \end{eqnarray}$

which we’ll solve for $startPage$ and $endPage$. We’ll be using three properties of the floor and ceiling functions in the calculations below, which I’ll summarize here:

$\begin{eqnarray} x &=& \lfloor x/2 \rfloor + \lceil x/2 \rceil \\ \lfloor x/2 \rfloor &=& x/2 - (x\ \mathbf{mod}\ 2)/2 \\ \lceil x/2 \rceil &=& x/2 + (x\ \mathbf{mod}\ 2)/2 \\ \end{eqnarray}$

We calculate thus:

$currentPage - startPage = endPage - currentPage - 1 - (1 - window \mathbf{mod} 2)$ $\ \wedge\ window = endPage - startPage$

$\equiv$ { solve first equation for $startPage$ and second equation for $endPage$ }

$startPage = -endPage - 2 \cdot currentPage + 2 - window\ \mathbf{mod}\ 2$ $\ \wedge\ endPage = startPage + window$

$\equiv$ { substitute $endPage$ for $startPage$ in first equation }

$startPage = -startPage - window - 2 \cdot currentPage + 2 - window\ \mathbf{mod}\ 2$ $\ \wedge\ endPage = startPage + window$

$\equiv$ { group $startPage$ and simplify first equation }

$startPage = currentPage - (window/2 + (window\ \mathbf{mod}\ 2)/2) + 1$ $\ \wedge\ endPage = startPage + window$

$\equiv$ { $x/2 + (x\ \mathbf{mod}\ 2)/2 = \lceil x/2 \rceil$ }

$startPage = currentPage - \lceil window/2 \rceil + 1$ $\ \wedge\ endPage = startPage + window$

$\equiv$ { substitute for $startPage$ in second equation }

$startPage = currentPage - \lceil window/2 \rceil + 1$ $\ \wedge\ endPage = currentPage - \lceil window/2 \rceil + 1 + window$

$\equiv$ { $x = \lfloor x/2 \rfloor + \lceil x/2 \rceil$ }

$startPage = currentPage - \lceil window/2 \rceil + 1$ $\ \wedge\ endPage = currentPage + \lfloor window/2 \rfloor + 1$

This works in the middle of the range. At the edges, we have to calculate a different window. The first page is 0 and can never be less. The last page $Npages-1$ and can never be more. At the left end, $startPage=0$ and $endPage=window$. At the right end, $startPage = Npages - window$ and $endPage = Npages$. We augment the expressions for $startPage$ and $endPage$ to handle these cases (note that the order of operations is important):

$startPage = [ (currentPage - \lceil window/2 \rceil + 1) \ \downarrow \ Npages - window ] \uparrow 0$ $endPage = [ (currentPage + \lfloor window/2 \rfloor + 1) \ \uparrow \ window] \downarrow Npages - 1$

For quick reference, here is a Python function implementing this:

from math import ceil, floor

def bounds(current_page, n_pages, window):
return (
max(min(i - ceil(window / 2.0) + 1, npages - window), 0),
min(max(i + floor(window / 2.0), window), npages-1)
)