/* * Copyright 2013 Florian Philipp * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include /* using fabs */ #include /* using malloc, realloc, free, NULL */ #include /* using printf, fopen, fwrite, fclose */ /** * Shewchuk summation algorithm. * * Derived from msum here: * http://code.activestate.com/recipes/393090/ */ struct Accumulator { /** * Number of currently used partial sums */ size_t partials_n; /** * Capacity for partial sums * partials_cap == 1 indicates use of partials.scalar */ size_t partials_cap; /** * Partial sums ordered by value * * To avoid dynamic allocation in the common case that only one sum is kept, * the scalar union member is used. * The additional if/else comparisons have negligible effect on the run time */ union { double* array; double scalar; } partials; }; /** * Initializes an Accumulator * * An Accumulator may only be initialized once to avoid memory leaks * unless it is destroyed between initializations. * See also: accumulator_reset */ void accumulator_init(struct Accumulator* self) { self->partials_n = 0; self->partials_cap = 1; self->partials.scalar = 0.; } /** * Releases all resources associated with this Accumulator * * May only be called once to avoid segfaults. * A destroyed Accumulator may only be used after calling accumulator_init. * As a rule of thumb, every accumulator_init should be paired with an * accumulator_destroy */ void accumulator_destroy(struct Accumulator* self) { if(self->partials_cap > 1) free(self->partials.array); } /** * Re-initializes an Accumulator in order to use it for another summation */ void accumulator_reset(struct Accumulator* self) { self->partials_n = 0; if(self->partials_cap == 1) self->partials.scalar = 0.; } /** * Private method of Accumulator. Updates existing partial sums * * \param self an initialized Accumulator * \param x a value to be added * \param tail output argument. Returns the index of the last used partial sum. * Is less or equal to self->partials_n * * \return the last partial sum. Has to be stored in self->partials[tail] */ static double _accumulator_update_partials(struct Accumulator* self, double x, size_t* tail) { *tail = 0; double* partials; if(self->partials_cap > 1) partials = self->partials.array; else partials = &self->partials.scalar; size_t partial_i; for(partial_i = 0; partial_i < self->partials_n; ++partial_i) { double y = partials[partial_i]; if(fabs(x) < fabs(y)) { double tmp = y; y = x; x = tmp; } double hi = x + y; double lo = y - (hi - x); if(lo != 0.) { partials[*tail] = lo; *tail += 1; } x = hi; } return x; } /** * Private method of Accumulator. Extends array of partial sums if required * * Updates self->partials_cap and self->partials. * May move data from self->partials.scalar to self->partials.array * * \param self an initialized Accumulator * \param tail the last index that has to be used * * \return 0 on success. 1 if memory allocation failed */ int _accumulator_reserve(struct Accumulator* self, size_t tail) { if(tail < self->partials_cap) return 0; size_t reserved = (tail + 1) * 2; double* partials_ext; if(self->partials_cap == 1) { partials_ext = malloc(reserved * sizeof(double)); if(! partials_ext) return 1; partials_ext[0] = self->partials.scalar; } else { partials_ext = realloc(self->partials.array, reserved * sizeof(double)); if(! partials_ext) return 1; } self->partials.array = partials_ext; self->partials_cap = reserved; return 0; } /** * Adds a value to the accumulated sum * * \return 0 on success. 1 if resource allocation failed */ int accumulator_add(struct Accumulator* self, double x) { size_t tail; x = _accumulator_update_partials(self, x, &tail); if(_accumulator_reserve(self, tail)) return 1; self->partials_n = tail + 1; if(self->partials_cap > 1) self->partials.array[tail] = x; else self->partials.scalar = x; return 0; } /** * Returns the sum of all added values * * Note that this call has some overhead. Results should be cached */ double accumulator_sum(const struct Accumulator* self) { if(self->partials_cap == 1) return self->partials.scalar; double sum = 0.; size_t i; for(i = 0; i < self->partials_n; ++i) sum += self->partials.array[i]; return sum; } /** * Kahan summation algorithm */ struct Kahan { double sum, compensation; }; void kahan_init(struct Kahan* self) { self->sum = self->compensation = 0.; } void kahan_add(struct Kahan* self, double added) { double y = added - self->compensation; double t = self->sum + y; self->compensation = (t - self->sum) - y; self->sum = t; } double kahan_sum(const struct Kahan* self) { return self->sum; } void test_shewchuk(const double* items, size_t n, size_t repetitions) { struct Accumulator accum; accumulator_init(&accum); size_t rep_i; for(rep_i = 0; rep_i < repetitions; ++rep_i) { size_t i; for(i = 0; i < n; ++i) { if(accumulator_add(&accum, items[i])) { printf("OOM\n"); goto dtor; } } } printf("Shewchuk: %g\n", accumulator_sum(&accum)); dtor: accumulator_destroy(&accum); } void test_kahan(const double* items, size_t n, size_t repetitions) { struct Kahan kahan; kahan_init(&kahan); size_t rep_i; for(rep_i = 0; rep_i < repetitions; ++rep_i) { size_t i; for(i = 0; i < n; ++i) kahan_add(&kahan, items[i]); } printf("Kahan: %g\n", kahan_sum(&kahan)); } void test_devnull(const double* items, size_t n) { FILE* fd = fopen("/dev/null", "w"); if(! fd) return; fwrite(items, sizeof(*items), n, fd); fclose(fd); } static void print_test(const double* items, size_t n_items, size_t repetitions) { printf("Testing summation of {"); if(n_items) { printf("%g", items[0]); size_t i; for(i = 1; i < n_items; ++i) printf(", %g", items[i]); } printf("} * %zd\n", repetitions); } #if ! (defined(WITH_SHEW) || defined(WITH_KAHAN) || defined(WITH_DEVNULL)) # error No algorithm defined. # error Use -DWITH_SHEW and/or -DWITH_KAHAN, or -DWITH_DEVNULL #endif int main() { static const double items[] = {1., 1e16, 1., -1e16}; const size_t n_items = sizeof(items) / sizeof(items[0]); #ifdef REPETITIONS const size_t repetitions = REPETITIONS; #else const size_t repetitions = 100000000; #endif print_test(items, n_items, repetitions); #ifdef WITH_SHEW test_shewchuk(items, n_items, repetitions); #endif #ifdef WITH_KAHAN test_kahan(items, n_items, repetitions); #endif #ifdef WITH_DEVNULL test_devnull(items, n_items); #endif return 0; }