Skip to content

feat: add C implementation for stats/base/ndarray/sstdev - #14718

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

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

Conversation

@aryan7071

Copy link
Copy Markdown
Contributor

Resolves a part of #14034

Description

What is the purpose of this pull request?

This pull request:

  • adds a native C implementation for computing the standard deviation of a one-dimensional single-precision floating-point ndarray (sstdev).
  • adds the required native addon build configuration, C source files, headers, and JavaScript bindings.
  • updates the test suite and benchmarks to cover and utilize the new native C addon.

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 27, 2026 17:46
@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 27, 2026
@aryan7071
aryan7071 marked this pull request as draft August 28, 2026 09:59
@stdlib-bot stdlib-bot removed the Needs Review A pull request which needs code review. label Aug 28, 2026
@stdlib-bot

stdlib-bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report

Package Statements Branches Functions Lines
stats/base/ndarray/sstdev $\\color{green}203/203$
$\\color{green}+0.00\\%$
$\\color{green}7/7$
$\\color{green}+0.00\\%$
$\\color{green}2/2$
$\\color{green}+0.00\\%$
$\\color{green}203/203$
$\\color{green}+0.00\\%$

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

@aryan7071
aryan7071 marked this pull request as ready for review August 29, 2026 21:48

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.

Please update the require statement in benchmark.js to point to the main package export './../lib/main.js' instead of './../lib/' so that the benchmark correctly tests the main entry point.

/**
* Computes the standard deviation of a one-dimensional single-precision floating-point ndarray.
*
* @param arrays list containing an 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
* @param arrays list containing an input ndarray and a zero-dimensional ndarray specifying the degrees of freedom adjustment
* ## 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

*/
float stdlib_stats_sstdev( const struct ndarray *arrays[] ) {
const struct ndarray *x = arrays[ 0 ];
const struct ndarray *correction = arrays[ 1 ];

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
const struct ndarray *correction = arrays[ 1 ];

const struct ndarray *x = arrays[ 0 ];
const struct ndarray *correction = arrays[ 1 ];

float c;

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
float c;
float correction;
stdlib_ndarray_get_float32( arrays[ 1 ], NULL, &correction );

// 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_sstdev_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
return API_SUFFIX(stdlib_strided_sstdev_ndarray)( stdlib_ndarray_dimension( x, 0 ), c, (const float *)stdlib_ndarray_data( x ), stdlib_ndarray_stride_elements( x, 0 ), stdlib_ndarray_offset_elements( x ) );
return API_SUFFIX(stdlib_strided_sstdev_ndarray)( stdlib_ndarray_dimension( x, 0 ), correction, (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.

The test/test.js file should only test the dynamic dispatcher (using proxyquire to mock tryRequire), rather than running the mathematical tests. The actual correctness tests should live exclusively in test.main.js (for JS) and test.native.js (for C).

Right now, test.js is just a copy of test.main.js. Please update test/test.js so it matches the dispatcher testing pattern;

refer this : lib/node_modules/%40stdlib/stats/base/ndarray/dstdev/test/test.js

tape( 'the function calculates the standard deviation of a one-dimensional ndarray', 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;


optsD = {
'dtype': 'float32'
};

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
};
opts = {
'dtype': 'float32'
};

};

x = new Float32Array( [ 1.0, -2.0, -4.0, 5.0, 0.0, 3.0 ] );
correction = scalar2ndarray( 1.0, 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
correction = scalar2ndarray( 1.0, optsD );
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.

The local dtype options variable is named optsD throughout this file , please rename it to opts (consistent with the established convention) across all test callbacks.

@ujjwalv01

Copy link
Copy Markdown
Member

@aryan7071 , It looks like lib/index.js was missed in this PR. Since you're adding native C bindings, you need to update lib/index.js to dynamically dispatch to the native implementation (using tryRequire) ,. Please apply the standard tryRequire dispatcher block in lib/index.js;

@ujjwalv01

Copy link
Copy Markdown
Member

Left few suggestions;

@aryan7071

Copy link
Copy Markdown
Contributor Author

Hi @ujjwalv01 , thanks for the detailed review! I've pushed a fix commit addressing all of these:

src/main.c: Simplified as suggested removed the intermediate correction ndarray variable and switched to stdlib_ndarray_get_float32( arrays[ 1 ], NULL, &correction ), and added the ## Notes block documenting the expected ndarrays list.
lib/index.js: Added the missing native-fallback dispatcher (tryRequire block) this was indeed missed.
test/test.js: Rewritten to test only the dispatcher logic via proxyquire, matching dstdev's pattern. The actual correctness tests now live exclusively in test.main.js and test.native.js.
test/test.native.js: Renamed optsD → opts throughout for consistency.
benchmark/benchmark.js: Fixed the require path to './../lib/main.js'.

All three test files pass locally (test.js 4/4, test.main.js 12/12, test.native.js 12/12) and the addon rebuilds cleanly.

@ujjwalv01

Copy link
Copy Markdown
Member

Looks good to me. Thanks @aryan7071 , for the changes;

cc : @kgryte

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

Please add backticks around the package path in the PR title (feat: add C implementation for stats/base/ndarray/sstdev) and fill in the AI disclosure, which still says {{TODO: add disclosure if applicable}}.

Comment on lines +206 to +228
// 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[] = {};
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_sstdev( 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[] = {};
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_sstdev( 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_sstdev( arrays );
// returns ~2.0817
// Free allocated memory:
stdlib_ndarray_free( x );
stdlib_ndarray_free( corr );

With { 1, 2, 3, 4 } this snippet returns 1.290994, not ~2.0817 (compiled and ran it). int64_t cshape[] = {} is also not valid C99 (-pedantic: "zero size arrays are an extension"). Match svariancech/README.md.


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 dstdev/README.md.

Comment on lines +63 to +69
// Create a zero-dimensional ndarray specifying the degrees of freedom adjustment:
const float cbuf[] = { 1.0f };
int64_t *cshape = NULL;
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
// Create a zero-dimensional ndarray specifying the degrees of freedom adjustment:
const float cbuf[] = { 1.0f };
int64_t *cshape = NULL;
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 );
// Create a data buffer for an ndarray specifying the degrees of freedom adjustment:
const float cbuf[] = { 1.0f };
// Specify the array strides:
int64_t cstrides[] = { 0 };
// Create an ndarray for the degrees of freedom adjustment:
// cppcheck-suppress invalidPointerCast
struct ndarray *correction = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT32, (uint8_t *)cbuf, 0, NULL, cstrides, 0, order, imode, nsubmodes, submodes );

The correction ndarray shouldn't reuse x's offset; match svariancech/examples/c/example.c. Same in README.md:301-306 and benchmark/c/benchmark.length.c:141-144.

Comment on lines +87 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 );

Line 135 then becomes data[ i ] = ( rand_float() * 20000.0f ) - 10000.0f;. See #15170 (comment) and svariancech/benchmark/c/benchmark.length.c.

$(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 +48 to +61
// Create a const-qualified view of the argument pointer list:
const struct ndarray *arr[ 2 ] = {
arrays[ 0 ],
arrays[ 1 ]
};
// Perform computation:
STDLIB_NAPI_CREATE_DOUBLE( env, (double)stdlib_stats_sstdev( arr ), v );

// Free allocated memory:
stdlib_ndarray_free( arrays[ 0 ] );
arrays[ 0 ] = NULL;

stdlib_ndarray_free( arrays[ 1 ] );
arrays[ 1 ] = NULL;

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 a const-qualified view of the argument pointer list:
const struct ndarray *arr[ 2 ] = {
arrays[ 0 ],
arrays[ 1 ]
};
// Perform computation:
STDLIB_NAPI_CREATE_DOUBLE( env, (double)stdlib_stats_sstdev( arr ), v );
// Free allocated memory:
stdlib_ndarray_free( arrays[ 0 ] );
arrays[ 0 ] = NULL;
stdlib_ndarray_free( arrays[ 1 ] );
arrays[ 1 ] = NULL;
// Create a const-qualified view of the argument pointer list:
const struct ndarray *arr[ 2 ] = { arrays[ 0 ], arrays[ 1 ] };
// Perform computation:
STDLIB_NAPI_CREATE_DOUBLE( env, (double)stdlib_stats_sstdev( arr ), v );
// Free allocated memory:
stdlib_ndarray_free( arrays[ 0 ] );
stdlib_ndarray_free( arrays[ 1 ] );
arrays[ 0 ] = NULL;
arrays[ 1 ] = NULL;

Match svariancech/src/addon.c.

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'
* });

This change isn't needed; keep the example as in lib/main.js. Same in lib/native.js:48-54, see #15170 (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 Review A pull request which needs code review. Statistics Issue or pull request related to statistical functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants