PROWAREtech

articles » current » c-plus-plus » procedures » eulers-math-library

C/C++: Euler's Math Library

Essential math functions around Euler's number, including natural log, log(), and e to the x power (e^x), exp(); includes supporting functions isinf(), isnan(), ceil(), fabs() and sqrt().

Found use for these functions when writing code for Nvidia CUDA, and not wanting to or not being able to import libraries.


// eulers.h

#ifndef EULERS_H
#define EULERS_H

#ifndef INFINITY
#define INFINITY   ((float)(1e+300 * 1e+300))
#endif

#ifndef NAN
#define NAN        (-(float)(INFINITY * 0.0F))
#endif

// Is INFINITY
int Isinf(double x) {
	union {
		double d;
		unsigned long long i;
	} u = { x };

	// Extract exponent and mantissa
	unsigned long long exp = (u.i >> 52) & 0x7FF;
	unsigned long long mantissa = u.i & ((1ULL << 52) - 1);

	// Check if exponent is all 1's and mantissa is 0
	if (exp == 0x7FF && mantissa == 0) {
		return (u.i >> 63) ? -1 : 1;  // Check sign bit
	}
	return 0;
}

// Is Not a Number
// Returns 1 if x is NaN, 0 otherwise
int Isnan(double x) {
	union {
		double d;
		unsigned long long i;
	} u = { x };

	// Extract exponent and mantissa
	unsigned long long exp = (u.i >> 52) & 0x7FF;
	unsigned long long mantissa = u.i & ((1ULL << 52) - 1);

	// NaN has all 1's in exponent and non-zero mantissa
	return (exp == 0x7FF && mantissa != 0);
}

// Ceiling
double Ceil(double x) {
	// Handle special cases
	if (Isinf(x) || Isnan(x)) return x;

	long long i = (long long)x;
	return (x > 0.0 && x > i) ? i + 1.0 : (double)i;
}

// Returns absolute value of x
double Fabs(double x) {
	// Use bit manipulation to clear sign bit
	union {
		double d;
		unsigned long long i;
	} u = { x };
	u.i &= ~(1ULL << 63);  // Clear sign bit
	return u.d;
}

// Implementation of sqrt(x) using Newton's method
// Valid for x >= 0
double Sqrt(double x) {
	// Handle special cases
	if (x < 0.0) return NAN;
	if (x == 0.0 || x == 1.0 || Isinf(x)) return x;

	// Initial guess: use bit manipulation for a good starting point
	union {
		double d;
		unsigned long long i;
	} u = { x };
	u.i = 0x5fe6eb50c7b537a9 - (u.i >> 1);
	double guess = u.d;

	// Newton's method: repeat until convergence
	for (int i = 0; i < 5; i++) {
		guess = (guess + x / guess) * 0.5;
	}

	return guess;
}

// Implementation of exp(x) using Taylor series
// Valid for any real number x
double Exp(double x) {
	// Handle special cases
	if (x == 0.0) return 1.0;
	if (Isinf(x)) return x > 0 ? INFINITY : 0.0;

	// Use the property exp(x) = (exp(x/n))^n to reduce large values
	int n = 1;
	if (Fabs(x) > 1.0) {
		n = (int)Ceil(Fabs(x));
		x /= n;
	}

	double sum = 1.0;  // First term
	double term = 1.0; // Current term
	double i = 1.0;    // Current iteration

	// Add terms until desired precision reached
	while (Fabs(term) > 1e-15 && i < 100) {
		term *= x / i;
		sum += term;
		i += 1.0;
	}

	// If dividing x by n earlier, need to take result to nth power
	double result = sum;
	for (int j = 1; j < n; j++) {
		result *= sum;
	}

	return result;
}

// Implementation of log(x) using series expansion
// Valid for x > 0
double Log(double x) {
	// Handle special cases
	if (x <= 0.0) return NAN;
	if (x == 1.0) return 0.0;
	if (Isinf(x)) return INFINITY;

	// Use log(x) = 2 * log(sqrt(x))
	// Repeatedly take sqrt until x is close to 1
	int power = 0;
	while (x >= 2.0) {
		x = Sqrt(x);
		power++;
	}
	while (x < 0.5) {
		x = Sqrt(x);
		power--;
	}

	// Now use series log(1+y) = y - y^2/2 + y^3/3 - ...
	// where y = (x-1)/(x+1)
	double y = (x - 1.0) / (x + 1.0);
	double y2 = y * y;
	double sum = y;
	double term = y;
	int i = 1;

	// Add terms until desired precision
	while (Fabs(term) > 1e-15 && i < 100) {
		term *= y2 * (2 * i - 1) / (2 * i + 1);
		sum += term;
		i++;
	}

	// Adjust for the sqrt operations that were done earlier
	return (sum * 2.0) * (1 << power);
}

#endif

Revised to be more IEEE compliant. This may work better on certain systems.


// eulers2.h

#ifndef EULERS_H
#define EULERS_H

// Define IEEE 754 double-precision bits for Infinity and NaN as constants:
static const union { unsigned long long __u; double __d; } __infinity_union = { 0x7FF0000000000000ULL };
static const union { unsigned long long __u; double __d; } __nan_union = { 0x7FF8000000000001ULL };

// Then define macros if they are not already defined (shouldn't be):
#ifndef INFINITY
#define INFINITY (__infinity_union.__d)
#endif

#ifndef NAN
#define NAN (__nan_union.__d)
#endif

// Check for infinity: returns +1 for +inf, -1 for -inf, 0 otherwise
int Isinf(double x) {
	union { double d; unsigned long long u; } u = { x };
	unsigned long long exp = (u.u >> 52) & 0x7FFULL;
	unsigned long long frac = u.u & 0x000FFFFFFFFFFFFFULL;

	if (exp == 0x7FF && frac == 0ULL) {
		return (u.u >> 63) ? -1 : 1;
	}
	return 0;
}

// Check for NaN: returns 1 if NaN, 0 otherwise
int Isnan(double x) {
	union { double d; unsigned long long u; } u = { x };
	unsigned long long exp = (u.u >> 52) & 0x7FFULL;
	unsigned long long frac = u.u & 0x000FFFFFFFFFFFFFULL;

	// NaN: exponent all 1's and non-zero fraction
	return (exp == 0x7FF && frac != 0ULL);
}

// Ceiling function: returns the smallest integer >= x
double Ceil(double x) {
	if (Isinf(x) || Isnan(x)) return x;

	long long i = (long long)x;
	// If x > 0 and x not an integer, ceil is i+1; else just i.
	return (x > 0.0 && x > (double)i) ? (double)(i + 1) : (double)i;
}

// Absolute value
double Fabs(double x) {
	union { double d; unsigned long long u; } u = { x };
	u.u &= ~(1ULL << 63);
	return u.d;
}

// Square root using Newton's method
double Sqrt(double x) {
	if (x < 0.0) return NAN;
	if (x == 0.0 || x == 1.0 || Isinf(x)) return x;

	// Initial guess: a clever bit trick (approximate)
	union { double d; unsigned long long i; } u = { x };
	// Initial guess: from Quake's fast inverse sqrt approximation
	u.i = 0x5fe6eb50c7b537a9ULL - (u.i >> 1);
	double guess = u.d;

	double old_guess;
	// Iterate until convergence
	do {
		old_guess = guess;
		guess = 0.5 * (guess + x / guess);
	} while (Fabs(guess - old_guess) > 1e-15 * guess);

	return guess;
}

// Exp(x): has range reduction
double Exp(double x) {
	// Handle special cases
	if (Isnan(x)) return NAN;
	if (Isinf(x)) return (x > 0.0) ? INFINITY : 0.0;
	if (x == 0.0) return 1.0;

	// Constants
	const double LN2 = 0.69314718055994530942; // Approximation of ln(2)

	// For large |x|, break down x = n*ln(2) + r, where r in [-ln(2)/2, ln(2)/2]
	// This gives us exp(x) = 2^n * exp(r), with |r| <= 0.3466 approx.
	int n = (int)(x / LN2);
	double r = x - n * LN2;

	// If x is very small (like negative large), then might have a large negative n.
	// For example, x = -100 might give a large negative n. This is still fine.

	// Now use a Taylor series expansion for exp(r) around 0:
	// exp(r) = 1 + r + r^2/2! + r^3/3! + ...
	double term = 1.0;
	double sum = 1.0;
	double i = 1.0;
	while (Fabs(term) > 1e-15 && i < 100) {
		term *= r / i;
		sum += term;
		i += 1.0;
	}

	// Now multiply by 2^n
	double result = sum;
	if (n > 0) {
		// multiply result by 2^n
		for (int j = 0; j < n; j++) {
			result *= 2.0;
			if (Isinf(result)) break; // avoid overflow
		}
	}
	else if (n < 0) {
		// divide result by 2^-n
		for (int j = 0; j < -n; j++) {
			result *= 0.5;
			if (result == 0.0) break; // underflow
		}
	}

	return result;
}

// Log(x): scaling into [0.5, 2) then using series
double Log(double x) {
	if (x < 0.0) return NAN;
	if (x == 0.0) return -INFINITY;
	if (Isinf(x)) return INFINITY;
	if (Isnan(x)) return NAN;
	if (x == 1.0) return 0.0;

	const double LN2 = 0.69314718055994530942;

	// Scale x into [0.5, 2) by adjusting an integer k such that:
	// x = 2^k * y, with y in [0.5, 2)
	int k = 0;
	while (x >= 2.0) {
		x *= 0.5;
		k++;
	}
	while (x < 0.5) {
		x *= 2.0;
		k--;
	}

	// Now use y = (x-1)/(x+1)
	// log(x) = 2 * [ y + y^3/3 + y^5/5 + ... ] for x in (0.5,2)
	double y = (x - 1.0) / (x + 1.0);
	double y2 = y * y;
	double term = y;
	double sum = term;
	int i = 1;
	// Add higher-order terms
	// The standard series: log(x) = 2*(y + y^3/3 + y^5/5 + ...)
	// Stop when terms get very small.
	do {
		i += 2;
		term *= y2;
		double add = term / i;
		if (Fabs(add) < 1e-15) break;
		sum += add;
	} while (i < 200);

	double result = 2.0 * sum + k * LN2;
	return result;
}

#endif

Now, use the above functions!


#include "eulers2.h"

double Tanh(double x) // Hyperbolic Tangent function
{
	double n = Exp(-x);
	double p = Exp(x);
	return (p - n) / (p + n);
}

double TanhPrime(double x) // derivative of Tanh
{
	double n = Exp(-x);
	double p = Exp(x);
	return 1.0 - ((p - n) / (p + n)) * ((p - n) / (p + n)); // this is simply: 1.0 - (tanh(x) * tanh(x))
}

double Sigmoid(double x) // and oldie but goodie, ReLU/ELU has replaced it for the most part
{
	return 1.0 / (1.0 + Exp(-x));
}

double SigmoidPrime(double x) // derivative of Sigmoid
{
	double n = Exp(-x);
	return (1.0 / (1.0 + n)) * (1.0 - (1.0 / (1.0 + n))); // this is simply: Sigmoid(x) * (1.0 - Sigmoid(x))
}
double ELU(double x, double alpha = 1.0) // Exponential Linear Unit function
{
	return x >= 0 ? x : (alpha * (Exp(x) - 1.0));
}

double ELUPrime(double x, double alpha = 1.0) // derivative of ELU; make sure to use same alpha value as passed to ELU()
{
	return x >= 0 ? 1.0 : (alpha * Exp(x));
}

float ConvertSoftmaxLossToAccuracy(float loss, int numClasses)
{
	// Calculate the maximum possible loss (random prediction)
	// For random prediction, probability is 1/numClasses for each class
	float maxLoss = -Log(1.0F / numClasses);

	// Calculate accuracy: 1 when loss is 0, 0 when loss equals maxLoss
	// Using a linear interpolation between these points
	float accuracy = 1.0F - (loss / maxLoss);

	// Clamp the result between 0 and 1 to handle edge cases
	accuracy = accuracy > 1.0F ? 1.0F : accuracy;
	accuracy = accuracy < 0.0F ? 0.0F : accuracy;
	return accuracy;
}

float ConvertBinaryLossToAccuracy(float loss)
{
	return Exp(-loss);
}

PROWAREtech

Hello there! How can I help you today?
Ask any question

PROWAREtech

This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
ACCEPT REJECT