Skip to content

feat: add C implementation for stats/base/ndarray/sstdevch - #14815

Open
aryan7071 wants to merge 3 commits into
stdlib-js:developfrom
aryan7071:add-c-implementation-stats-base-ndarray-sstdevch
Open

aryan7071 wants to merge 3 commits into
stdlib-js:developfrom
aryan7071:add-c-implementation-stats-base-ndarray-sstdevch

Conversation

@aryan7071

@aryan7071 aryan7071 commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Resolves a part of #14034

Description

What is the purpose of this pull request?

This pull request:

  • adds a C ndarray implementation for stats/base/ndarray/sstdevch
  • adds lib/native.js and updates lib/index.js to prefer the native add-on with a fallback to the existing JavaScript implementation
  • adds test/test.main.js and test/test.native.js to separately exercise the JavaScript and native code paths
  • adds C examples and benchmarks (examples/c/example.c, benchmark/c/benchmark.length.c) alongside a benchmark/benchmark.native.js
  • adds a "C APIs" section to README.md documenting stdlib_stats_sstdevch
  • updates package.json (gypfile, browser, include/src directories) to support building the add-on

Related Issues

Does this pull request have any related issues?

This pull request has the following related issues:

Questions

Any questions for reviewers of this pull request?

No.

Other

Any other information relevant to this pull request? This may include screenshots, references, and/or implementation notes.

No.

Checklist

Please ensure the following tasks are completed before submitting this pull request.

AI Assistance

When authoring the changes proposed in this PR, did you use any kind of AI assistance?

  • Yes
  • No

If you answered "yes" above, how did you use AI assistance?

  • Code generation (e.g., when writing an implementation or fixing a bug)
  • Test/benchmark generation
  • Documentation (including examples)
  • Research and understanding

Disclosure

If you answered "yes" to using AI assistance, please provide a short disclosure indicating how you used AI assistance. This helps reviewers determine how much scrutiny to apply when reviewing your contribution. Example disclosures: "This PR was written primarily by Claude Code." or "I consulted ChatGPT to understand the codebase, but the proposed changes were fully authored manually by myself.".

{{TODO: add disclosure if applicable}}


@stdlib-js/reviewers

@aryan7071
aryan7071 requested a review from a team August 30, 2026 14:11
@stdlib-bot stdlib-bot added Statistics Issue or pull request related to statistical functionality. Needs Review A pull request which needs code review. and removed Needs Review A pull request which needs code review. labels Aug 30, 2026
@stdlib-bot

stdlib-bot commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report

Package Statements Branches Functions Lines
stats/base/ndarray/sstdevch $\\color{red}202/203$
$\\color{green}+0.00\\%$
$\\color{red}5/6$
$\\color{green}+0.00\\%$
$\\color{green}2/2$
$\\color{green}+0.00\\%$
$\\color{red}202/203$
$\\color{green}+0.00\\%$

The above coverage report was generated for the changes in this PR.

@aryan7071
aryan7071 marked this pull request as draft August 30, 2026 14:14
@stdlib-bot stdlib-bot removed the Needs Review A pull request which needs code review. label Aug 30, 2026
@aryan7071
aryan7071 marked this pull request as ready for review August 30, 2026 19:13
@stdlib-bot stdlib-bot added the Needs Review A pull request which needs code review. label Aug 30, 2026
@ujjwalv01

Copy link
Copy Markdown
Member

Could you please apply the same changes discussed in #14718 to this PR as well?

@ujjwalv01 ujjwalv01 added Needs Changes Pull request which needs changes before being merged. and removed Needs Review A pull request which needs code review. labels Sep 7, 2026

@0PrashantYadav0 0PrashantYadav0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several fixes made in #14718 after review apply here too (src/main.c, test/test.js, test/test.native.js).

Comment on lines +17 to +38
*/
#include "stdlib/stats/base/ndarray/sstdevch.h"
#include "stdlib/stats/strided/sstdevch.h"
#include "stdlib/ndarray/ctor.h"
#include "stdlib/blas/base/shared.h"

/**
* Computes the standard deviation of a one-dimensional single-precision floating-point ndarray using a one-pass trial mean algorithm.
*
* @param arrays list containing an input ndarray and a zero-dimensional ndarray specifying the degrees of freedom adjustment
* @return standard deviation
*/
float stdlib_stats_sstdevch( const struct ndarray *arrays[] ) {
const struct ndarray *x = arrays[ 0 ];
const struct ndarray *correction = arrays[ 1 ];
float c;

// For zero-dimensional arrays, `stdlib_ndarray_iget_float32` returns the first (and only) indexed element, regardless of the supplied index:
stdlib_ndarray_iget_float32( correction, 0, &c );

return API_SUFFIX(stdlib_strided_sstdevch_ndarray)( stdlib_ndarray_dimension( x, 0 ), c, (const float *)stdlib_ndarray_data( x ), stdlib_ndarray_stride_elements( x, 0 ), stdlib_ndarray_offset_elements( x ) );
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*/
#include "stdlib/stats/base/ndarray/sstdevch.h"
#include "stdlib/stats/strided/sstdevch.h"
#include "stdlib/ndarray/ctor.h"
#include "stdlib/blas/base/shared.h"
/**
* Computes the standard deviation of a one-dimensional single-precision floating-point ndarray using a one-pass trial mean algorithm.
*
* @param arrays list containing an input ndarray and a zero-dimensional ndarray specifying the degrees of freedom adjustment
* @return standard deviation
*/
float stdlib_stats_sstdevch( const struct ndarray *arrays[] ) {
const struct ndarray *x = arrays[ 0 ];
const struct ndarray *correction = arrays[ 1 ];
float c;
// For zero-dimensional arrays, `stdlib_ndarray_iget_float32` returns the first (and only) indexed element, regardless of the supplied index:
stdlib_ndarray_iget_float32( correction, 0, &c );
return API_SUFFIX(stdlib_strided_sstdevch_ndarray)( stdlib_ndarray_dimension( x, 0 ), c, (const float *)stdlib_ndarray_data( x ), stdlib_ndarray_stride_elements( x, 0 ), stdlib_ndarray_offset_elements( x ) );
}
*/
#include "stdlib/stats/base/ndarray/sstdevch.h"
#include "stdlib/stats/strided/sstdevch.h"
#include "stdlib/ndarray/ctor.h"
#include "stdlib/blas/base/shared.h"
/**
* Computes the standard deviation of a one-dimensional single-precision floating-point ndarray using a one-pass trial mean algorithm.
*
* ## Notes
*
* - The function expects the following ndarrays:
*
* - a one-dimensional input ndarray.
* - a zero-dimensional ndarray specifying the degrees of freedom adjustment.
*
* @param arrays list containing ndarrays
* @return standard deviation
*/
float stdlib_stats_sstdevch( const struct ndarray *arrays[] ) {
const struct ndarray *x = arrays[ 0 ];
float correction;
stdlib_ndarray_get_float32( arrays[ 1 ], NULL, &correction );
return API_SUFFIX(stdlib_strided_sstdevch_ndarray)( stdlib_ndarray_dimension( x, 0 ), correction, (const float *)stdlib_ndarray_data( x ), stdlib_ndarray_stride_elements( x, 0 ), stdlib_ndarray_offset_elements( x ) );
}

Match svariancech/src/main.c (#15008) and sstdevyc (#15003) for the correction argument. Same missing blank line after the license header in src/addon.c:17, examples/c/example.c:17, benchmark/c/benchmark.length.c:17.

Comment on lines +48 to +146
# Define the program used for compiling C source files:
ifdef C_COMPILER
CC := $(C_COMPILER)
else
CC := gcc
endif

# Define the command-line options when compiling C files:
CFLAGS ?= \
-std=c99 \
-O3 \
-Wall \
-pedantic

# Determine whether to generate position independent code ([1][1], [2][2]).
#
# [1]: https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#Code-Gen-Options
# [2]: http://stackoverflow.com/questions/5311515/gcc-fpic-option
ifeq ($(OS), WINNT)
fPIC ?=
else
fPIC ?= -fPIC
endif

# List of includes (e.g., `-I /foo/bar -I /beep/boop/include`):
INCLUDE ?=

# List of source files:
SOURCE_FILES ?=

# List of libraries (e.g., `-lopenblas -lpthread`):
LIBRARIES ?=

# List of library paths (e.g., `-L /foo/bar -L /beep/boop`):
LIBPATH ?=

# List of C targets:
c_targets := example.out


# RULES #

#/
# Compiles source files.
#
# @param {string} [C_COMPILER] - C compiler (e.g., `gcc`)
# @param {string} [CFLAGS] - C compiler options
# @param {(string|void)} [fPIC] - compiler flag determining whether to generate position independent code (e.g., `-fPIC`)
# @param {string} [INCLUDE] - list of includes (e.g., `-I /foo/bar -I /beep/boop/include`)
# @param {string} [SOURCE_FILES] - list of source files
# @param {string} [LIBPATH] - list of library paths (e.g., `-L /foo/bar -L /beep/boop`)
# @param {string} [LIBRARIES] - list of libraries (e.g., `-lopenblas -lpthread`)
#
# @example
# make
#
# @example
# make all
#/
all: $(c_targets)

.PHONY: all

#/
# Compiles C source files.
#
# @private
# @param {string} CC - C compiler (e.g., `gcc`)
# @param {string} CFLAGS - C compiler options
# @param {(string|void)} fPIC - compiler flag determining whether to generate position independent code (e.g., `-fPIC`)
# @param {string} INCLUDE - list of includes (e.g., `-I /foo/bar`)
# @param {string} SOURCE_FILES - list of source files
# @param {string} LIBPATH - list of library paths (e.g., `-L /foo/bar`)
# @param {string} LIBRARIES - list of libraries (e.g., `-lopenblas`)
#/
$(c_targets): %.out: %.c
$(QUIET) $(CC) $(CFLAGS) $(fPIC) $(INCLUDE) -o $@ $(SOURCE_FILES) $< $(LIBPATH) -lm $(LIBRARIES)

#/
# Runs compiled examples.
#
# @example
# make run
#/
run: $(c_targets)
$(QUIET) ./$<

.PHONY: run

#/
# Removes generated files.
#
# @example
# make clean
#/
clean:
$(QUIET) -rm -f *.o *.out

.PHONY: clean

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Define the program used for compiling C source files:
ifdef C_COMPILER
CC := $(C_COMPILER)
else
CC := gcc
endif
# Define the command-line options when compiling C files:
CFLAGS ?= \
-std=c99 \
-O3 \
-Wall \
-pedantic
# Determine whether to generate position independent code ([1][1], [2][2]).
#
# [1]: https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#Code-Gen-Options
# [2]: http://stackoverflow.com/questions/5311515/gcc-fpic-option
ifeq ($(OS), WINNT)
fPIC ?=
else
fPIC ?= -fPIC
endif
# List of includes (e.g., `-I /foo/bar -I /beep/boop/include`):
INCLUDE ?=
# List of source files:
SOURCE_FILES ?=
# List of libraries (e.g., `-lopenblas -lpthread`):
LIBRARIES ?=
# List of library paths (e.g., `-L /foo/bar -L /beep/boop`):
LIBPATH ?=
# List of C targets:
c_targets := example.out
# RULES #
#/
# Compiles source files.
#
# @param {string} [C_COMPILER] - C compiler (e.g., `gcc`)
# @param {string} [CFLAGS] - C compiler options
# @param {(string|void)} [fPIC] - compiler flag determining whether to generate position independent code (e.g., `-fPIC`)
# @param {string} [INCLUDE] - list of includes (e.g., `-I /foo/bar -I /beep/boop/include`)
# @param {string} [SOURCE_FILES] - list of source files
# @param {string} [LIBPATH] - list of library paths (e.g., `-L /foo/bar -L /beep/boop`)
# @param {string} [LIBRARIES] - list of libraries (e.g., `-lopenblas -lpthread`)
#
# @example
# make
#
# @example
# make all
#/
all: $(c_targets)
.PHONY: all
#/
# Compiles C source files.
#
# @private
# @param {string} CC - C compiler (e.g., `gcc`)
# @param {string} CFLAGS - C compiler options
# @param {(string|void)} fPIC - compiler flag determining whether to generate position independent code (e.g., `-fPIC`)
# @param {string} INCLUDE - list of includes (e.g., `-I /foo/bar`)
# @param {string} SOURCE_FILES - list of source files
# @param {string} LIBPATH - list of library paths (e.g., `-L /foo/bar`)
# @param {string} LIBRARIES - list of libraries (e.g., `-lopenblas`)
#/
$(c_targets): %.out: %.c
$(QUIET) $(CC) $(CFLAGS) $(fPIC) $(INCLUDE) -o $@ $(SOURCE_FILES) $< $(LIBPATH) -lm $(LIBRARIES)
#/
# Runs compiled examples.
#
# @example
# make run
#/
run: $(c_targets)
$(QUIET) ./$<
.PHONY: run
#/
# Removes generated files.
#
# @example
# make clean
#/
clean:
$(QUIET) -rm -f *.o *.out
.PHONY: clean
# RULES #
#/
# Removes generated files for building an add-on.
#
# @example
# make clean-addon
#/
clean-addon:
$(QUIET) -rm -f *.o *.node
.PHONY: clean-addon
#/
# Removes generated files.
#
# @example
# make clean
#/
clean: clean-addon
.PHONY: clean

This is a copy of examples/c/Makefile; match svariancech/src/Makefile.

$(QUIET) $(CC) $(CFLAGS) $(fPIC) $(INCLUDE) -o $@ $(SOURCE_FILES) $< $(LIBPATH) -lm $(LIBRARIES)

#/
# Runs compiled examples.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Runs compiled examples.
# Runs compiled benchmarks.

Comment on lines +86 to +92
* Generates a random number on the interval [0, 1).
*
* @return random number
*/
static double rand_double( void ) {
return (double)rand() / ( (double)RAND_MAX + 1.0 );
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Generates a random number on the interval [0, 1).
*
* @return random number
*/
static double rand_double( void ) {
return (double)rand() / ( (double)RAND_MAX + 1.0 );
}
* Generates a random number on the interval [0,1).
*
* @return random number
*/
static float rand_float( void ) {
int r = rand();
return (float)r / ( (float)RAND_MAX + 1.0f );
}

And data[ i ] = ( rand_float() * 20000.0f ) - 10000.0f; at L134, as in svariancech/benchmark/c/benchmark.length.c. Same as #15170 (comment).

Comment on lines +221 to +241
// Create an input ndarray:
const float xbuf[] = { 1.0f, 2.0f, 3.0f, 4.0f };
int64_t xshape[] = { 4 };
int64_t xstrides[] = { STDLIB_NDARRAY_FLOAT32_BYTES_PER_ELEMENT };
int8_t submodes[] = { STDLIB_NDARRAY_INDEX_ERROR };
struct ndarray *x = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)xbuf, 1, xshape, xstrides, 0, STDLIB_NDARRAY_ROW_MAJOR, STDLIB_NDARRAY_INDEX_ERROR, 1, submodes );

// Create a zero-dimensional ndarray specifying the degrees of freedom adjustment:
const float cbuf[] = { 1.0f };
int64_t *cshape = NULL;
int64_t cstrides[] = { 0 };
struct ndarray *correction = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)cbuf, 0, cshape, cstrides, 0, STDLIB_NDARRAY_ROW_MAJOR, STDLIB_NDARRAY_INDEX_ERROR, 0, submodes );

// Compute the standard deviation:
const struct ndarray *arrays[] = { x, correction };
float v = stdlib_stats_sstdevch( arrays );
// returns ~2.0817

// Free allocated memory:
stdlib_ndarray_free( x );
stdlib_ndarray_free( correction );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Create an input ndarray:
const float xbuf[] = { 1.0f, 2.0f, 3.0f, 4.0f };
int64_t xshape[] = { 4 };
int64_t xstrides[] = { STDLIB_NDARRAY_FLOAT32_BYTES_PER_ELEMENT };
int8_t submodes[] = { STDLIB_NDARRAY_INDEX_ERROR };
struct ndarray *x = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)xbuf, 1, xshape, xstrides, 0, STDLIB_NDARRAY_ROW_MAJOR, STDLIB_NDARRAY_INDEX_ERROR, 1, submodes );
// Create a zero-dimensional ndarray specifying the degrees of freedom adjustment:
const float cbuf[] = { 1.0f };
int64_t *cshape = NULL;
int64_t cstrides[] = { 0 };
struct ndarray *correction = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)cbuf, 0, cshape, cstrides, 0, STDLIB_NDARRAY_ROW_MAJOR, STDLIB_NDARRAY_INDEX_ERROR, 0, submodes );
// Compute the standard deviation:
const struct ndarray *arrays[] = { x, correction };
float v = stdlib_stats_sstdevch( arrays );
// returns ~2.0817
// Free allocated memory:
stdlib_ndarray_free( x );
stdlib_ndarray_free( correction );
// Create an ndarray:
const float data[] = { 1.0f, -2.0f, 2.0f };
int64_t shape[] = { 3 };
int64_t strides[] = { STDLIB_NDARRAY_FLOAT32_BYTES_PER_ELEMENT };
int8_t submodes[] = { STDLIB_NDARRAY_INDEX_ERROR };
struct ndarray *x = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)data, 1, shape, strides, 0, STDLIB_NDARRAY_ROW_MAJOR, STDLIB_NDARRAY_INDEX_ERROR, 1, submodes );
// Create an ndarray for specifying the degrees of freedom adjustment:
const float cdata[] = { 1.0f };
int64_t cstrides[] = { 0 };
struct ndarray *corr = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)cdata, 0, NULL, cstrides, 0, STDLIB_NDARRAY_ROW_MAJOR, STDLIB_NDARRAY_INDEX_ERROR, 1, submodes );
// Compute the result:
const struct ndarray *arrays[] = { x, corr };
float v = stdlib_stats_sstdevch( arrays );
// returns ~2.0817f
// Free allocated memory:
stdlib_ndarray_free( x );
stdlib_ndarray_free( corr );

~2.0817 is wrong for { 1, 2, 3, 4 } (that gives ~1.2910); this matches the JS example and svariancech's README.


The function accepts the following arguments:

- **arrays**: `[in] struct ndarray**` list containing the following ndarrays: a one-dimensional input ndarray and a zero-dimensional ndarray specifying the degrees of freedom adjustment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **arrays**: `[in] struct ndarray**` list containing the following ndarrays: a one-dimensional input ndarray and a zero-dimensional ndarray specifying the degrees of freedom adjustment.
- **arrays**: `[in] struct ndarray**` list containing the following ndarrays:
- `[in] struct ndarray*` a one-dimensional input ndarray.
- `[in] struct ndarray*` a zero-dimensional ndarray specifying the degrees of freedom adjustment. Providing a non-zero degrees of freedom adjustment has the effect of adjusting the divisor during the calculation of the [standard deviation][standard-deviation] according to `N-c` where `N` is the number of elements in the input ndarray and `c` corresponds to the provided degrees of freedom adjustment. When computing the [standard deviation][standard-deviation] of a population, setting this parameter to `0` is the standard choice (i.e., the provided array contains data constituting an entire population). When computing the corrected sample [standard deviation][standard-deviation], setting this parameter to `1` is the standard choice (i.e., the provided array contains data sampled from a larger population; this is commonly referred to as Bessel's correction).

Match svariancech's README.

int64_t cstrides[] = { 0 };

// cppcheck-suppress invalidPointerCast
struct ndarray *correction = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)cbuf, 0, cshape, cstrides, offset, order, imode, 0, submodes );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
struct ndarray *correction = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)cbuf, 0, cshape, cstrides, offset, order, imode, 0, submodes );
struct ndarray *correction = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)cbuf, 0, cshape, cstrides, offset, order, imode, nsubmodes, submodes );

Same in README.md:318.

Comment on lines +31 to +37
* var opts = {
* 'dtype': 'float32'
* });
* };
*
* var x = new Float32Vector( [ 1.0, -2.0, 2.0 ] );
*
* var correction = scalar2ndarray( 1.0, opts );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* var opts = {
* 'dtype': 'float32'
* });
* };
*
* var x = new Float32Vector( [ 1.0, -2.0, 2.0 ] );
*
* var correction = scalar2ndarray( 1.0, opts );
* var x = new Float32Vector( [ 1.0, -2.0, 2.0 ] );
* var correction = scalar2ndarray( 1.0, {
* 'dtype': 'float32'
* });

Unrelated to the C implementation; please revert.

@@ -0,0 +1,229 @@
/**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/test.js still duplicates these tests; replace it with the proxyquire dispatcher test from svariancech/test/test.js, as requested in #14718 (comment).

tape( 'the function calculates the standard deviation of a one-dimensional ndarray using a one-pass trial mean algorithm', opts, function test( t ) {
var correction;
var expected;
var optsD;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var optsD;
var opts;

Rename optsD to opts throughout, as in svariancech/test/test.native.js and #14718 (comment).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Changes Pull request which needs changes before being merged. Statistics Issue or pull request related to statistical functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants