Skip to content

Commit fe7b8b6

Browse files
committed
feat: add initial implementation of number/float64/base/ulp-difference
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: passed - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: passed - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent fba6fde commit fe7b8b6

File tree

4 files changed

+361
-0
lines changed

4 files changed

+361
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 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+
'use strict';
20+
21+
var EPS = require( '@stdlib/constants/float64/eps' );
22+
var ulpdiff = require( './../lib' );
23+
24+
var d = ulpdiff( 1.0, 1.0+EPS );
25+
console.log( d );
26+
27+
d = ulpdiff( 5.8364e-319, 5.8367e-319 );
28+
console.log( d );
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 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+
'use strict';
20+
21+
/**
22+
* Compute the number of representable double-precision floating-point values that separate two double-precision floating-point numbers along the real number line.
23+
*
24+
* @module @stdlib/number/float64/base/ulp-difference
25+
*
26+
* @example
27+
* var EPS = require( '@stdlib/constants/float64/eps' );
28+
* var ulpdiff = require( '@stdlib/number/float64/base/ulp-difference' );
29+
*
30+
* var d = ulpdiff( 1.0, EPS );
31+
* // returns 1.0
32+
*
33+
* d = ulpdiff( EPS, 1.0 );
34+
* // returns 1.0
35+
*
36+
* d = ulpdiff( 1.0, EPS+EPS );
37+
* // returns 2.0
38+
*
39+
* d = ulpdiff( 1.0, NaN );
40+
* // returns NaN
41+
*
42+
* d = ulpdiff( NaN, 1.0 );
43+
* // returns NaN
44+
*
45+
* d = ulpdiff( NaN, NaN );
46+
* // returns NaN
47+
*/
48+
49+
// MODULES //
50+
51+
var main = require( './main.js' );
52+
53+
54+
// EXPORTS //
55+
56+
module.exports = main;
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 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+
'use strict';
20+
21+
// MODULES //
22+
23+
var isnan = require( '@stdlib/math/base/assert/is-nan' );
24+
var SIGN_MASK = require( '@stdlib/constants/float64/high-word-sign-mask' );
25+
var toWords = require( '@stdlib/number/float64/base/to-words' ).assign;
26+
var Uint32Array = require( '@stdlib/array/uint32' );
27+
28+
29+
// VARIABLES //
30+
31+
var WX = new Uint32Array( 2 ); // WARNING: not thread safe
32+
var WY = new Uint32Array( 2 );
33+
var WZ = new Uint32Array( 2 );
34+
35+
// 2^32:
36+
var TWO_32 = 4294967296;
37+
38+
39+
// FUNCTIONS //
40+
41+
/**
42+
* Converts the high and low words of a double-precision floating-point number to a lexicographically ordered integer.
43+
*
44+
* ## Notes
45+
*
46+
* - This function mutates the input array.
47+
*
48+
* @private
49+
* @param {Array<integer>} words - high and low words
50+
* @returns {Array<integer>} input array
51+
*/
52+
function monotoneKey( words ) {
53+
if ( words[ 0 ]&SIGN_MASK ) { // x < 0
54+
words = negate( words ); // maps -∞ to 0
55+
} else { // x >= 0
56+
words[ 0 ] |= SIGN_MASK; // push +0 to just above -0
57+
}
58+
return words;
59+
}
60+
61+
/**
62+
* Perform two's-complement negation.
63+
*
64+
* ## Notes
65+
*
66+
* - This function mutates the input array.
67+
*
68+
* @private
69+
* @param {Array<integer>} words - high and low words
70+
* @returns {Array<integer>} input array
71+
*/
72+
function negate( words ) {
73+
words[ 0 ] = ~words[ 0 ];
74+
words[ 1 ] = ~words[ 1 ];
75+
words[ 1 ] += 1;
76+
77+
// Handle the carry into the high word...
78+
if ( words[ 1 ] === 0 ) {
79+
words[ 0 ] += 1;
80+
}
81+
return words;
82+
}
83+
84+
/**
85+
* Returns the ordering of two double-precision floating-point numbers according to their lexicographically ordered high and low words.
86+
*
87+
* @private
88+
* @param {Array<integer>} wa - high and low words for first value
89+
* @param {Array<integer>} wb - high and low words for second value
90+
* @returns {integer} relative ordering
91+
*/
92+
function compare( wa, wb ) {
93+
if ( wa[ 0 ] > wb[ 0 ] ) {
94+
return 1;
95+
}
96+
if ( wa[ 0 ] < wb[ 0 ] ) {
97+
return -1;
98+
}
99+
if ( wa[ 1 ] > wb[ 1 ] ) {
100+
return 1;
101+
}
102+
if ( wa[ 1 ] < wb[ 1 ] ) {
103+
return -1;
104+
}
105+
return 0;
106+
}
107+
108+
/**
109+
* Performs double-word subtraction.
110+
*
111+
* @private
112+
* @param {Array<integer>} wa - high and low words for first value
113+
* @param {Array<integer>} wb - high and low words for second value
114+
* @param {Array<integer>} wc - output array
115+
* @returns {Array<integer>} output array
116+
*/
117+
function subtract( wa, wb, wc ) {
118+
var ha;
119+
var hb;
120+
var la;
121+
var lb;
122+
123+
ha = wa[ 0 ];
124+
la = wa[ 1 ];
125+
hb = wb[ 0 ];
126+
lb = wb[ 1 ];
127+
128+
if ( la >= lb ) {
129+
wc[ 0 ] = ha - hb;
130+
wc[ 1 ] = la - lb;
131+
} else {
132+
wc[ 0 ] = ( ha - hb - 1 ); // wrap
133+
wc[ 1 ] = ( la + TWO_32 ) - lb; // borrow
134+
}
135+
return wc;
136+
}
137+
138+
139+
// MAIN //
140+
141+
/**
142+
* Computes the number of representable double-precision floating-point values that separate two double-precision floating-point numbers along the real number line.
143+
*
144+
* ## Notes
145+
*
146+
* - Adjacent double-precision floating-point numbers differ by 1 ulp (unit in the last place).
147+
*
148+
* @param {number} x - first value
149+
* @param {number} y - second value
150+
* @returns {number} result
151+
*
152+
* @example
153+
* var EPS = require( '@stdlib/constants/float64/eps' );
154+
*
155+
* var d = ulpdiff( 1.0, 1.0+EPS );
156+
* // returns 1.0
157+
*
158+
* d = ulpdiff( 1.0+EPS, 1.0 );
159+
* // returns 1.0
160+
*
161+
* d = ulpdiff( 1.0, 1.0+EPS+EPS );
162+
* // returns 2.0
163+
*
164+
* d = ulpdiff( 1.0, NaN );
165+
* // returns NaN
166+
*
167+
* d = ulpdiff( NaN, 1.0 );
168+
* // returns NaN
169+
*
170+
* d = ulpdiff( NaN, NaN );
171+
* // returns NaN
172+
*/
173+
function ulpdiff( x, y ) {
174+
var ord;
175+
var wx;
176+
var wy;
177+
var wz;
178+
if ( isnan( x ) || isnan( y ) ) {
179+
return NaN;
180+
}
181+
// Convert input values to high and low words:
182+
wx = toWords( x, WX, 1, 0 );
183+
wy = toWords( y, WY, 1, 0 );
184+
185+
// Convert the values to lexicographically order integers:
186+
wx = monotoneKey( wx );
187+
wy = monotoneKey( wy );
188+
189+
// Determine the relative ordering of the two values so that we always subtract the smaller value from the larger value and ensure that the result is always >= 0:
190+
ord = compare( wx, wy );
191+
192+
// Perform subtraction...
193+
if ( ord === 0 ) {
194+
// Identical encoding:
195+
return 0;
196+
}
197+
if ( ord === 1 ) {
198+
wz = subtract( wx, wy, WZ );
199+
} else { // ord === -1
200+
wz = subtract( wy, wx, WZ );
201+
}
202+
// Return a double as a result, which is fine for ≤2^53 ulps:
203+
return ( wz[ 0 ]*TWO_32 ) + wz[ 1 ];
204+
}
205+
206+
207+
// EXPORTS //
208+
209+
module.exports = ulpdiff;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"name": "@stdlib/number/float64/base/ulp-difference",
3+
"version": "0.0.0",
4+
"description": "Compute the number of representable double-precision floating-point values that separate two double-precision floating-point numbers along the real number line.",
5+
"license": "Apache-2.0",
6+
"author": {
7+
"name": "The Stdlib Authors",
8+
"url": "https://github.com/stdlib-js/stdlib/graphs/contributors"
9+
},
10+
"contributors": [
11+
{
12+
"name": "The Stdlib Authors",
13+
"url": "https://github.com/stdlib-js/stdlib/graphs/contributors"
14+
}
15+
],
16+
"main": "./lib",
17+
"directories": {
18+
"benchmark": "./benchmark",
19+
"doc": "./docs",
20+
"example": "./examples",
21+
"lib": "./lib",
22+
"test": "./test"
23+
},
24+
"types": "./docs/types",
25+
"scripts": {},
26+
"homepage": "https://github.com/stdlib-js/stdlib",
27+
"repository": {
28+
"type": "git",
29+
"url": "git://github.com/stdlib-js/stdlib.git"
30+
},
31+
"bugs": {
32+
"url": "https://github.com/stdlib-js/stdlib/issues"
33+
},
34+
"dependencies": {},
35+
"devDependencies": {},
36+
"engines": {
37+
"node": ">=0.10.0",
38+
"npm": ">2.7.0"
39+
},
40+
"os": [
41+
"aix",
42+
"darwin",
43+
"freebsd",
44+
"linux",
45+
"macos",
46+
"openbsd",
47+
"sunos",
48+
"win32",
49+
"windows"
50+
],
51+
"keywords": [
52+
"stdlib",
53+
"stdmath",
54+
"mathematics",
55+
"math",
56+
"relative",
57+
"difference",
58+
"abs",
59+
"diff",
60+
"error",
61+
"distance",
62+
"dist",
63+
"numbers",
64+
"ulp",
65+
"float64",
66+
"double"
67+
]
68+
}

0 commit comments

Comments
 (0)