expHighPrecision function
Internal helper method for exponential function.
x
is the original argument of the exponential function.
extra
bits of precision on input (To Be Confirmed).
highPrecision
extra bits of precision on output (To Be Confirmed)
Implementation
double expHighPrecision(double x,
[double extra = 0.0, List<double>? highPrecision]) {
double intPartA;
double intPartB;
int intVal;
// Lookup exp(floor(x)).
// intPartA will have the upper 22 bits, intPartB will have the lower
// 52 bits.
if (x < 0.0) {
intVal = -x.toInt();
if (intVal > 746) {
if (highPrecision != null) {
highPrecision[0] = 0.0;
highPrecision[1] = 0.0;
}
return 0.0;
}
if (intVal > 709) {
// This will produce a subnormal output
final result = expHighPrecision(x + 40.19140625, extra, highPrecision) /
285040095144011776.0;
if (highPrecision != null) {
highPrecision[0] /= 285040095144011776.0;
highPrecision[1] /= 285040095144011776.0;
}
return result;
}
if (intVal == 709) {
// exp(1.494140625) is nearly a machine number...
final result = expHighPrecision(x + 1.494140625, extra, highPrecision) /
4.455505956692756620;
if (highPrecision != null) {
highPrecision[0] /= 4.455505956692756620;
highPrecision[1] /= 4.455505956692756620;
}
return result;
}
intVal++;
intPartA = expIntTableA[expIntTableMaxIndex - intVal];
intPartB = expIntTableB[expIntTableMaxIndex - intVal];
intVal = -intVal;
} else {
if (x == double.infinity) {
if (highPrecision != null) {
highPrecision[0] = double.infinity;
highPrecision[1] = 0.0;
}
return double.infinity;
}
intVal = x.toInt();
if (intVal > 709) {
if (highPrecision != null) {
highPrecision[0] = double.infinity;
highPrecision[1] = 0.0;
}
return double.infinity;
}
intPartA = expIntTableA[expIntTableMaxIndex + intVal];
intPartB = expIntTableB[expIntTableMaxIndex + intVal];
}
// Get the fractional part of x, find the greatest multiple of 2^-10 less than
// x and look up the exp function of it.
// fracPartA will have the upper 22 bits, fracPartB the lower 52 bits.
final intFrac = ((x - intVal) * 1024.0).toInt();
final fracPartA = expFracTableA[intFrac];
final fracPartB = expFracTableB[intFrac];
// epsilon is the difference in x from the nearest multiple of 2^-10. It
// has a value in the range 0 <= epsilon < 2^-10.
// Do the subtraction from x as the last step to avoid possible
// loss of percison.
final epsilon = x - (intVal + intFrac / 1024.0);
// Compute z = exp(epsilon) - 1.0 via a minimax polynomial. z has
// full double precision (52 bits). Since z < 2^-10, we will have
// 62 bits of precision when combined with the contant 1. This will be
// used in the last addition below to get proper rounding.
// Remez generated polynomial. Converges on the interval [0, 2^-10], error
// is less than 0.5 ULP
var z = 0.04168701738764507;
z = z * epsilon + 0.1666666505023083;
z = z * epsilon + 0.5000000000042687;
z = z * epsilon + 1.0;
z = z * epsilon + -3.940510424527919E-20;
// Compute (intPartA+intPartB) * (fracPartA+fracPartB) by binomial
// expansion.
// tempA is exact since intPartA and intPartB only have 22 bits each.
// tempB will have 52 bits of precision.
final tempA = intPartA * fracPartA;
final tempB =
intPartA * fracPartB + intPartB * fracPartA + intPartB * fracPartB;
// Compute the result. (1+z)(tempA+tempB). Order of operations is
// important. For accuracy add by increasing size. tempA is exact and
// much larger than the others. If there are extra bits specified from the
// pow() function, use them.
final tempC = tempB + tempA;
double result;
if (extra != 0.0) {
result = tempC * extra * z + tempC * extra + tempC * z + tempB + tempA;
} else {
result = tempC * z + tempB + tempA;
}
if (highPrecision != null) {
// If requesting high precision
highPrecision[0] = tempA;
highPrecision[1] = tempC * extra * z + tempC * extra + tempC * z + tempB;
}
return result;
}