Exponentialfunktion zur Basis 2
u32p32exp2m1()

Geschrieben von am .

1. Einführung

Gesucht ist eine Funktion zur Berechnung einer Exponentialfunktion in Ganzzahl-Arithmetik.

Als Basis bietet statt der Eulerkonstante e der Wert 2 an, um mit der Exponentatialfunktion mit Basis 2 und Exponenten n ∊ ℕ₀ kompatibel zu sein, die trivial durch den Shift-Operator implementiert ist. Wir können deshalb den Argumentbereich auf 01 ohne die 1 beschränken, also auf das oben offene Intervall x ∊ [0…1[.

Wenn Eingabe, alle Zwischenergebnisse und das Ergebnis in diesem offenen Intervall liegen, können wir dieses auf den C-Datentyp uint32_t, abbilden, indem wir die Werte mit 232 multiplizieren.

Einem mathematischen Wert x entspricht dann der C-Wert x^ 232 x .

Leider liegt Wertebereich unserer Funktion 2x [ 20 21 [ = [12[ außerhalb des erwünschten Bereiches. Deshalb berechnen wir stattderer die Funktion f (x) = 2x -1 , deren Wertebereich f (x) [01[ genau unser gewünschtes Intervall ist.

2. Mathematik

Aufgabe ist die Berechnung von f (a) = 2a -1 für Eingaben a [01[ , wobei auch alle Zwischenergebnisse im Intervall [01[ liegen müssen.

2.1. Vorbereitung für die Reihenentwicklung

Um zur Berechnung eine Potenzreihe nutzen zu können, wechseln wir zur Basis e:

f(a) = 2a -1 = e a loge 2 -1

S loge 2

z aS

g (z) ez -1 = 2a -1 = f (a)

Die Konstante S= loge 2 0.693 liegt im erlaubten Bereich, und es gilt z< loge 2 .

Damit die Potenzreihe schnell konvergiert, zerlegen wir z:

z=y+x mit y {0,¼,½} und 0x<¼

und nutzen das Potenzgesetz der Addition:

g (z) = g ( y+x ) = e y+x -1 = ey ex -1

y kann nur die drei Werte 0, ¼ und ½ annehmen; deshalb können die konstanten Werte von e0, e¼ und e½ vorab berechnet und in einer Tabelle bereitgestellt werden.

Weil y und x nicht negativ sind, sind die Potenzen ey und x größer oder gleich 1 und liegen damit außerhalb des erlaubten Bereiches. Deshalb definieren wir:

g1 (y) ey -1 g2 (x) ex -1

Wie man leicht nachrechnen kann, liegen die Werte von g1(y) und g2(x) zwischen 0 und 1. Damit können wir g(z) innerhalb des erlaubten Zahlenbereiches berechnen:

g (z) = ey ex -1 = ( ey -1 +1) ( ex -1 +1) -1

g (z) = ( g1 (y) +1) ( g2 (x) +1) -1

g (z) = g1 (y) g2 (x) + g1 (y) + g2 (x)

Die Exponentialfunktion zur Basis e kann als Potenzreihe berechnet werden:

g2 (x) = ex -1 = n=0 xn n! -1 = n=1 xn n!

Dabei berücksichtigen wir aber nur die Terme bis zu einem Grad N:

g2 ~ (x) n=1 N xn n! n=1 xn n! = g2 (x)

2.2. Horner-Schema

Die Potenzreihe lässt sich nach dem Horner-Schema umformen, um Rechenoperationen zu sparen und Rundungsfehler zu minimieren. Wir benutzen im Beispiel N = 4:

n=1 4 xn n! = x+ x2 2 + x3 6 + x4 24

Wir klammern nacheinander x, x/2, x/3 usw. aus:

n=1 4 xn n! = x (1+ x2 + x2 6 + x3 24 )

n=1 4 xn n! = x (1+ x2 (1+ x3 + x2 12 ) )

n=1 4 xn n! = x (1+ x2 (1+ x3 (1+ x4 ) ) )

Leider liegen wegen der Additionen der 1 die Zwischenergebnisse außerhalb des uns erlaubten Bereiches. Deshalb nutzen wir ein modifiziertes Horner-Schema.

2.3. Modifiziertes Horner-Schema

n=1 4 xn n! = x+ x2 2 + x3 6 + x4 24

Wir klammern nacheinander x/2, x/3 usw. aus, lassen aber jeweils das führende x stehen:

n=1 4 xn n! = x+ x2 (x+ x2 3 + x3 12 )

n=1 4 xn n! = x+ x2 (x+ x3 (x+ x2 4 ) )

n=1 4 xn n! = x+ x2 (x+ x3 (x+ x4 (x) ) )

Damit ergibt sich diese Berechnungsfolge, bei der für x<loge2 alle Zwischenergebnisse hj kleiner als 1 sind:

h4 x

h3 x+ x4 h4 = h4 x 14 +x

h2 x+ x3 h3 = h3 x 13 +x

h1 x+ x2 h2 = h2 x 12 +x

2.4. Vollständiger Algorithmus

Einige Konstanten werden vorab berechnet:

S0 loge (2) =0.6931471805599453

C0 g1 (0) = e 04 -1 =0

C1 g1 (¼) = e 14 -1 =0.2840254167

C2 g1 (½) = e 12 -1 =0.6487212707

Berechnung von f(a):

z Sa

i 4z {0,1,2}

y i/4

x z-y

hx

h hx/7+x

h hx/6+x

h hx/5+x

h hx/4+x

h hx/3+x

h hx/2+x

f (a) Ci h+ Ci +h

3. Implementierung

Wir erinnern uns daran, dass die in C genutzte skalierte Zahl um den Faktor 232 größer ist als die wirkliche mathematische Zahl:

x^ = 232 x

Die Ganzzahl-Addition auf den skalierten Zahlen ist identisch zur mathematischen Addition:

a+b ^ = 232 (a+b) = 232 a + 232 b = a^ + b^

Die Ganzzahl-Multiplikation auf den skalierten Zahlen liefert ein Ergebnis, das um den Faktor 232 zu groß ist:

a·b ^ = 232 (a·b) = 232 a ·b = 232 a · 232 b 232 = a^ · b^ 232

Deshalb muss:

  1. eine 32-Bit-Multiplikation mit 64-Bit-Ergebnis genutzt werden, und
  2. das 64-Bit-Ergebnis durch 232 geteilt werden, was durch einen Rechts-Shift um 32 Bits realisiert werden kann.

Da es in C keine 32-Bit-Multiplikation mit 64-Bit-Ergebnis gibt, müssen wir eine 64-Bit-Multiplikation und einen 64-Bit-Shift um 32 Stellen codieren (u32mulhi). Zum Glück sind aktuelle Compiler schlau genug, aus der Sequenz von 64-Bit-Multiplikation und 64-Bit-Shift eine 32-Bit-Multiplikation zu generieren, deren oberes Ergebniswort genutzt wird.

//--------------------------------------------------------------------------------------
//	Auxiliary multiplication function with impicite division by 2^32
//--------------------------------------------------------------------------------------

static inline constexpr uint32_t
u32mulhi( const uint32_t a, const uint32_t b ){

	return (uint32_t)( ((uint64_t) a * b) >> 32 );
}

//--------------------------------------------------------------------------------------
//	Constants
//--------------------------------------------------------------------------------------

// 2**32 * log(2) - unfortunately log(const) is not a constexpr

static constexpr uint32_t LOG_2 = 0xb17217f7u;

// two top bits are handled by a lookup table,
// resulting in series expansion input to be less then 0.25

static constexpr unsigned LookupBits =  2;
static constexpr unsigned WordLength = 32;
static constexpr unsigned ShiftValue = WordLength - LookupBits;

// ( exp( 0/4 ) - 1 ) * 2**32 = 0x.0000.0000 | unfortunately exp() is not a constexpr

static constexpr uint32_t EXP_0_BY_4_MINUS_1 = 0;

// ( exp( 1/4 ) - 1 ) * 2**32 = 0x.48b5.e3c3 | unfortunately exp() is not a constexpr

static constexpr uint32_t EXP_1_BY_4_MINUS_1 = 0x48b5e3c3u;

// ( exp( 2/4 ) - 1 ) * 2**32 = 0x.a612.98e1 | unfortunately exp() is not a constexpr

static constexpr uint32_t EXP_2_BY_4_MINUS_1 = 0xa61298e1u;

// precomputed reciprocal values

static constexpr uint32_t ONE_BY_3 = (uint32_t)( ( 1ull << 32 ) / 3 );
static constexpr uint32_t ONE_BY_5 = (uint32_t)( ( 1ull << 32 ) / 5 );
static constexpr uint32_t ONE_BY_6 = (uint32_t)( ( 1ull << 32 ) / 6 );
static constexpr uint32_t ONE_BY_7 = (uint32_t)( ( 1ull << 32 ) / 7 );

//--------------------------------------------------------------------------------------
//	main function
//--------------------------------------------------------------------------------------
//	Integer computation of u32bexpm1(x) := 2**32  *  ( 2 ** (x/2**32) - 1 )
//--------------------------------------------------------------------------------------

uint32_t
u32p32exp2m1( uint32_t arg ){

	//------------------------------------------------------------------------------
	//	exp2( arg ) = exp( arg * log(2) )
	//------------------------------------------------------------------------------
	//	exp2( arg ) = exp(     z        )		z := arg * log(2)
	//------------------------------------------------------------------------------

	const uint32_t z = u32mulhi( arg, LOG_2 );

	//------------------------------------------------------------------------------
	//	extract leading two bits (used by table lookup)
	//------------------------------------------------------------------------------
	//	exp( z ) = exp( y ) * exp( x )
	//------------------------------------------------------------------------------

	const uint32_t i = z      >> ShiftValue;
	const uint32_t x = z - (i << ShiftValue);

	//------------------------------------------------------------------------------
	//	compute series expansion using modified horner scheme
	//------------------------------------------------------------------------------

	//------------------------------------------------------------------------------
	//	h := x
	//------------------------------------------------------------------------------

	uint32_t h = x;

	//------------------------------------------------------------------------------
	//	h := x + x/7 * x
	//------------------------------------------------------------------------------

	h = u32mulhi( u32mulhi( h, x ), ONE_BY_7 ) + x;

	//------------------------------------------------------------------------------
	//	h := x + x/6 * (x + x/7 * x)
	//------------------------------------------------------------------------------

	h = u32mulhi( u32mulhi( h, x ), ONE_BY_6 ) + x;

	//------------------------------------------------------------------------------
	//	h := x + x/5 * (x + x/6 * (x + x/7 * x))
	//------------------------------------------------------------------------------

	h = u32mulhi( u32mulhi( h, x ), ONE_BY_5 ) + x;

	//------------------------------------------------------------------------------
	//	h := x + x/4 * (x + x/5 * (x + x/6 * (x + x/7 * x)))
	//------------------------------------------------------------------------------

	h = ( u32mulhi( h, x ) >> 2) + x;

	//------------------------------------------------------------------------------
	//	h := x + x/3 * (x + x/4 * (x + x/5 * (x + x/6 * (x + x/7 * x))))
	//------------------------------------------------------------------------------

	h = u32mulhi( u32mulhi( h, x ), ONE_BY_3 ) + x;

	//------------------------------------------------------------------------------
	// h := x + x/2 * (x + x/3 * (x + x/4 * (x + x/5 * (x + x/6 * (x + x/7 * x)))))
	//------------------------------------------------------------------------------

	h = ( u32mulhi( h, x ) >> 1) + x;

	//------------------------------------------------------------------------------
	// Now:	h = exp(x)-1 = x + x²/2! + x³/3! + x⁴/4! + x⁵/5! + x⁶/6! + x⁷/7!
	//------------------------------------------------------------------------------
	//	note the "-1" resulting from the missing "1" in the series expansion
	//------------------------------------------------------------------------------

	//------------------------------------------------------------------------------
	//	Compute: f(a) = exp(z) - 1 = exp(y) * exp(x) - 1
	//------------------------------------------------------------------------------
	//	f(a) =                          exp(z)       - 1
	//	f(a) =           exp(y)           *  exp(x)  - 1
	//	f(a) = ( 1 + EXP_i_BY_4_MINUS_1 ) * (1 + h ) - 1
	//------------------------------------------------------------------------------
	//	f(a) = EXP_i_BY_4_MINUS_1 * h + EXP_i_BY_4_MINUS_1 + h
	//------------------------------------------------------------------------------

	switch( i ){

		//----------------------------------------------------------------------
		//	i=0: multiply by exp( 0/4 ) == 1
		//----------------------------------------------------------------------
		//	exp( 0/4 ) = 1 + EXP_0_BY_4_MINUS_1 = 1
		//----------------------------------------------------------------------
		//	u32mulhi( EXP_0_BY_4_MINUS_1, h ) + EXP_0_BY_4_MINUS_1 + h
		//	u32mulhi(          0        , h ) +          0         + h
		//                                                               h
		//----------------------------------------------------------------------

	case 0:	break;

		//----------------------------------------------------------------------
		//	i=1: multiply by exp( 1/4 )
		//----------------------------------------------------------------------
		//	exp( 1/4 ) = 1 + EXP_1_BY_4_MINUS_1
		//----------------------------------------------------------------------

	case 1: h = u32mulhi( EXP_1_BY_4_MINUS_1, h ) + EXP_1_BY_4_MINUS_1 + h;
		break;

		//----------------------------------------------------------------------
		//	i=2: multiply by exp( 2/4 )
		//----------------------------------------------------------------------
		//	exp( 2/4 ) = 1 + EXP_2_BY_4_MINUS_1
		//----------------------------------------------------------------------

	case 2: h = u32mulhi( EXP_2_BY_4_MINUS_1, h ) + EXP_2_BY_4_MINUS_1 + h;
		break;

		//----------------------------------------------------------------------
		//	i>=3: cannot happen
		//----------------------------------------------------------------------

	default: break;
	}

	//------------------------------------------------------------------------------
	//	heuristic correction for truncation errors and series remainder
	//------------------------------------------------------------------------------

	return h + ( u32mulhi( arg, 12 ) >> 1 );
}

Download: u32p32exp2m1.cc

Bei der Verwendung des Ganzzahl-Typs uint32_t wird das Ergebnis der Multiplikation nach unten gerundet. Dadurch sind Zwischenergebnisse und das Endergebnis immer zu niedrig. Der Korrekturfaktor verschiebt das Endergebnis, der Fehler ist dadurch symmetrisch um 0 verteilt und das Endergebnis hat einen maximalen Fehler von ±4, was ±9.3·10-10 entspricht. Durch kleine Justierungen an den Parametern dürfte die Genauigkeit noch verbessert werden können, das war aber für meine Anwendung nicht nötig.

Sie können das Gesamtpaket als u32p32exp2m1.zip herunterladen.