runCalculation method Null safety
override
Run the solve/integrate algorithm.
Implementation
@override
Future<bool> runCalculation() async {
final DisplayMode precision = model.displayMode;
// The number of digits being displayed determines how precisely we
// estimate the integral.
const int maxIterations = 10;
// Complexity is... uh... O(a lot)
// In testing, typical functions converge in 3 or 4 iterations.
// With the default 50ms/program instruction, and a trivial function
// that generates a random number, it takes over an hour to get here.
// 10 is conservatively high, and a nice, round number
// that's low enough so the thing will terminate before the universe
// expires.
_lastEstimate = 0;
final Value originalY = model.y;
final Value originalX = model.x;
double a = model.yF; // lower bound
double b = model.xF; // upper bound
final double signResult;
if (a == b) {
model.z = originalX;
model.t = originalY;
model.x = model.y = Value.zero;
return true;
} else if (a > b) {
signResult = -1;
final tmp = b;
b = a;
a = tmp;
} else {
signResult = 1;
}
final double span = b - a;
// This is a port of qromo(), copied from
// https://www.hpmuseum.org/forum/thread-16523.html
// The post includes the text "You may freely use any of the code
// here and please ask questions or PM me if something is not clear."
/*
double qromo(double (*f)(double), double a, double b, int n, double eps) {
double R1[n], R2[n];
double *Ro = &R1[0], *Ru = &R2[0];
double h = b-a;
int i, j;
unsigned long long k = 1;
Ro[0] = f((a+b)/2)*h;
for (i = 1; i < n; ++i) {
unsigned long long s = 1;
double sum = 0;
double *Rt;
k *= 3;
h /= 3;
for (j = 1; j < k; j += 3)
sum += f(a+(j-1)*h+h/2) + f(a+(j+1)*h+h/2);
Ru[0] = h*sum + Ro[0]/3;
for (j = 1; j <= i; ++j) {
s *= 9;
Ru[j] = (s*Ru[j-1] - Ro[j-1])/(s-1);
}
if (i > 1 && fabs(Ro[i-1]-Ru[i]) <= eps*fabs(Ru[i])+eps)
return Ru[i];
Rt = Ro;
Ro = Ru;
Ru = Rt;
}
return Ro[n-1]; // no convergence, return best result,
// error is fabs((Ru[n-2]-Ro[n-1])/Ro[n-1])
}
*/
var ro = Float64List(maxIterations);
var ru = Float64List(maxIterations);
double h = span;
int k = 1;
ro[0] = await runSubroutine((a + b) / 2) * h;
_lastEstimate = ro[0] * signResult;
int calls = 1;
double totalMagnitude = ro[0].abs();
int i;
for (i = 1; i < maxIterations; i++) {
int s = 1;
double sum = 0;
k *= 3;
h /= 3;
for (int j = 1; j < k; j += 3) {
double f1 = await runSubroutine(a + (j - 1) * h + h / 2);
double f2 = await runSubroutine(a + (j + 1) * h + h / 2);
calls += 2;
totalMagnitude += f1.abs() + f2.abs();
sum += f1 + f2;
}
ru[0] = h * sum + ro[0] / 3;
for (int j = 1; j <= i; ++j) {
s *= 9;
ru[j] = (s * ru[j - 1] - ro[j - 1]) / (s - 1);
}
final double digit;
final area = (totalMagnitude / calls) * span;
if (area < 1e-100) {
digit = precision.leastSignificantDigit(1).toDouble();
} else {
digit = log10(area) - 1 + precision.leastSignificantDigit(area);
// I think log10(area).floor() is closer to what the 15C does, but
// subtracting 1 instead makes it so the error scales smoothly, which
// makes more sense to me. We're so much faster than the real
// calculator that being overly accurate doesn't hurt, so this is
// a pretty conservative choice.
}
final double eps = fpow(10.0, digit.toDouble());
final rt = ro;
ro = ru;
ru = rt;
if (i > 1 && (ru[i - 1] - ro[i]).abs() <= eps * ro[i].abs() + eps) {
break;
}
_lastEstimate = ro[i] * signResult;
}
final ok = i < maxIterations;
if (!ok) {
i--;
}
final err = ((ru[i - 1] - ro[i]) / ro[i]).abs();
model.z = originalX;
model.t = originalY;
model.yF = err;
model.xF = ro[i] * signResult;
return true; // The 15C never gives CalculatorError on failure to converge
}