Computing floating-point division from an accurate reciprocal [WiP]

This article addresses the problem of computing $\circ(\frac{x}{y})$ from $\circ(\frac{1}{y})$, targetting single and double floating-point precision.

New method description


The method we use is based on the end of a Newton-Raphson division iteration [1, 2]. The iteration starts with an accurate approximation $r$ of $\frac{1}{y}$ and $a_0 = x \times \circ(\frac{1}{y})$

the error of $a_0$ is $\epsilon_0$,
$\delta_0 = | \frac{x}{y} - \circ(x \times r)|$
$\delta_0 = | \frac{x}{y} - x \times \frac{1}{y} (1 + \epsilon_r) (1 +\epsilon_{mult}) |$
$\delta_0 = | \frac{x}{y} \times (\epsilon_r + \epsilon_{mult} + \epsilon_r . \epsilon_{mult}) | $
$\epsilon_0 = \frac{\delta_0}{\frac{x}{y}} \le | \epsilon_r + \epsilon_{mult} + \epsilon_r . \epsilon_{mult} |$

$r$ is assumed to be a correctly rounded approximation of $\frac{1}{y}$ and $a_0$ is also implemented as a correctly rounded multiplication, so  $ \epsilon_{mult}$ and $\epsilon_r$ are bounded by $2^{-53} $ which means:

$\delta_0 \le |2^{-52} + 2^{-106}|$: $a_0$'s accuracy is a little greater than one $ulp$. Our goal is to get a final accuracy of less than one half of one $ulp$ (i.e. a correctly rounded result).

Our method is based on the following (standard) iteration:
$ e_1 = x - y \times a_0 $
$ a_1 = a_0 + e_1 \times r $

A piece of pythonsollya code available at the end of this article can help measure the accuracy of this method in a few random test cases.

Without special care this method is not valid for all inputs:

  • $r$ overflows if $y <= 2^{-127}$ 
  • computing $r$ raises DivByZero even if $x$ is not a canonical number 

Special cases management


Once the numerical behavior is proven we can focus on the behavior on special cases.

x y $\frac{1}{y}$ $\frac{x}{y}$ New method
$a_0$ $e_1$ $a_1$
0 0 $\infty$ DBZ $qNaN$ IO $qNaN$ IO $qNaN$ $qNaN$
Num Num $0$ $0$ $0$ $0$
$\infty$ $0$ $0$ $0$ $0$ $0$
$qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$
$\infty$ 0 $\infty$ DBZ $\infty$ $\infty$  $qNaN$ IO $qNaN$
Num Num $\infty$ $\infty$ $qNaN$ IO $qNaN$ IO
$\infty$ $0$ $0$ $0$ $0$ $0$
$qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$
$Num$ 0 $\infty$ DBZ $\infty$ DBZ $\infty$ $qNaN$ IO $qNaN$
Num Num (A) Num (A) Num Num Num
$\infty$ $0$ $0$ $0$ $0$ $0$
$qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$
$NaN$ 0 $\infty$ DBZ $\infty$ DBZ $qNaN$ $qNaN$  $qNaN$
Num Num (A) $qNaN$ $qNaN$ $qNaN$ $qNaN$
$\infty$ $0$ $qNaN$ $qNaN$ $qNaN$ $qNaN$
$qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$ $qNaN$

As illustrated by the table above, most cases behave similarly if we first compute $\frac{1}{y}$ as a single floating-point operation and then use the iteration to compute $\frac{x}{y}$. Here behaving similarly means returning the same results and raising the same exception flags.
   However there are a few cases (indicated in bold red) were a spurious flag can be raisen or an incorrect result returned. We have to carrefully consider the flags during each of the steps of the new methods: $\circ(\frac{1}{y})$, $a_0$, $e_1$, $a_1$ as those stucking flags will be accumulated during the computation.
    The case (A) requires specific attention. An overflow in $\frac{1}{y}$ can occur (with or without the IEEE overflow flags) while $\frac{x}{y}$ remains in the range of normal numbers. For example if $y=2^{-149}$ (the smallest subnormal numbers) and $x = 2^{-126}$ (the smallest normal number). $\circ_{RN}(\frac{1}{y}) = +\infty $ but  $\circ_{RN}(\frac{x}{y}) = 2^{23}$.

To avoid intermediary overflows or underflows or spurious exceptions we modify the method as follows:

$ y', s = normalize(y) $ (silent)
$ x', t = normalize(x) $ (silent)
$ r = \circ(\frac{1}{y'})$ (silent)
$ - = fsdiv(x, y) $ (result discarded)
$ a_0 = \circ(x' \times r)$
$ e_1 = x' - y' \times a_0 $ (silent)
$ a_1 = a_0 + e_1 \times r $ (silent)
$ n, m = 1, 1 \ if ' x' > y' \ else \ 0.5, 2 $
$ R = (a_1 \times m) \times (n \times s \times t) $

Each operation annoted with (silent) does not raise IEEE754 flags nor exceptions. For a number $y$ The normalize function returns the floating-point mantissa $y'$ of $y$ and the reciprocal $s$ of the scaling factor such that $y'$ and $s$ are floating-point numbers $y' = s \times y$, $1 \le y' < 2$, and $s$ is a power of 2.
If $y=\pm 0$, $y = \pm \infty$ or $y=NaN$ then $normalize(y) = y, 1.0 $.
normalize does not raise any IEEE754 flags.

Assuming $x$ and $y$ are non-zero numbers , $1 \le |x'| < 2$ and $1 \le |y'| < 2$ which means  $ \frac{1}{2} \le | \frac{x'}{y'} | \le 2 $.

The floating-point seed for division function, $fsdiv(x, y)$,  is a specific instruction which raises all the specific cases IEEE flags as if it computed $\circ(\frac{x}{y})$ but does not compute the actual result (nor detect and raise flags for numerical overflow nor underflow). It can been implemented easily and cheaply (and already exists in some architectures).

Managing overflow and underflow

We must ensure that no loss of precision occurs during the intermediate iteration while the final result is correctly rounded (even if subnormal). 
To implement this we rely on normalized inputs $x'$ and $y'$ and on normalization factors $s$ and $t$. 
$s = 2^p$ and $t = 2^q$, $e_{min} \le p, q \le e_{max} $ (e.g $ e_{min} = -149$, $e_{max}=127$ for $fp_{32}$ format)
While $a_1$ remains within $[0.5, 2]$, $s \times t$ may overflow or underflow.
Thus we introduced the extra scaling factors $n$ and $m$ which brings $a_1 \times m \ in \ [1, 2[$
and if $f = n \times m \times t$ overflows then $R$ overflows, and reciprocaly if it less than the the smallest normal numbers and $a_1$ is inexact then $R$ underflows.


The following code, based on pythonsollya, implements a quick and dirty test to check the method error.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# -*- coding: utf-8 -*-
import sollya
import random

sollya.settings.display = sollya.hexadecimal

def sollya_fma(x, y, z, precision, rndmode=sollya.RN):
    """ implement fused multiply and add using sollya.
        The internal precision must be enough so that x * y
        is computed exactly """
    return sollya.round(x * y + z, precision, rndmode)

def sollya_mul(x, y, precision, rndmode=sollya.RN):
    """ implement multiply using sollya """
    return sollya.round(x * y, precision, rndmode)

def iteration(x, y, approx_div, approx_recp, precision=sollya.binary64):
    """ implementation of refinement iteration """
    e_n = sollya_fma(y, -approx_div, x, precision)
    r_n = sollya_fma(approx_recp, e_n, approx_div, precision)
    return r_n


def my_division(x, y, precision=sollya.binary64, rndmode=sollya.RN):
    """ full division routine """
    approx_recp = sollya.round(1.0 / y, precision, rndmode)
    approx_div = sollya_mul(approx_recp, x, precision)
    NUM_ITER = 1
    for i in range(NUM_ITER):
        approx_div = iteration(x, y, approx_div, approx_recp, precision)
    return approx_div


NUM_TEST = 1000000
PRECISION = sollya.binary64
RNDMODE = sollya.RN
num_error = 0

for i in range(NUM_TEST):
    input_value_x = sollya.round(random.random(), PRECISION, RNDMODE)
    input_value_y = sollya.round(random.random(), PRECISION, RNDMODE)
    expected = sollya.round(input_value_x / input_value_y, PRECISION, RNDMODE)
    result = my_division(input_value_x, input_value_y, PRECISION, RNDMODE)
    error = abs(expected - result)
    rel_error = abs(error / expected)
    if error != 0.0:
        num_error += 1
        print("{} / {} = {}, result is {}, error = {}".format(input_value_x, input_value_y, expected, result, error))


print("{} error(s) encountered".format(num_error))

References:

  • [1] Newton-Raphson Algorithms for Floating-Point Division Using an FMA, JM Muller et al. (pdf)
  • [2] Proving the IEEE Correctness of Iterative Floating-Point Square Root, Divide, and Remainder Algorithms, M Cornea-Hassegan (pdf)
  • [3] pythonsollya's gitlab (Python wrapper for the Sollya library

Accelerating Erasure Coding with Bit Matrix Multiply


Introduction

In this article we will present a method for fast implementation of Erasure Coding primitives based on Galois Field arithmetic. The Jerasure library manual (pdf) is a good introduction to the erasure coding method considered in this article.

    For or purpose it suffices to say we have $r$ partial stripes. Each striple is constituted by $k$ $w$-bit data words and we want to compute the $m$ $w$-bit parity words to complete each striple.

Each parity word is computed as a dot-product of the data striple multiplied by a constant vectors whose values depends on the index of the parity word we want to compute (those index ranges from
$0$ to $m-1$). The vector elements and dot-product results are in $GF(2^w)$.

Multiplication by a constant through vector by matrix multiplication


We will consider the primitive operation on a $w$-bit data word $D$, which must be multiplied by a $w$-bit constant $C$ to get a $w$-bit parity word $P$: $D \times C = P$

The methode, due to Mastrovito (detailed in [5]), expands a multiplication by a constant and the modulo reduction into a multiplication by a bit matrix as follows:
$$
\begin{pmatrix}
data word
\end{pmatrix}
\times
\begin{pmatrix}
C . X^0 [g] \\
C . X^1 [g] \\
\vdots \\
C . X^{k-1} [g]
\end{pmatrix}
=
\begin{pmatrix}
parity word
\end{pmatrix}
$$

The multiplicand matrix is built by expanding $C . X^i [g] $ for each row $i$ of the matrix.
The matrix multiplication will accumulate the multiplication $d_i \times X^i [g] $ to build the final result:
\begin{align}
 P & = D \times C [g] \\
    & = (\oplus_{i=0}^{w-1} d_i \times X^i) \times C  [g] \\
    & = (\oplus_{i=0}^{w-1} d_i . C \times X^i)   [g] \\
    & = \oplus_{i=0}^{w-1} d_i . (C \times X^i [g] )
\end{align}

\begin{align*}
  \times &

\begin{pmatrix}
c_{0,0} & c_{0,1} & \dots & c_{0,w-1} \\
c_{1,0} & c_{1,1} & \dots & c_{1,w-1} \\
\vdots    &               &          &  \vdots \\
c_{k-1,0} & c_{k-1,1} & \dots & c_{w-1,w-1} \\
\end{pmatrix} \\
\begin{pmatrix} d_0 & d_1 & \dots & d_{w-1}  \end{pmatrix}   &
\begin{pmatrix}
p_0 & p_1 & \dots & p_{w-1}
\end{pmatrix}

\end{align*}

Multiplication parallelization

We have seen how to use vector by matrix multiplication to compute the multiplication of a data word to get part of a parity word (we still have to accumulate those parts to get the actual parity word). We can extend the left hand side vector to a full matrix and implements multiple multiplications by the same expanded constant C, realizing a Single Instruction Multiple Data (SIMD) operation.
With a single operation implementing a multiplication between $w \times w$ matrices we implement $w$ parallel multiplication by a constant C.

Erasure code operation


The macro operation for to compute erasure coding parity is a matrix multiplication where the right hand side matrix element are constant in $GF(2^w)$. This operation is decomposed into vector by constant vector multiplications, themselves decomposed into multiplication by constants and $xor$ accumulations.

References:

  • [1] Screaming Fast Galois Field Arithmetic Using Intel SIMD Instructions (pdf)
  • [2] Improving the coding speed of erasure codes with polynomial ring transforms (pdf)
  • [3] A New Architecture for a Parallel Finite Field Multiplier with Low Complexity Based on Composite Fields, C. Paar (pdf)
  • [4] Jerasure library manual (pdf)
  • [5] Fast and Secure Finite Field Multipliers (pdf) by Pamula and Tisserand (contains description of Mastrovito's method)

Computing floating-point approximation of log(1+x)

log1p / log1pf standard functions are parts in any math library. Those functions respectively computes a double precision and a single precision approximation of the $log(1+x)$ of their double precision (resp. single precision) input $x$. Such functions exist in standard C library, also in c++ ([1]), python ([2]) and numerous other bindings.

log1p is similar (in essence) to expm1: it offers a more accurate implementation of a composition of logarithm (resp. exponential) and a linear function regularly used together. But it exhibits new challenges: the implementation must be very accurate around 0 to ensure expected numerical properties (e.g. faithful rounding).

Let us first study the possible approximation on two easy intervals before considering a more generic approximation.

Approximation log(1+x) around 0

In an interval close to 0, let us say $[-\epsilon, \epsilon]$, $log(1+x)$ can be accurately approximated by a small polynomial. This polynomial can be obtain with Remez algorithm for example.


Using the following piece of code (relying on pythonsollya), we can see that only a degree 6 polynomial is required to get an approximation of $\frac{log(1+x)}{x}$ accurate to 53 bits over $[-2^{-7}, 2^{-7}]$. $\frac{log(1+x)}{x}$ is targeted rather than $log(1+x)$ because the later is very close to $x$ around 0, thus dividing by $x$ allows to search for a polynomial with a first non-zero coefficient.


1
2
3
>>> import sollya
>>> sollya.guessdegree(sollya.log1p(sollya.x)/sollya.x, sollya.Interval(-2**-7, 2**-7), 2**-53)
[6;6]

Approximation log(1+x) for values lesser than -0.5

On $]-1, -0.5]$, it is easy to see (using Sterbeinz's lemma) that $1+x$ is exact, thus one can fallback to $\circ(log(\circ(1+x)))$ to implement log1p(x) accurately, as $\circ(1+x) = 1+x$.

Generic appoximation

The floating-point addition $\circ(1+x)$ is not exact for most value of $x$. In fact for x between 2 and  $2^{p-1}$ it is only inaccurate for a few p-bit values matching the pattern $2^k - 1 + r$, with $ 2 \le k < p -1 $, $r < 1$. 

But we will discard this fact and focus on a more generic approximation, using the floating-point decomposition of $x$. We will assume the availability of a fast reciprocal operation $fast\_rcp$ which returns an approximation of the reciprocal accurate to $f$ bits. We will use the standard decomposition $x = m .2^{-e}$, where $m$ is $x$'s (signed) mantissa and $e$ is its exponent.

$1+x = 1 + m .2^e = 2^e . 2^{-e} + m .2^{-e} = 2^e \times (m + 2^{-e})$ (1)

$ log(1+x) = e \times log(2) + log (m + 2^{-e}) $ (2)

Let us rename $t = m + 2^{-e}$ and let us consider $r = fast\_rcp(t)$, which means $r \sim \frac{1}{m + 2^{-e}} $

$m + 2^{-e} = \frac{(m + 2^{-e}) \times r}{r}$ (3)
$ log (m + 2^{-e}) = log(t.r) - log(r) $ (4)

$ r \times 2^{-e}$ is exact, because it is a multiplication by a power of $2$.

$ log(1+x) = e \times log(2) + log(t.r) - log(r) $  (5)

We would like to tabulate $-log(r)$, as well as $log(2)$ and $log(t.r)$ can be approximated by a polynomial.
As $r \sim \frac{1}{t}$ then $r.t$ is close to one, with an error corresponding to the error of the approximation $t$: $ | 1 - r.t |  < 2^{-f} $

However as $ -e $ can become big, $t$ range is quite large. It is not possible to limit the table for $r$ to a few hundreds entries. We suggest to rewrite the equation as follows:

$t = m_t \times 2^{e_t}$, $r' = fast\_rcp(m_t)$ (6)
$ log(t) = log(m_t \times 2^{e_t} \times \frac{r'}{r'}) $ (7)
$ log(t) = e_t \times log(2) + log(m_t \times r') - log(r')  $ (8)

With $m_t \times r' \sim 1$ and $m_t \in [1, 2[$ we get $r' \in [0.5, 1]$. $r'$ values corresponds to a few discrete values accross $[0.5, 1]$ determined by the implementation of $fast\_rcp$.
As those values can be known statically it becomes possible to tabulate $log(r')$ easily. For example if  $fast\_rcp$ provides an approximation based on the first $f$ bits of mantissa of $m_t$, the table will have $2^f$ entries and can be accessed by looking as the first $f$ bits of $m_t$'s mantissa (as the mapping between those bits and $r'$ is deterministic, it is easy to tabulate $log(r')$ for every possible value of $r'$. This is the example for example on Intel's IA architecture, where $f=12$.

   
Once the different terms of Equations (8) are obtained we can reconstruct the final $log(t)$. We have to be careful with catastrophic cancellation cases which are the big caveats to look for in floating-point approximations. In our current implementation, this is solved by using multi-word arithmetic for the final operations, before an eventual rounding to the final precision. But this could be further optimized.

Source code

The meta implementation of this method can be found here in the metalibm code generator function library. Below, two command line examples to generate single and double precision source code:


1
2
>>> python3 metalibm_functions/ml_log1p.py --precision binary32 
>>> python3 metalibm_functions/ml_log1p.py --precision binary64 


Conclusion

This method has nothing new, but listing the details here can be useful to understand how a floating-point approximation can be built. Comments are welcome.

Thank you to hugo B. for proof reading this article. This article initially published on March 19th 2019 was updated on March 30th 2019 (with typo fixes and clarifications).

References:
     The use of  fast_rcp for logarithm implementation was first described in P. Markstein's book IA-64 and Elementary Functions: Speed and Precision.

Docker Power

Docker hype is gone, long live docker ...

   Some coworker of mine just told me that when I explained to him I had just re-discovered the existence of docker. To be clear, I think I missed the train on that one by a long shot. Docker has been around and growing for a few years now since its initial public release in March 2013.

What is docker ?

  Let us start by what it is not. It not a virtual machine system (not per say), it is not a lazy way for lazy developper to package a simple application and its dependencies (or is it ?), it is a lightweight, elegant (does it matter ?) and interesting way to package / contain (in the sense separate, restrain and control) and deploy / distribute applications. The word application should be understood as some large runtime with very specific interactions and dependencies that you wish to deploy on a certain numbers of servers. I do not think it should be use to package basic application with very basic dependencies that are supposed to run once.

Ressource for using Docker on Ubuntu

A simple tutotial can be found here: https://docs.docker.com/install/linux/docker-ce/ubuntu/. The next stop will be reading about Dockerfile (kind of Makefile for a docker image) https://docs.docker.com/engine/reference/builder/#usage . Finally, you should have a okk at docker's hub (https://hub.docker.com/) which stores and indexes pre-built docker images. You can browse through it to look for the basis for your own image.

My day to day use of docker

I now use docker on a daily basis for metalibm (https://github.com/kalray/metalibm) and pythonsollya (https://gitlab.com/metalibm-dev/pythonsollya) integration. Those two tools have a lot of dependencies which do not exist under standard linux packages. It was very easy to build a docker container, use gitlab registry and fire it up every time I want to validate change.

References

  1. Docker official website https://www.docker.com/
  2. Wikipedia's page (always interesting) https://en.wikipedia.org/wiki/Docker_(software)
  3. Docker's hub (registry for docker images): https://hub.docker.com/


Determining numerical bound for floating-point reciprocal overflow


This article is both an example of pythonsollya use and a small study of floating-point property of the reciprocal function.

Context and objectives

Our goal is to find a bound $b$ such that when computing $\frac{1}{x}$ for any $x \lt b $  an IEEE overflow exception is raised (and the value corresponding to an overflow in the given rounding mode is returned). $b^*$ designs the exact value and $b$ the floating-point number of precision $p$ which verify the above property. Let us note that $b$ is not necessarily equal to $b^*$ rounded in a pre-defined rounding mode.

We will use the following notations: $\circ$ is the rounding operation which can be specialized by a rounding mode, $RNE$ is rounding to nearest (tie-to-even), $RU$ is rounding up (to $+\infty$) and $RD$ is rounding downward (to $-\infty)$, $next(x)$ is the minimal floating-point number strictly greater than $x$ and $prev(x)$ is the minimal floating-point number strictly lower than $x$.

Let us first define an overflow bound:

Defintion 1: $b$, (respectively $b^*$) is defined as the floating-point (resp. exact) overflow bound of the reciprocal function if it is the minimal floating-point (resp. real) number which verifies: $ \circ(\frac{1}{b})$ does not overflow.

Section 7.4.0 of the IEEE-754 standard defines overflow as: "The overflow exception shall be signaled if and only if the destination format’s largest finite number is exceeded in magnitude by what would have been the rounded floating-point result (...) were the exponent range unbounded. " This means that $b$ shoud depend on the rounding mode.

Let us first consider $b_{RNE}$ the bound associated to the round to nearest (tie-to-even) mode and defined as the maximal $x$ such that: $ \frac{1}{x} \ge B_{RNE} = \omega + \frac{1}{2} ulp(\omega) $.
$B_{RNE}$ is the mid-point between the largest floating-point number $\omega$ and the first value defined with an unbounded exponent range which will be rounded to $+\infty$ as per Section 7.4.
Note that the inequality does not need to be strict since $\omega$ has an odd mantissa ($1 ... 1$) if $\frac{1}{x} = B_{RNE}$ it should be rounded to a value greater than $\omega$ because of the tie-to-even rule, thus it will overflow.

$p$ is the format precision (e.g. $p=24$ for binary32 format).
$ b^*_{RNE} = \frac{1}{B_{RNE}} = \frac{1}{\omega + 2^{emax - p}} $
$ b^*_{RU} = \frac{1}{B_{RU}} = \frac{1}{\omega} $

Evaluating bound values

Let us compute (approximation to) $b^*_{RNE}$ and $b^*_{RU}$ using pythonsollya:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# importing pythonsollya
>>> import sollya
# forcing display setting to hexadecimal value
# (easier to read)
>>> sollya.settings.display = hexadecimal
# building 2.0 as a Sollya Object
>>> S2 = sollya.SollyaObject(2)
# building binary32's omega value (greatest floating-point number)
>>> omega = S2**127 * (S2**24 - 1) * S2**-23
>>> omega
0x1.fffffep127
>>> B_RU = omega
# omega + 1/2 ulp(omega)
>>> B_RNE = omega  + S2**103
# rounding 1 / B_RU with 100-bit precision
>>> sollya.round(1 / B_RU, 100, sollya.RD)
0x1.000001000001000001000001p-128
# rounding B_RNE to 100-bit precision
>>> sollya.round(1 / B_RNE, 100, sollya.RD)
0x1.0000008000004000002p-128
# rounding 1 / B_RNE to binary32 precision
>>> sollya.round(1 / B_RNE, sollya.binary32, sollya.RN)
0x1p-128
# rounding 1 / B_RU to binary32 precision
>>> sollya.round(1 / B_RU, sollya.binary32, sollya.RN)
0x1p-128

The first interestings values are given lines 16 and 19: correctly rounded approximation on 100 bits of $b_{RU} = \frac{1}{B_{RU}}$ and $b_{RNE} = \frac{1}{B_{RNE}}$. Let us first notice that those two values are greater than they counterpart rounded to binary32 (visible on lines 22 and 25).
Let us call $c_- = 2^{-128}$ and $c_+ = next(c_-) = 2^{-128} + 2^{-149} $ , we have $c_- \lt b_{RU} \lt c_+ $ and $c_- \lt b_{RNE}\lt c_+ $ which means $ \frac{1}{c_-} \gt \frac{1}{b_{RNE}} \gt \frac{1}{c_+} $ and $ \frac{1}{c_-} \gt \frac{1}{b_{RU}} \gt \frac{1}{c_+} $ and $\forall x , x \le c, \frac{1}{x} \gt \frac{1}{b_{RNE}}$

Claim 1: $ \forall mode, \circ_{mode}(\frac{1}{c_+}) $ is a normal value.
Claim 2: $ \forall mode, \circ_{mode}(\frac{1}{c_-}) $ overflows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>> c_minus = S2**-128
>>> c_plus = S2**-128 + S2**-149
>>> c_minus
0x1p-128
>>> c_plus
0x1.000008p-128
>>> for rnd_mode in [sollya.RN, sollya.RU, sollya.RD, sollya.RZ]:
...     print sollya.round(1 / c_minus, sollya.binary32, rnd_mode), sollya.round(1 / c_plus, sollya.binary32, rnd_mode)
infty 0x1.fffffp127
infty 0x1.fffff2p127
0x1.fffffep127 0x1.fffffp127
0x1.fffffep127 0x1.fffffp127

$c_+ $ is greater than both $b^*_{RU}$ and $b^*_{RNE}$, and $c_-$ is lower (strictly) than both of them. Thus, as there is no floating point number between $c_-$ and $c_+$ we can chose $c_-$ as the overflow bound (included) or $c_+$(excluded). There is no need to distinguish several values of $c$ based on the rounding mode. $\forall \ x \ge c_+$, $\frac{1}{x}$ will not overflow and $\forall \ x \le c_-$, $\frac{1}{x}$ will overflow in any rounding mode.

Let us now consider the remaining rounding modes (rounding down and towards zero). Without loss of generality we can reduce the study to positive overflow and thus to a single rounding mode, let us say rounding down.
$B_{RD} = next(\omega) = 2^{128} $ (assuming unbounded exponent range)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> B_RD = S2**128
>>> b_RD = S2**-128
>>> sollya.round(1 / B_RD, sollya.binary32, sollya.RD)
0x1p-128
>>> sollya.round(1 / B_RD, sollya.binary32, sollya.RU)
0x1p-128
>>> sollya.round(1 / c_minus, sollya.binary32, sollya.RD)
0x1.fffffep127
>>> sollya.round(1 / c_plus, sollya.binary32, sollya.RD)
0x1.fffffp127

$\frac{1}{B_{RD}}$ is exactly equal to the floating point number $c_-$ and $\frac{1}{c_-}$ will indeed lead to an overflow even in rounded down mode. This overflow will return the $\omega$ value as output.
$\circ_{RD}(\frac{1}{c_+}) = \omega - 2^{104} - 2^{105} - 2^{106} $ which is a normal number strictly lower than omega. Thus we get the same behavior as for $RNE$ and $RU$: $c_-$ is the overflow bound (included) and $c_+$ is the excluded overflow bound $\square$.

References:

  • Sollya: a tool for manipulating arbitrary precision numbers, computing approximation polynomial ... (Sollya's website)
  • pythonsollya: a python wrapper for sollya (pythonsollya's gitlab)
  • Quick introduction to IEEE-754 floating-point arithmetic standard (web)


On the quality of random

Warning: This article is still an ongoing work (updated Jul 6th 2018)

How to measure random quality ?

I came accross a technical and theoretical challenge recently: how to evaluate the quality of a True Random Number Generator (TRNG) ? (Hopefully others had the same question, see stackoverflow and references at the end of this article).
    A TRNG can have many implementation, in my case it was a small piece of a computer chip built around free running oscillators (FROs) which has been integrated to provide random numbers. As the source of entropy (namly quantum effects and what not, but do not ask me I am not an expert) is not deterministic such a generator is called "True" compared to DRBG / DRNG: deterministic random number generators which look like random but are in fact fully deterministic. Basically if you know their inner state (which is much more reduced that the quantom states of a few transistors) you know the next value that will come out, and the one after that and so on and so fourth.

    I had not reason not to trust the IP vendor which provided the TRNG, nor the front end engineer which integrated it in our SoC nor the backend engineer which P&R the IP and check DRC enforcement. But still it would have been good to verify at the end that the number coming from the IP where "truly" random. But How do you do that ?

How to test random number generator "quality" ?

   The NIST has an answer: a series of tests (aimed at DRBG: linklink) which ensure with a certain level of certainty that some bits are random, i.e. the outcome of the generation could not have been predicted. That the thing with RNG generator, you can only evaluate randomness afterward once the generation is done, for example by computing statistics on the generated number and check if they "look" random enough. Some tests are pretty simple some are more complex but they are not perfect.

By definition strong encryption / hash should be indistinguishable from pure random, also it is likely to be predicatble, as said before if ones know the internal state of the generator and the generation algorithm (see differences between PRNG and TRNG).

No test is absolute: the only thing a test can show is that a source is definitely not random but one can never be sure a source is truly random.

Testing /dev/urandom using dieharder:

cat /dev/urandom | dieharder -a -g 200

And the first lines of the output (on my laptop):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#=============================================================================#
#            dieharder version 3.31.1 Copyright 2003 Robert G. Brown          #
#=============================================================================#
   rng_name    |rands/second|   Seed   |
stdin_input_raw|  2.86e+07  |1906170125|
#=============================================================================#
        test_name   |ntup| tsamples |psamples|  p-value |Assessment
#=============================================================================#
   diehard_birthdays|   0|       100|     100|0.04063257|  PASSED  
      diehard_operm5|   0|   1000000|     100|0.39003783|  PASSED  
  diehard_rank_32x32|   0|     40000|     100|0.29253977|  PASSED  
    diehard_rank_6x8|   0|    100000|     100|0.54463760|  PASSED  
   diehard_bitstream|   0|   2097152|     100|0.08717072|  PASSED  
        diehard_opso|   0|   2097152|     100|0.17956788|  PASSED  
        diehard_oqso|   0|   2097152|     100|0.23600778|  PASSED  
         diehard_dna|   0|   2097152|     100|0.56596361|  PASSED  
diehard_count_1s_str|   0|    256000|     100|0.11492636|  PASSED  
diehard_count_1s_byt|   0|    256000|     100|0.38479922|  PASSED  
 diehard_parking_lot|   0|     12000|     100|0.12488862|  PASSED  
    diehard_2dsphere|   2|      8000|     100|0.05409247|  PASSED  
    diehard_3dsphere|   3|      4000|     100|0.48664732|  PASSED  
     diehard_squeeze|   0|    100000|     100|0.99872432|   WEAK  

Notice the "Weak" line 22 ?
Post on reddit about interpreting diehard results: link

Differences between PRNG and TRNG ? 

(unpredictability)

What is an entropy pool ?

How fast can you generate Random Numbers ?

Intel entropy source is around 3Gbs (to be refined by conditionner)

Can good entropy sources be expected to fail some ENT / Dieharder tests ?

Indeed, as indicated in this stackoverflow responses: Perform ENT / Dieharder and raw entropy or on whitened entropy ?

Certifications for Random Number Generators:

NIST SP800-90
A. Recommendation for RNG using DRBG (NIST webpage)
B. Recommendation for Entropy Source for RBG (NIST webpage)
C. Recommendation for RBG Construction (NIST Draft)

References:

New instruction for Fast GCM multiplication


New Operation/Instruction for Fast GCM multiplication


In this article we will study how we could compute GCM multiplication with only 3 instructions (and two exclusive 64-bit or) by extending a very interesting white paper by Intel:  Intel ® Carry-Less Multiplication Instruction and its Usage for computing the GCM Mode ([4]).
We will first introduce GCM, then survey the basic of the carry less multiplication and the Karatsuba algorithm for multiplication before detailing the modulo reduction method of [4]. Then we will introduce our new operation and evaluate its hardware implementation.

Galois Counter Mode

Galois-Counter Mode or GCM ([1]) is a block cipher mode of operation which uses a multiplication in a finite field (Galois Field or $GF$) as basis for the authentication procedure. The Galois Field used is $ GF(2)[X] \!/\!_{X^{128} + X^7 + X ^2 + 1}$. The fact that the irreducible polynomial used to build the field has a very low number of non-zero terms is very useful to implement a fast modulo reduction.

Carry Less Multiplication

In the following most binary values encode polynomial over $GF(2)[X]$. For example a 64-bit value $A$ encodes $A=\sum_{i=0}^{63}a_i . X^i$ where a_i is the i-th bit of A (and a coefficient in $GF(2)$). The addition of two polynomials $A$ and $B$ is simply their binary xor: $A + B = \sum_{i=0}^{63}(a_i + b_i \mod 2) = \sum_{i=0}^{63}(a_i \oplus b_i ) = A \oplus B $.
A multiplication of polynomials over $GF(2)[X] is called a Carry Less Multiplication because there are no carry propagation between monomial term of distinct degrees.
The following listing shows a possible implementation of a Carry-Less multiplication of two 64-bit polynomials, giving a 128-bit result. As we operate on polynomials, the accumulate operation is a XOR which contrary to an addition does not propagate a carry.

__int128 poly_mul_64x64_128(uint64_t a, uint64_t b)
{
   int i;
   __int128 result = 0;
   for (i = 0; i < 64; ++i) {
       // result +=  b . X^i if a_i is non-zero
       result ^= ((a >> i) & 1) ? (b << i) : 0;
   }

}

Recently, in x86 architecture,  the carry-less multiplication operation is implemented by a specific instruction: PCLMULQDQ ([2]).

Assuming the availability of a $64-bit \times 64-bit \rightarrow 128-bit $ carry-less operation, a full GCM reduction can be performed by first computing the full $128 \times 128$ CLM using 4 $64 \times 64$ multiply operations and then performing a $mod G$ reduction on the $128$ most significant bits of the multiplication result.

Fast GCM Multiplication

A very interesting white paper by intel [4] sums up a lot of good ideas on how to implement a fast GCM multiplication.
   The proposal is two-fold:
  • Use Karatsuba algorithm [5] to accelerate the multiplication
  • Use the structure of the field to accelerate the reduction part 
This white paper contains other interesting idea (folding ...) that will not be detailed in this article.

Karatsuba multiplication

We wish to compute $M = A \times B $, where $A$ and $B$ are 128-bit polynomial (degree 127 in $GF(2)[X]$). Thus M is at of degree 255. Let us split A and B into high and low part:
$$ A = A_{hi}. X^{64} + A_{lo}, B = B_{hi}. X^{64} + B_{lo}$$
$$ M = A_{hi} \times B_{hi} . X^{128} +  (A_{hi} \times B_{lo} + A_{lo} \times B_{hi}) . X^{64} + A_{lo} \times B_{lo} $$
Assuming $ C =  A_{hi} \times B_{hi}$, and $ D = A_{lo} \times B_{lo}$ and $E = (A_{hi} + A_{lo}) $ and $F = B_ {hi} + B_{lo}$,   $ (A_{hi} \times B_{lo} + A_{lo} \times B_{hi}) $ can be writen as $$  (A_{hi} \times B_{lo} + A_{lo} \times B_{hi}) = E \times F - C - D $$.
As we are working in $GF(2)[X]$, the operation $+$ is equivalent to $-$: both are binary $xor$.

We went from using 4 half multiplications in the standard algorithm to only 3 with Karatsuba (while appending a few additions). Additions are considered cheap compared to multiplications (and they generally are) so it should not be an issue.

Fast Reduction

[4] provides the following  algorithm (Algorithm 3) to compute, $R=M_{hi} \times X^t (\mod G)$, assuming an input $M_{hi}$ and $G$ an irreducible polynomial of degree $t$, $s=max(degree( M_{hi}))+1$ (for GCM, $s=128$):

  1. Preprocessing: we compute $g$ and $q$, $g$ is the polynomial built by the $t-1$ least coefficient of $G$ and $q$ is the quotient of the division of $X^{t+s}$ by $G$ where $s$ is the degree of the quotient of the division of $X^t \times M$ by $G$
  2. Compute $A = M_{hi} \times q$, 
  3. Multiply the $s$ most significant terms of $A$ by $g$
  4. Output the $t$ least significant terms of the Step 3.
Let us apply this algorithm to the case of GCM: $G = X^{128} + X^7 + X^2 + X + 1$, $g=X^7 + X^2 + X + 1$. We want to compute $M = A \times B $, where $A$ and $B$ are polynomials of degree 127. 
Let us first compute $q$ such that $X^{256}=q \times G + r $ with $degree(r) < degree(G)$, it is easy to verify that $X^{256}=G^2 \oplus x^{14}+x^4+x^2+1 $, thus $q = G$. 

We split M in 4 part of 64 term each: from most significant to least signifiant $M_3, M_2, M_1, M_0$, $M_{hi} =[ M_3 : M_2] = M_3 . X^{64} \oplus M_2 $.

$$ M [G] = M_1 . X^{64} \oplus M_0 \oplus ((M_3 X^{64} \oplus M_2) . X^{128} [G]) $$

To get $((M_3 X^{64} \oplus M_2) . X^{128} [G])$, we use the algorithm from [4]:
$$ A =  (M_3 X^{64} \oplus M_2) \times q $$
As we only need the 128 most significan terms of $A$, $A_{hi}$ we can compute them as follows:

\begin{equation}
\begin{split}
A_{hi} & = [M_3 : M_2] \oplus [M_3 : M_2 ] >> (128 - 7) \oplus [M_3 : M_2 ] >> (128 - 2) \\
             &\oplus [M_3 : M_2 ] >> (128 - 1) \oplus  [M_3 : M_2 ] >> (128 - 0)   \\
             & = [M_3 : M_2] \oplus [0 : M_3 ] >> 57 \oplus [0 : M_3 ] >> 62 \oplus [0 : M_3] >> 63 \\
      & = [M_3 : (M_2 \oplus M_3 >> 57 \oplus M_3 >> 62 \oplus M_3 >> 63)]
\end{split}
\end{equation}

We now compute $R_{lo}$ from $ R = A_{hi} \times g $
\begin{equation}
\begin{split}
R &= A_{hi} \times g \\
 &=  A_{hi} \times (X^7 + X ^2 + X + 1) \\
&= [A_{hi,1} : A_{hi,0}] << 7 \oplus [A_{hi,1} : A_{hi,0}] << 2 \oplus [A_{hi,1} : A_{hi,0}] << 1 \oplus [A_{hi,1} : A_{hi,0}]
\end{split}
\end{equation}

Computing $A_{hi}$ requires 3 64-bit right shifts and 3 64-bit exclusive-or operations.
Computing $R$ from $A_{hi}$ is a little more expensive: it requires 3 128-bit shifts and 3 128-bit exclusive or operations. Overall a hardware implementation of this reduction will require around 576 standard xor gates (static shifts being free).

Merging operation for GCM multiplication

We are now going to merge the operations described above and the Karatsuba algorithm. The idea is to suggest a "generic" operation that could be implemented as processor instruction to accelerate GCM multiplication with more efficiency than the expanded version described in [4].
  1. Compute $O_1 = (A_{lo} \times B_{lo} \oplus (A_{lo} \times B_{lo} . X^{64})) [G] $
  2. Compute $O_2 = (A_{hi} \times B_{hi} . X^{128} \oplus (A_{hi} \times B_{hi} . X^{64})) [G] \oplus O_1 $
  3. Compute $(O_3 = ((A_{hi} \oplus A_{lo}) \times (B_{hi} \oplus B_{lo}) . X^{64}) [G] \oplus O_2 $
  4. Output the final result $O_3 $ 
Each step consists in:
  • One $64-bit \times 64-bit$ carry-less multiplication
  • One 128-bit modulo $G$ reduction
  • A few exclusive-or operations
The product operands for the third step ($O_3$) may be computed in parallel to step 1 and 2, they consist in two 64-bit exclusive or operations.
We can factorize the previous steps into a new primitive: GCMRED.

$GCMRED(A, B, \alpha, \beta, C)$, with $A$ and $B$ 64-bit operand, $C$ a 128-bit operand and $\alpha, \beta$ coefficients in $\{0, 1, X^{64}, X^{128}\}$  implements the following operations:
$$ GCMRED(A, B, \alpha, \beta, C) = (A \times B) . (\alpha + \beta)  [G] \oplus C $$
We can then compact a full GCM multiplication into 3 consecutives application of $GCMRED$:

\begin{equation*}
\begin{split}
 GCM(A_{full}, B_{full}) &= GCMRED(A_{hi} \oplus A_{lo}, B_{hi} \oplus B_{lo}, X^{64}, 0, \\
                                               & \ \ \  \ \ \  GCMRED(A_{hi} , B_{hi} , X^{64}, X^{128}, \\
                                               &  \ \ \ \ \ \ \ \ \      GCMRED(A_{lo} , B_{lo} , 1, X^{64}, 0 )))
\end{split}
\end{equation*}

As you can see we do not need the full range of $\alpha$ and $\beta$: only 3 variants are required, namely: $(X^{64}, 0)$, $(X^{64}, X^{128})$ and $(1, X^{64})$. Thus only those 3 may be encoded as possible instructions.

Complexity of GCMRED

A possible implementation of GCMRED is illustrated by the figure below. There are 4 64-bit inputs: $A$, $B$ ,$C_{hi}$ and $C_{lo}$ and 2 64-bit output corresponding to the high and low part of the output. 

The desisgn consists in :
  • A $64 \times 64$ Carry-Less Multiplication
  • A 128-bit GCM modulo reducer
  • 6 64-bit exclusive or operators
  • 4 64-bit and operators (to zeroify signals)
  • 2 64-bit 3-input multiplexers 
A naive CLM implementations would require around 4096 $and$ gates (with two 1-bit inputs and one 1-bit output) and about the same number of $xor$ gates (give or take a few). The GCM modulo reducer as described in a previous section requires about 576 $xor$ gates. 
   If we consider the silicon area of a xor gates is about twice those of a nand gate (generally used as reference for silicon design comparison),  the silicon area of an $and$ gate is $ \frac{3}{2}$ times a nand gate and the silicon area of a 3-input multiplexer (with one input to zero) to be 5 times a nan gate we can evaluate the overcost of GCMRED compare to a simple CLM multiplication to be roughly around $25\%$.
   This delta may be under estimated as the area to implement a carry less multiply can be further reduced from the naive implementation by using the Karatsuba algorithm recursively to obtain a more efficient design.

Endianess and GCM

In the AES version of Galois Counter Mode, input are assumed to be received in reverse order (least significant bit correspond to the term $X^{127}$ in a 128-bit value) which implies both inputs and outputs must be permuted before and after standard polynomial computations to ensure correct results. Such a permutation is not performed by GCMRED. The cost of supporting it may be limited to introducing a level of inputs multiplexers and a level of output multiplexers to select between standard I/Os and permuted ones (allowing only two permutations).

Conclusion

This small technical report suggests a small addition to the PCLMULQDQ design with which a 25% area increase will bring down the cost of computing a GCM multiplication to five operations (with a critical path length of 3). 

Future works include: synthesising the design and a standard CLM using a standard silicon technology node for better comparison (with critical path and latency analysis), comparing more accurately a full GCM implementation using the new GCMRED instruction operation with Intel's best implementation (including folding and factorized bit reversing).

Updated on January 3rd, 2018
Thank you to Julien LM and Hugues de LSG and Arnaud O for pointing many mistakes in the first versions.
Second update on August 27th, 2018, fixing typo in GCMRED description

References: