not-so-float 0.0.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,23 +1,21 @@
1
- Boost Software License - Version 1.0 - August 17th, 2003
1
+ MIT License
2
2
 
3
- Permission is hereby granted, free of charge, to any person or organization
4
- obtaining a copy of the software and accompanying documentation covered by
5
- this license (the "Software") to use, reproduce, display, distribute,
6
- execute, and transmit the Software, and to prepare derivative works of the
7
- Software, and to permit third-parties to whom the Software is furnished to
8
- do so, all subject to the following:
3
+ Copyright (c) 2025 Victor Poughon
9
4
 
10
- The copyright notices in the Software and this entire statement, including
11
- the above license grant, this restriction and the following disclaimer,
12
- must be included in all copies of the Software, in whole or in part, and
13
- all derivative works of the Software, unless such copies or derivative
14
- works are solely in the form of machine-executable object code generated by
15
- a source language processor.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
16
14
 
17
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
20
- SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
21
- FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
22
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
- DEALINGS IN THE SOFTWARE.
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # Not So Float
2
+
3
+ ![NPM Version](https://img.shields.io/npm/v/not-so-float)
4
+
5
+ **Not So Float** is a pure TypeScript library that implements **Interval Union
6
+ Arithmetic** over IEEE 754 double precision floats (JS numbers).
7
+
8
+ ```typescript
9
+ import * as nsf from "not-so-float";
10
+
11
+ const X = nsf.union([nsf.inter(-10, -1), nsf.inter(100, 120)]);
12
+ const Y = nsf.union([nsf.inter(-10, 2)]);
13
+ const Q = nsf.udiv(X, Y);
14
+
15
+ console.log(`(${X.toString()}) / ${Y.toString()} = ${Q.toString()}`);
16
+ ```
17
+
18
+ ```
19
+ ([-10, -1] U [100, 120]) / [-10, 2] = [-Infinity, -0.49999999999999994] U [0.09999999999999999, Infinity]
20
+ ```
21
+
22
+ References:
23
+
24
+ - [Hickey, T., Ju, Q. and Van Emden, M.H., 2001. Interval arithmetic: From principles to implementation. Journal of the ACM (JACM), 48(5), pp.1038-1068.](https://fab.cba.mit.edu/classes/S62.12/docs/Hickey_interval.pdf)
25
+ - [Schichl, H., Domes, F., Montanher, T. and Kofler, K., 2017. Interval unions. BIT Numerical Mathematics, 57(2), pp.531-556.](https://www.ime.usp.br/~montanhe/unions.pdf)
26
+
27
+ Design goals:
28
+
29
+ - No dependencies
30
+ - Functional (stateless) API
31
+ - Strong focus on simplicity and clarity of implementation
32
+ - No signed zero convention, `-0 === 0` should still be true in the context of interval bounds
33
+ - No empty interval, but empty unions
34
+ - Support division by intervals containing zero without compromise
35
+
36
+ > [!TIP]
37
+ > If you like this open-source project consider [sponsoring me](https://github.com/sponsors/victorpoughon) on GitHub, thank you ❤️
38
+
39
+ ## Installation
40
+
41
+ `not-so-float` build version is available [on npm](https://www.npmjs.com/package/not-so-float):
42
+
43
+ ```sh
44
+ npm install not-so-float
45
+ ```
46
+
47
+ ## Outward Rounding
48
+
49
+ The main interest of interval arithmetic is the inclusion property. Any interval
50
+ operation result is always guaranteed to contain all possible results obtained
51
+ from the underlying real operation. However floating point operations are
52
+ inexact. So we must use _outward rounding_ when computing interval bounds to
53
+ maintain the inclusion property.
54
+
55
+ Unfortunately JS doesn't allow controling the IEEE 754 rounding mode, and rounds
56
+ to the nearest representable float, like most programming languages. This is a
57
+ computer science tragedy of the first order, because IEEE 754 actually specifies
58
+ rounding modes that would allow clean implementations of interval arithmetic
59
+ natively. Instead, we implement two functions: `next()` and `prev()` using typed
60
+ arrays bit manipulation. These functions return the next and previous
61
+ representable floating point value, exactly like the C function
62
+ [`nextafter()`](https://en.cppreference.com/w/c/numeric/math/nextafter).
63
+
64
+ ```typescript
65
+ function next(x: number): number;
66
+ function prev(x: number): number;
67
+ ```
68
+
69
+ By making sure every interval bound operation rounds outward, we can maintain
70
+ the inclusion property even with numerical precision errors inherent to floating
71
+ point.
72
+
73
+ ## Interval
74
+
75
+ An interval is a pair of IEEE 754 double precision numbers called the lower (lo)
76
+ and upper (hi) bounds:
77
+
78
+ $$
79
+ [\text{ lo}, \text{ hi }]
80
+ $$
81
+
82
+ The only restriction is that bounds must always be less or equal: $\text{lo}
83
+ \leq \text{hi}$. The lower bound may be negative infinity, the upper bound may
84
+ be positive infinity. This table describes the set of real numbers that an
85
+ interval represents:
86
+
87
+ <div align="center">
88
+
89
+ | lo | hi | Interval Type | Represents<br> any $x \in \mathbb{R}$ such that |
90
+ | :-------: | :-------: | :-----------: | :---------------------------------------------: |
91
+ | $a$ | $b$ | Real | $a \leq x \leq$ b |
92
+ | $-\infty$ | $b$ | Semi-Infinite | $x \leq b$ |
93
+ | $a$ | $+\infty$ | Semi-Infinite | $a \leq x$ |
94
+ | $-\infty$ | $+\infty$ | Infinite | $x \in \mathbb{R}$ |
95
+
96
+ </div>
97
+
98
+ Where $a$ and $b$ are real IEEE 754 values (not infinite).
99
+ The special case when both bounds are equal is called a _degenerate_ interval.
100
+
101
+ To construct an interval, use the `nsf.inter()` function:
102
+
103
+ ```typescript
104
+ nsf.inter(a: number, b: number) : Interval
105
+ ```
106
+
107
+ For example:
108
+
109
+ ```typescript
110
+ > I = nsf.inter(0, 6)
111
+ Interval { lo: 0, hi: 6 }
112
+ ```
113
+
114
+ The interval class member functions:
115
+
116
+ ```typescript
117
+ class Interval {
118
+ isFull(): boolean;
119
+ isFinite(): boolean;
120
+ isDegenerate(): boolean;
121
+ contains(value: number): boolean;
122
+ superset(other: Interval): boolean;
123
+ subset(other: Interval): boolean;
124
+ toString(numbers?: (x: number) => string): string;
125
+ print(): void;
126
+ }
127
+ ```
128
+
129
+ ## Union
130
+
131
+ A union is an ordered disjoint set of intervals:
132
+
133
+ $$
134
+ [a, b] \cup [c, d] \ldots \cup [e, f]
135
+ $$
136
+
137
+ To construct a union, use the `nsf.union()` function:
138
+
139
+ ```typescript
140
+ nsf.union(intervals: Interval[]) : Union
141
+ ```
142
+
143
+ Union class member functions:
144
+
145
+ ```typescript
146
+ class Union {
147
+ forEach(callback: (x: Interval, index: number) => void): void;
148
+ toString(numbers?: (x: number) => string): string;
149
+ print(): void;
150
+ }
151
+ ```
152
+
153
+ For example:
154
+
155
+ ```typescript
156
+ > const U = nsf.union([nsf.inter(1, 2), nsf.inter(4, 6)])
157
+ > U.toString()
158
+ '[1, 2] U [4, 6]'
159
+ > U.intervals
160
+ [ Interval { lo: 1, hi: 2 }, Interval { lo: 4, hi: 6 } ]
161
+ ```
162
+
163
+ ## Compare
164
+
165
+ Compare intervals and unions with the following utility functions:
166
+
167
+ ```typescript
168
+ function overlap(A: Interval | Union, B: Interval | Union): boolean;
169
+ function disjoint(A: Interval | Union, B: Interval | Union): boolean;
170
+ ```
171
+
172
+ ## Arithmetic
173
+
174
+ <div align="center">
175
+
176
+ | Operation | API function |
177
+ | ------------------------------------------: | :----------------- |
178
+ | Addition | `nsf.add(A, B)` |
179
+ | Unary negation | `nsf.neg(A)` |
180
+ | Subtraction | `nsf.sub(A, B)` |
181
+ | Multiplication | `nsf.mul(A, B)` |
182
+ | Division | `nsf.div(A, B)` |
183
+ | Exponentiation<br>(integer exponent) | `nsf.powInt(A, n)` |
184
+ | Exponentiation<br>(interval/union exponent) | `nsf.pow(A, B)` |
185
+
186
+ </div>
187
+
188
+ > [!IMPORTANT]
189
+ > Note that **every operation returns a union**. This is for consistency, because
190
+ > while some operations (add, sub, mul) between intervals can return a single
191
+ > interval, others (division, exponentiation, log, sqrt) can return zero, one or
192
+ > two disjoint intervals even with interval inputs.
193
+
194
+ ## Unary Functions
195
+
196
+ <div align="center">
197
+
198
+ | Operation | API function |
199
+ | ----------------: | :------------ |
200
+ | Absolute value | `nsf.abs(A)` |
201
+ | Natural logarithm | `nsf.log(A)` |
202
+ | Exponential | `nsf.exp(A)` |
203
+ | Square root | `nsf.sqrt(A)` |
204
+ | Cosine | `nsf.cos(A)` |
205
+ | Sine | `nsf.sin(A)` |
206
+ | Tangent | `nsf.tan(A)` |
207
+
208
+ </div>
209
+
210
+ ## Binary Functions
211
+
212
+ <div align="center">
213
+
214
+ | Operation | API function |
215
+ | --------: | :-------------- |
216
+ | Minimum | `nsf.min(A, B)` |
217
+ | Maximum | `nsf.max(A, B)` |
218
+
219
+ </div>
220
+
221
+ ## Future improvements
222
+
223
+ * Support multiple rounding modes