time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

dnanmeanpw.c (4362B)


      1 /**
      2 * @license Apache-2.0
      3 *
      4 * Copyright (c) 2020 The Stdlib Authors.
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *    http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 #include "stdlib/stats/base/dnanmeanpw.h"
     20 #include <stdint.h>
     21 
     22 /**
     23 * Computes the sum of double-precision floating-point strided array elements, ignoring `NaN` values and using pairwise summation.
     24 *
     25 * ## Method
     26 *
     27 * -   This implementation uses pairwise summation, which accrues rounding error `O(log2 N)` instead of `O(N)`. The recursion depth is also `O(log2 N)`.
     28 *
     29 * ## References
     30 *
     31 * -   Higham, Nicholas J. 1993. "The Accuracy of Floating Point Summation." _SIAM Journal on Scientific Computing_ 14 (4): 783–99. doi:[10.1137/0914050](https://doi.org/10.1137/0914050).
     32 *
     33 * @private
     34 * @param N       number of indexed elements
     35 * @param W       two-element output array
     36 * @param X       input array
     37 * @param stride  stride length
     38 * @return        output value
     39 */
     40 static void dnansumpw( const int64_t N, double *W, const double *X, const int64_t stride ) {
     41 	double *xp1;
     42 	double *xp2;
     43 	double sum;
     44 	int64_t ix;
     45 	int64_t M;
     46 	int64_t n;
     47 	int64_t i;
     48 	double s0;
     49 	double s1;
     50 	double s2;
     51 	double s3;
     52 	double s4;
     53 	double s5;
     54 	double s6;
     55 	double s7;
     56 	double v;
     57 
     58 	if ( N <= 0 ) {
     59 		return;
     60 	}
     61 	if ( N == 1 || stride == 0 ) {
     62 		if ( X[ 0 ] == X[ 0 ] ) {
     63 			W[ 0 ] += X[ 0 ];
     64 			W[ 1 ] += 1;
     65 			return;
     66 		}
     67 		return;
     68 	}
     69 	if ( stride < 0 ) {
     70 		ix = (1-N) * stride;
     71 	} else {
     72 		ix = 0;
     73 	}
     74 	if ( N < 8 ) {
     75 		// Use simple summation...
     76 		sum = 0.0;
     77 		n = 0;
     78 		for ( i = 0; i < N; i++ ) {
     79 			v = X[ ix ];
     80 			if ( v == v ) {
     81 				sum += X[ ix ];
     82 				n += 1;
     83 			}
     84 			ix += stride;
     85 		}
     86 		W[ 0 ] += sum;
     87 		W[ 1 ] += n;
     88 		return;
     89 	}
     90 	// Blocksize for pairwise summation: 128 (NOTE: decreasing the blocksize decreases rounding error as more pairs are summed, but also decreases performance. Because the inner loop is unrolled eight times, the blocksize is effectively `16`.)
     91 	if ( N <= 128 ) {
     92 		// Sum a block with 8 accumulators (by loop unrolling, we lower the effective blocksize to 16)...
     93 		s0 = 0.0;
     94 		s1 = 0.0;
     95 		s2 = 0.0;
     96 		s3 = 0.0;
     97 		s4 = 0.0;
     98 		s5 = 0.0;
     99 		s6 = 0.0;
    100 		s7 = 0.0;
    101 		n = 0;
    102 
    103 		M = N % 8;
    104 		for ( i = 0; i < N-M; i += 8 ) {
    105 			v = X[ ix ];
    106 			if ( v == v ) {
    107 				s0 += v;
    108 				n += 1;
    109 			}
    110 			ix += stride;
    111 			v = X[ ix ];
    112 			if ( v == v ) {
    113 				s1 += v;
    114 				n += 1;
    115 			}
    116 			ix += stride;
    117 			v = X[ ix ];
    118 			if ( v == v ) {
    119 				s2 += v;
    120 				n += 1;
    121 			}
    122 			ix += stride;
    123 			v = X[ ix ];
    124 			if ( v == v ) {
    125 				s3 += v;
    126 				n += 1;
    127 			}
    128 			ix += stride;
    129 			v = X[ ix ];
    130 			if ( v == v ) {
    131 				s4 += v;
    132 				n += 1;
    133 			}
    134 			ix += stride;
    135 			v = X[ ix ];
    136 			if ( v == v ) {
    137 				s5 += v;
    138 				n += 1;
    139 			}
    140 			ix += stride;
    141 			v = X[ ix ];
    142 			if ( v == v ) {
    143 				s6 += v;
    144 				n += 1;
    145 			}
    146 			ix += stride;
    147 			v = X[ ix ];
    148 			if ( v == v ) {
    149 				s7 += v;
    150 				n += 1;
    151 			}
    152 			ix += stride;
    153 		}
    154 		// Pairwise sum the accumulators:
    155 		sum = ((s0+s1) + (s2+s3)) + ((s4+s5) + (s6+s7));
    156 
    157 		// Clean-up loop...
    158 		for (; i < N; i++ ) {
    159 			v = X[ ix ];
    160 			if ( v == v ) {
    161 				sum += X[ ix ];
    162 				n += 1;
    163 			}
    164 			ix += stride;
    165 		}
    166 		W[ 0 ] += sum;
    167 		W[ 1 ] += n;
    168 		return;
    169 	}
    170 	// Recurse by dividing by two, but avoiding non-multiples of unroll factor...
    171 	n = N / 2;
    172 	n -= n % 8;
    173 	if ( stride < 0 ) {
    174 		xp1 = (double *)X + ( (n-N)*stride );
    175 		xp2 = (double *)X;
    176 	} else {
    177 		xp1 = (double *)X;
    178 		xp2 = (double *)X + ( n*stride );
    179 	}
    180 	dnansumpw( n, W, xp1, stride );
    181 	dnansumpw( N-n, W, xp2, stride );
    182 }
    183 
    184 /**
    185 * Computes the arithmetic mean of a double-precision floating-point strided array, ignoring `NaN` values and using pairwise summation.
    186 *
    187 * @param N       number of indexed elements
    188 * @param X       input array
    189 * @param stride  stride length
    190 * @return        output value
    191 */
    192 double stdlib_strided_dnanmeanpw( const int64_t N, const double *X, const int64_t stride ) {
    193 	double W[] = { 0.0, 0.0 };
    194 	dnansumpw( N, W, X, stride );
    195 	return W[ 0 ] / W[ 1 ];
    196 }