Skip to content

Commit c4ccb4b

Browse files
committed
feat: add support for single-precision floating-point arithmetic emulation
1 parent b6968e0 commit c4ccb4b

16 files changed

+1857
-58
lines changed

lib/node_modules/@stdlib/math/base/tools/evalpoly-compile/README.md

+61-23
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,20 @@ limitations under the License.
3636
var compile = require( '@stdlib/math/base/tools/evalpoly-compile' );
3737
```
3838

39-
#### compile( c )
39+
#### compile( c\[, options] )
4040

41-
Compiles a module `string` containing an exported function which evaluates a [polynomial][@stdlib/math/base/tools/evalpoly] having coefficients `c`.
41+
Compiles a module string containing an exported function which evaluates a [polynomial][@stdlib/math/base/tools/evalpoly] having coefficients `c`.
4242

4343
```javascript
4444
var str = compile( [ 3.0, 2.0, 1.0 ] );
4545
// returns <string>
4646
```
4747

48-
In the example above, the output `string` would correspond to the following module:
48+
The function supports the following `options`:
49+
50+
- **dtype**: input argument floating-point data type (e.g., `float64` or `float32`). Default: `'float64'`.
51+
52+
In the example above, the output string would correspond to the following module:
4953

5054
<!-- eslint-disable no-unused-expressions -->
5155

@@ -71,7 +75,7 @@ function evalpoly( x ) {
7175
if ( x === 0.0 ) {
7276
return 3.0;
7377
}
74-
return 3.0 + (x * (2.0 + (x * 1.0))); // eslint-disable-line max-len
78+
return 3.0 + (x * (2.0 + (x * 1.0)));
7579
}
7680

7781

@@ -82,6 +86,55 @@ module.exports = evalpoly;
8286

8387
The coefficients should be ordered in **ascending** degree, thus matching summation notation.
8488

89+
By default, the function assumes double-precision floating-point arithmetic. To emulate single-precision floating-point arithmetic, set the `dtype` option to `'float32'`.
90+
91+
```javascript
92+
var str = compile( [ 3.0, 2.0, 1.0 ], {
93+
'dtype': 'float32'
94+
});
95+
// returns <string>
96+
```
97+
98+
In the previous example, the output string would correspond to the following module:
99+
100+
<!-- eslint-disable no-unused-expressions -->
101+
102+
```javascript
103+
'use strict';
104+
105+
// MODULES //
106+
107+
var float64ToFloat32 = require( '@stdlib/number/float64/base/to-float32' );
108+
109+
110+
// MAIN //
111+
112+
/**
113+
* Evaluates a polynomial.
114+
*
115+
* ## Notes
116+
*
117+
* - The implementation uses [Horner's rule][horners-method] for efficient computation.
118+
*
119+
* [horners-method]: https://en.wikipedia.org/wiki/Horner%27s_method
120+
*
121+
* @private
122+
* @param {number} x - value at which to evaluate the polynomial
123+
* @returns {number} evaluated polynomial
124+
*/
125+
function evalpoly( x ) {
126+
if ( x === 0.0 ) {
127+
return 3.0;
128+
}
129+
return float64ToFloat32(3.0 + float64ToFloat32(x * float64ToFloat32(2.0 + float64ToFloat32(x * 1.0)))); // eslint-disable-line max-len
130+
}
131+
132+
133+
// EXPORTS //
134+
135+
module.exports = evalpoly;
136+
```
137+
85138
</section>
86139

87140
<!-- /.usage -->
@@ -103,29 +156,14 @@ The coefficients should be ordered in **ascending** degree, thus matching summat
103156
<!-- eslint no-undef: "error" -->
104157

105158
```javascript
106-
var randu = require( '@stdlib/random/base/randu' );
107-
var round = require( '@stdlib/math/base/special/round' );
108-
var Float64Array = require( '@stdlib/array/float64' );
159+
var discreteUniform = require( '@stdlib/random/array/discrete-uniform' );
109160
var compile = require( '@stdlib/math/base/tools/evalpoly-compile' );
110161

111-
var coef;
112-
var sign;
113-
var str;
114-
var i;
115-
116-
// Create an array of random coefficients...
117-
coef = new Float64Array( 10 );
118-
for ( i = 0; i < coef.length; i++ ) {
119-
if ( randu() < 0.5 ) {
120-
sign = -1.0;
121-
} else {
122-
sign = 1.0;
123-
}
124-
coef[ i ] = sign * round( randu()*100.0 );
125-
}
162+
// Create an array of random coefficients:
163+
var coef = discreteUniform( 10, -100, 100 );
126164

127165
// Compile a module for evaluating a polynomial:
128-
str = compile( coef );
166+
var str = compile( coef );
129167
console.log( str );
130168
```
131169

lib/node_modules/@stdlib/math/base/tools/evalpoly-compile/docs/types/index.d.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,28 @@
1818

1919
// TypeScript Version: 4.1
2020

21+
/**
22+
* Interface describing function options.
23+
*/
24+
interface Options {
25+
/**
26+
* Input value floating-point data type (e.g., `float64` or `float32`). Default: `'float64'`.
27+
*/
28+
dtype?: 'float64' | 'float32';
29+
}
30+
2131
/**
2232
* Compiles a module string which exports a function for evaluating a polynomial.
2333
*
2434
* @param c - polynomial coefficients sorted in ascending degree
35+
* @param options - function options
2536
* @returns module string exporting a function for evaluating a polynomial
2637
*
2738
* @example
2839
* var str = compile( [ 3.0, 2.0, 1.0 ] );
2940
* // returns <string>
3041
*/
31-
declare function compile( c: Array<number> ): string;
42+
declare function compile( c: Array<number>, options?: Options ): string;
3243

3344

3445
// EXPORTS //

lib/node_modules/@stdlib/math/base/tools/evalpoly-compile/docs/types/test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import compile = require( './index' );
2424
// The function returns a string...
2525
{
2626
compile( [ 3.0, 2.0, 1.0 ] ); // $ExpectType string
27+
compile( [ 3.0, 2.0, 1.0 ], { 'dtype': 'float32' } ); // $ExpectType string
2728
}
2829

2930
// The compiler throws an error if the function is provided a first argument which is not an array of numbers...
@@ -34,6 +35,31 @@ import compile = require( './index' );
3435
compile( 123 ); // $ExpectError
3536
compile( {} ); // $ExpectError
3637
compile( ( x: number ): number => x ); // $ExpectError
38+
39+
compile( true, {} ); // $ExpectError
40+
compile( false, {} ); // $ExpectError
41+
compile( 'abc', {} ); // $ExpectError
42+
compile( 123, {} ); // $ExpectError
43+
compile( {}, {} ); // $ExpectError
44+
compile( ( x: number ): number => x, {} ); // $ExpectError
45+
}
46+
47+
// The compiler throws an error if the function is provided a second argument which is not an object...
48+
{
49+
compile( [ 3.0, 2.0, 1.0 ], true ); // $ExpectError
50+
compile( [ 3.0, 2.0, 1.0 ], false ); // $ExpectError
51+
compile( [ 3.0, 2.0, 1.0 ], 'abc' ); // $ExpectError
52+
compile( [ 3.0, 2.0, 1.0 ], 123 ); // $ExpectError
53+
compile( [ 3.0, 2.0, 1.0 ], ( x: number ): number => x ); // $ExpectError
54+
}
55+
56+
// The compiler throws an error if the function is provided an invalid `dtype` option...
57+
{
58+
compile( [ 3.0, 2.0, 1.0 ], { 'dtype': true } ); // $ExpectError
59+
compile( [ 3.0, 2.0, 1.0 ], { 'dtype': false } ); // $ExpectError
60+
compile( [ 3.0, 2.0, 1.0 ], { 'dtype': [] } ); // $ExpectError
61+
compile( [ 3.0, 2.0, 1.0 ], { 'dtype': 123 } ); // $ExpectError
62+
compile( [ 3.0, 2.0, 1.0 ], { 'dtype': ( x: number ): number => x } ); // $ExpectError
3763
}
3864

3965
// The compiler throws an error if the function is provided an insufficient number of arguments...

lib/node_modules/@stdlib/math/base/tools/evalpoly-compile/examples/index.js

+4-19
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
'use strict';
2020

2121
var resolve = require( 'path' ).resolve;
22-
var randu = require( '@stdlib/random/base/randu' );
23-
var round = require( '@stdlib/math/base/special/round' );
24-
var Float64Array = require( '@stdlib/array/float64' );
22+
var discreteUniform = require( '@stdlib/random/array/discrete-uniform' );
2523
var tryRequire = require( '@stdlib/utils/try-require' );
2624

2725
var compile = tryRequire( resolve( __dirname, '..', 'lib' ) );
@@ -32,23 +30,10 @@ if ( compile instanceof Error ) {
3230
}
3331

3432
function main() {
35-
var coef;
36-
var sign;
37-
var str;
38-
var i;
39-
40-
// Create an array of random coefficients...
41-
coef = new Float64Array( 10 );
42-
for ( i = 0; i < coef.length; i++ ) {
43-
if ( randu() < 0.5 ) {
44-
sign = -1.0;
45-
} else {
46-
sign = 1.0;
47-
}
48-
coef[ i ] = sign * round( randu()*100.0 );
49-
}
33+
// Create an array of random coefficients:
34+
var coef = discreteUniform( 10, -100, 100 );
5035

5136
// Compile a module for evaluating a polynomial:
52-
str = compile( coef );
37+
var str = compile( coef );
5338
console.log( str );
5439
}

lib/node_modules/@stdlib/math/base/tools/evalpoly-compile/lib/main.js

+53-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var join = require( 'path' ).join;
2424
var readFile = require( '@stdlib/fs/read-file' ).sync;
2525
var replace = require( '@stdlib/string/replace' );
2626
var isInteger = require( '@stdlib/assert/is-integer' ).isPrimitive;
27+
var Float32Array = require( '@stdlib/array/float32' );
2728

2829

2930
// VARIABLES //
@@ -35,9 +36,15 @@ var dir = join( __dirname, 'templates' );
3536

3637
// Templates:
3738
var SINGLE_COEFFICIENT_TEMPLATE = readFile( join( dir, 'single_coefficient.js.txt' ), opts ); // eslint-disable-line id-length
39+
3840
var EVALPOLY_TEMPLATE = readFile( join( dir, 'evalpoly.js.txt' ), opts );
41+
var EVALPOLY_FLOAT32_TEMPLATE = readFile( join( dir, 'evalpoly.float32.js.txt' ), opts );
42+
3943
var EMPTY_TEMPLATE = readFile( join( dir, 'empty.js.txt' ), opts );
44+
4045
var LOOP_TEMPLATE = readFile( join( dir, 'loop.js.txt' ), opts );
46+
var LOOP_FLOAT32_TEMPLATE = readFile( join( dir, 'loop.float32.js.txt' ), opts );
47+
4148
var MAX_CHARS = 68; // max-len (80) - chars already in line ('tab': 4, 'return ': 7, ';': 1)
4249

4350

@@ -47,25 +54,39 @@ var MAX_CHARS = 68; // max-len (80) - chars already in line ('tab': 4, 'return '
4754
* Compiles a module string which exports a function for evaluating a polynomial.
4855
*
4956
* @param {NumericArray} c - polynomial coefficients sorted in ascending degree
57+
* @param {Options} [options] - function options
58+
* @param {string} [options.dtype='float64'] - input value floating-point data type
5059
* @returns {string} module string exporting a function for evaluating a polynomial
5160
*
5261
* @example
5362
* var str = compile( [ 3.0, 2.0, 1.0 ] );
5463
* // returns <string>
5564
*/
56-
function compile( c ) {
65+
function compile( c, options ) {
5766
var horner;
67+
var opts;
68+
var tmpl;
5869
var str;
5970
var n;
6071
var m;
6172
var i;
6273

74+
opts = {
75+
'dtype': 'float64'
76+
};
77+
if ( arguments.length > 1 ) {
78+
opts.dtype = options.dtype || opts.dtype;
79+
}
6380
n = c.length;
6481

6582
// If no coefficients, the function always returns 0...
6683
if ( n === 0 ) {
6784
return EMPTY_TEMPLATE;
6885
}
86+
if ( opts.dtype === 'float32' ) {
87+
// Ensure that coefficients have been converted to single-precision:
88+
c = new Float32Array( c );
89+
}
6990
// If only one coefficient, the function always returns that coefficient...
7091
if ( n === 1 ) {
7192
str = c[ 0 ].toString();
@@ -88,17 +109,34 @@ function compile( c ) {
88109
str += ',\n';
89110
}
90111
}
91-
return replace( LOOP_TEMPLATE, '{{coefficients}}', str );
112+
if ( opts.dtype === 'float32' ) {
113+
tmpl = LOOP_FLOAT32_TEMPLATE;
114+
} else {
115+
tmpl = LOOP_TEMPLATE;
116+
}
117+
return replace( tmpl, '{{coefficients}}', str );
92118
}
93119
// If more than one coefficient, apply Horner's method...
94-
horner = c[ 0 ].toString();
120+
if ( opts.dtype === 'float32' ) {
121+
horner = 'float64ToFloat32(';
122+
} else {
123+
horner = '';
124+
}
125+
horner += c[ 0 ].toString();
95126
if ( isInteger( c[ 0 ] ) ) {
96127
horner += '.0';
97128
}
98129
for ( i = 1; i < n; i++ ) {
99-
horner += ' + (x * ';
100-
if ( i < m ) {
101-
horner += '(';
130+
if ( opts.dtype === 'float32' ) {
131+
horner += ' + float64ToFloat32(x * ';
132+
if ( i < m ) {
133+
horner += 'float64ToFloat32(';
134+
}
135+
} else {
136+
horner += ' + (x * ';
137+
if ( i < m ) {
138+
horner += '(';
139+
}
102140
}
103141
horner += c[ i ].toString();
104142
if ( isInteger( c[ i ] ) ) {
@@ -109,11 +147,19 @@ function compile( c ) {
109147
for ( i = 0; i < (2*(n-1))-1; i++ ) {
110148
horner += ')';
111149
}
150+
if ( opts.dtype === 'float32' ) {
151+
horner += ')';
152+
}
112153
str = c[ 0 ].toString();
113154
if ( isInteger( c[ 0 ] ) ) {
114155
str += '.0';
115156
}
116-
str = replace( EVALPOLY_TEMPLATE, '{{coefficient}}', str );
157+
if ( opts.dtype === 'float32' ) {
158+
tmpl = EVALPOLY_FLOAT32_TEMPLATE;
159+
} else {
160+
tmpl = EVALPOLY_TEMPLATE;
161+
}
162+
str = replace( tmpl, '{{coefficient}}', str );
117163
str = replace( str, '{{horner}}', horner );
118164
return replace( str, '{{eslint}}', ( horner.length > MAX_CHARS ) ? ' // eslint-disable-line max-len' : '' );
119165
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
// MODULES //
4+
5+
var float64ToFloat32 = require( '@stdlib/number/float64/base/to-float32' );
6+
7+
8+
// MAIN //
9+
10+
/**
11+
* Evaluates a polynomial.
12+
*
13+
* ## Notes
14+
*
15+
* - The implementation uses [Horner's rule][horners-method] for efficient computation.
16+
*
17+
* [horners-method]: https://en.wikipedia.org/wiki/Horner%27s_method
18+
*
19+
* @private
20+
* @param {number} x - value at which to evaluate the polynomial
21+
* @returns {number} evaluated polynomial
22+
*/
23+
function evalpoly( x ) {
24+
if ( x === 0.0 ) {
25+
return {{coefficient}};
26+
}
27+
return {{horner}};{{eslint}}
28+
}
29+
30+
31+
// EXPORTS //
32+
33+
module.exports = evalpoly;

0 commit comments

Comments
 (0)