Search code examples
c++cgal

Large interval upper bound of a CGAL lazy number


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):

enter image description here

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?


Solution

  • 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.