I have an example of a sum of the form s = a_1/b_1 + a_2/b_2 + ...
where the a_i
and the b_i
are CGAL lazy numbers, such that the interval returned by s.interval()
is very large. Its lower bound is close to the expected value (and close to CGAL::as_double(s)
) but its upper bound is huge (even infinite).
But if I use mydivision(a_i, b_i)
(defined below) instead of a_i/b_i
, then the interval is thin as expected. Here is mydivision
:
typedef CGAL::Quotient<CGAL::MP_Float> Quotient;
typedef CGAL::Lazy_exact_nt<Quotient> lazyScalar;
lazyScalar mydivision(lazyScalar x1, lazyScalar x2) {
Quotient q1 = x1.exact();
Quotient q2 = x2.exact();
lazyScalar q = lazyScalar(Quotient(
q1.numerator() * q2.denominator(), q1.denominator() * q2.numerator())
);
return q;
}
This is the sum (it goes to pi/2 - 1
):
This is the full code:
#include <vector>
#include <CGAL/number_utils.h>
#include <CGAL/Lazy_exact_nt.h>
#include <CGAL/MP_Float.h>
#include <CGAL/Quotient.h>
#include <CGAL/Interval_nt.h>
typedef CGAL::Quotient<CGAL::MP_Float> Quotient;
typedef CGAL::Lazy_exact_nt<Quotient> lazyScalar;
typedef std::vector<lazyScalar> lazyVector;
// "my" division
lazyScalar mydivision(lazyScalar x1, lazyScalar x2) {
Quotient q1 = x1.exact();
Quotient q2 = x2.exact();
lazyScalar q = lazyScalar(Quotient(
q1.numerator() * q2.denominator(), q1.denominator() * q2.numerator())
);
return q;
}
// sum the elements of a vector of lazy numbers
lazyScalar lazySum(lazyVector lv) {
const size_t n = lv.size();
lazyScalar sum(0);
for(size_t i = 0; i < n; i++) {
sum += lv[i];
}
return sum;
}
// element-wise division of two vectors of lazy numbers
lazyVector lv1_dividedby_lv2(lazyVector lv1, lazyVector lv2) {
const size_t n = lv1.size();
lazyVector lv(n);
for(size_t i = 0; i < n; i++) {
lv[i] = lv1[i] / lv2[i];
}
return lv;
}
// same as above using "my" division
lazyVector lv1_mydividedby_lv2(lazyVector lv1, lazyVector lv2) {
const size_t n = lv1.size();
lazyVector lv(n);
for(size_t i = 0; i < n; i++) {
lv[i] = mydivision(lv1[i], lv2[i]);
}
return lv;
}
// cumulative products of the elements of a vector of lazy numbers
lazyVector lazyCumprod(lazyVector lvin) {
const size_t n = lvin.size();
lazyVector lv(n);
lazyScalar prod(1);
for(size_t i = 0; i < n; i++) {
prod *= lvin[i];
lv[i] = prod;
}
return lv;
}
// the series with the ordinary division
lazyScalar Euler(int n) {
lazyVector lv1(n);
for(int i = 0; i < n; i++) {
lv1[i] = lazyScalar(i + 1);
}
lazyVector lv2(n);
for(int i = 0; i < n; i++) {
lv2[i] = lazyScalar(2*i + 3);
}
return lazySum(lv1_dividedby_lv2(lazyCumprod(lv1), lazyCumprod(lv2)));
}
// the series with "my" division
lazyScalar myEuler(int n) {
lazyVector lv1(n);
for(int i = 0; i < n; i++) {
lv1[i] = lazyScalar(i + 1);
}
lazyVector lv2(n);
for(int i = 0; i < n; i++) {
lv2[i] = lazyScalar(2*i + 3);
}
return lazySum(lv1_mydividedby_lv2(lazyCumprod(lv1), lazyCumprod(lv2)));
}
// test - the difference starts to occur for n=170
int main() {
lazyScalar euler = Euler(171);
CGAL::Interval_nt<false> interval = euler.approx();
std::cout << "lower bound: " << interval.inf() << "\n"; // prints 0.57
std::cout << "upper bound: " << interval.sup() << "\n"; // prints inf
lazyScalar myeuler = myEuler(171);
CGAL::Interval_nt<false> myinterval = myeuler.approx();
std::cout << "lower bound: " << myinterval.inf() << "\n"; // prints 0.57
std::cout << "upper bound: " << myinterval.sup() << "\n"; // prints 0.57
return 0;
}
Why this large upper bound?
You are computing factorials and similar products. Once they exceed the maximum value for a double
, there isn't much an interval represented as a pair of double can do.
At least it gives you a safe answer, and you can still get the true answer using exact()
at the end. Your division would be simpler as return exact(x1/x2);
, but you don't even need to call exact
that often, just exact(Euler(171))
Computing the value as (1/3)*(2/5)*(3/7)
instead of (1*2*3)/(3*5*7)
would avoid having an overflow so soon.