test_pie/external/exact_ccd/expansion.cpp

439 lines
9.5 KiB
C++
Raw Normal View History

2023-09-14 11:12:02 +02:00
// Released into the public-domain by Robert Bridson, 2009.
// Modified by Tyson Brochu, 2011.
#include "expansion.h"
#include <cassert>
#include <cmath>
#include <iostream>
namespace CCD
{
namespace {
//==============================================================================
void
two_sum(double a,
double b,
double& x,
double& y)
{
x=a+b;
double z=x-a;
y=(a-(x-z))+(b-z);
}
//==============================================================================
// requires that |a|>=|b|
void
fast_two_sum(double a,
double b,
double& x,
double& y)
{
assert( a == a && b == b );
assert(std::fabs(a)>=std::fabs(b));
x=a+b;
y=(a-x)+b;
}
//==============================================================================
void
split(double a,
double& x,
double& y)
{
double c=134217729*a;
x=c-(c-a);
y=a-x;
}
//==============================================================================
void
two_product(double a,
double b,
double& x,
double& y)
{
x=a*b;
double a1, a2, b1, b2;
split(a, a1, a2);
split(b, b1, b2);
y=a2*b2-(((x-a1*b1)-a2*b1)-a1*b2);
}
} // namespace
//==============================================================================
bool
is_zero( const expansion& a )
{
return ( a.v.size() == 0 );
}
//==============================================================================
int
sign( const expansion& a )
{
if ( a.v.size() == 0 ) { return 0; }
// REVIEW: I'm assuming we can get the sign of the expansion by the sign of its leading term (i.e. the sum of all other terms < leading term )
// This true if the expansion if increasing and nonoverlapping
if ( a.v.back() > 0 )
{
return 1;
}
return -1;
}
//==============================================================================
void
add(double a, double b, expansion& sum)
{
sum.resize(2);
two_sum(a, b, sum.v[1], sum.v[0]);
}
//==============================================================================
// a and sum may be aliased to the same expansion for in-place addition
void
add(const expansion& a, double b, expansion& sum)
{
size_t m=a.v.size();
sum.v.reserve(m+1);
double s;
for(size_t i=0; i<m; ++i){
two_sum(b, a.v[i], b, s);
if(s) sum.v.push_back(s);
}
sum.v.push_back(b);
}
//==============================================================================
// aliasing a, b and sum is safe
void
add(const expansion& a, const expansion& b, expansion& sum)
{
if(a.v.empty())
{
sum=b;
return;
}else if(b.v.empty())
{
sum=a;
return;
}
// Shewchuk's fast-expansion-sum
expansion merge(a.v.size()+b.v.size(), 0);
unsigned int i=0, j=0, k=0;
for(;;){
if(std::fabs(a.v[i])<std::fabs(b.v[j])){
merge.v[k++]=a.v[i++];
if(i==a.v.size()){
while(j<b.v.size()) merge.v[k++]=b.v[j++];
break;
}
}else{
merge.v[k++]=b.v[j++];
if(j==b.v.size()){
while(i<a.v.size()) merge.v[k++]=a.v[i++];
break;
}
}
}
sum.v.reserve(merge.v.size());
sum.v.resize(0);
double q, r;
fast_two_sum(merge.v[1], merge.v[0], q, r);
if(r) sum.v.push_back(r);
for(i=2; i<merge.v.size(); ++i){
two_sum(q, merge.v[i], q, r);
if(r) sum.v.push_back(r);
}
if(q) sum.v.push_back(q);
}
//==============================================================================
void
subtract( const double& a, const double& b, expansion& difference)
{
add( a, -b, difference );
}
//==============================================================================
void
subtract(const expansion& a, const expansion& b, expansion& difference)
{
// could improve this a bit!
expansion c;
negative(b, c);
add(a, c, difference);
}
//==============================================================================
void
negative(const expansion& input, expansion& output)
{
output.resize(input.v.size());
for(unsigned int i=0; i<input.v.size(); ++i)
output.v[i]=-input.v[i];
}
//==============================================================================
void
multiply(double a, double b, expansion& product)
{
product.resize(2);
two_product(a, b, product.v[1], product.v[0]);
}
//==============================================================================
void
multiply(double a, double b, double c, expansion& product)
{
expansion ab;
multiply(a, b, ab);
multiply(ab, c, product);
}
//==============================================================================
void
multiply(double a, double b, double c, double d, expansion& product)
{
multiply(a, b, product);
expansion abc;
multiply(product, c, abc);
multiply(abc, d, product);
}
//==============================================================================
void
multiply(const expansion& a, double b, expansion& product)
{
// basic idea:
// multiply each entry in a by b (producing two new entries), then
// two_sum them in such a way to guarantee increasing/non-overlapping output
product.resize(2*a.v.size());
if(a.v.empty()) return;
two_product(a.v[0], b, product.v[1], product.v[0]); // finalize product[0]
double x, y, z;
for(unsigned int i=1; i<a.v.size(); ++i){
two_product(a.v[i], b, x, y);
// finalize product[2*i-1]
two_sum(product.v[2*i-1], y, z, product.v[2*i-1]);
// finalize product[2*i], could be fast_two_sum instead
fast_two_sum(x, z, product.v[2*i+1], product.v[2*i]);
}
// multiplication is a prime candidate for producing spurious zeros, so
// remove them by default
remove_zeros(product);
}
//==============================================================================
void
multiply(const expansion& a, const expansion& b, expansion& product)
{
// most stupid way of doing it:
// multiply a by each entry in b, add each to product
product.resize(0);
expansion term;
for(unsigned int i=0; i<b.v.size(); ++i){
multiply(a, b.v[i], term);
add(product, term, product);
}
}
//==============================================================================
void compress( const expansion& e, expansion& h )
{
if ( is_zero( e ) )
{
make_zero( h );
return;
}
expansion g( e.v.size(), 0 );
size_t bottom = e.v.size() - 1;
double q = e.v[bottom];
for ( ssize_t i = e.v.size() - 2; i >= 0; --i )
{
double new_q, small_q;
fast_two_sum( q, e.v[i], new_q, small_q );
if ( small_q != 0 )
{
g.v[bottom--] = new_q;
q = small_q;
}
else
{
q = new_q;
}
}
g.v[bottom] = q;
h.v.resize( e.v.size(), 0 );
unsigned int top = 0;
for ( size_t i = bottom+1; i < e.v.size(); ++i )
{
double new_q, small_q;
fast_two_sum( g.v[i], q, new_q, small_q );
if ( small_q != 0 )
{
h.v[top++] = small_q;
}
q = new_q;
}
h.v[top] = q;
h.resize( top+1 );
}
//==============================================================================
bool divide( const expansion& x, const expansion& y, expansion& q )
{
assert( !is_zero( y ) );
if ( is_zero( x ) )
{
// 0 / y = 0
make_expansion( 0, q );
return true;
}
const double divisor = estimate(y);
// q is the quotient, built by repeatedly dividing the remainder
// Initially, q = estimate(x) / estimate(y)
make_expansion( estimate(x) / divisor, q );
expansion qy;
multiply( q, y, qy );
expansion r;
subtract( x, qy, r );
while ( !is_zero(r) )
{
// s is the next term in the quotient q:
// s = estimate(r) / estimate(y)
expansion s;
make_expansion( estimate(r) / divisor, s );
if ( is_zero(s) )
{
assert ( !is_zero(y) );
std::cout << "underflow, s == 0" << std::endl;
std::cout << "divisor: " << divisor << std::endl;
return false;
}
// q += s
add( q, s, q );
// r -= s*y
expansion sy;
multiply( s, y, sy );
// underflow, quotient not representable by an expansion
if ( is_zero(sy) )
{
assert ( !is_zero(s) && !is_zero(y) );
std::cout << "underflow, sy == 0" << std::endl;
return false;
}
subtract( r, sy, r );
expansion compressed_r;
compress( r, compressed_r );
r = compressed_r;
}
remove_zeros( q );
return true;
}
//==============================================================================
void
remove_zeros(expansion& a)
{
unsigned int i, j;
for ( i = 0, j = 0; i < a.v.size(); ++i )
{
if ( a.v[i] )
{
a.v[j++] = a.v[i];
}
}
a.resize(j);
}
//==============================================================================
double
estimate(const expansion& a)
{
double x=0;
for(unsigned int i=0; i<a.v.size(); ++i)
x+=a.v[i];
return x;
}
//==============================================================================
bool equals( const expansion& a, const expansion& b )
{
bool same = (a.v.size() == b.v.size());
if (!same) { return false; }
for ( unsigned int i = 0; i < a.v.size(); ++i )
{
same &= (a.v[i] == b.v[i]);
}
return same;
}
//==============================================================================
void print_full( const expansion& e )
{
if ( e.v.size() == 0 )
{
std::cout << "0" << std::endl;
return;
}
for ( unsigned int j = 0; j < e.v.size(); ++j )
{
std::cout << e.v[j] << " ";
}
std::cout << std::endl;
}
}