@cloudglides/nox 1.1.4 → 1.1.6

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/PERFORMANCE.md ADDED
@@ -0,0 +1,69 @@
1
+ # Performance Benchmarks
2
+
3
+ Benchmarks run on Node.js v20.19.6 on modern hardware.
4
+
5
+ ## RNG Operations (per second)
6
+
7
+ | Operation | Rate |
8
+ |-----------|------|
9
+ | `nextFloat()` | 2.5M |
10
+ | `nextInt(100)` | 2.8M |
11
+ | `int(1, 100)` | 1.8M |
12
+ | `bool()` | 3.5M |
13
+ | `choice()` | 1.5M |
14
+
15
+ ## Batch Operations
16
+
17
+ | Operation | Rate |
18
+ |-----------|------|
19
+ | `floats(1000)` | 2.5k/sec |
20
+ | `ints(1000, 100)` | 2.2k/sec |
21
+ | `bools(1000)` | 2.8k/sec |
22
+
23
+ ## Distributions
24
+
25
+ | Distribution | Rate |
26
+ |--------------|------|
27
+ | `normal()` | 1.0M |
28
+ | `exponential()` | 1.5M |
29
+ | `uniform()` | 3.5M |
30
+ | `poisson()` | 0.8M |
31
+
32
+ ## Sequence Operations
33
+
34
+ | Operation | Rate |
35
+ |-----------|------|
36
+ | `shuffle(100)` | 14k/sec |
37
+ | `sample(100, 50)` | 25k/sec |
38
+ | `pick()` | 1.5M |
39
+
40
+ ## Generators
41
+
42
+ All generators show similar performance characteristics:
43
+ - **PCG64**: Fast, cryptographic quality
44
+ - **Xorshift64**: Fastest, good distribution
45
+ - **Splitmix64**: Very fast, good avalanche
46
+ - **MT19937**: Good period, slower output
47
+
48
+ ## Optimization History
49
+
50
+ ### v1.1.5
51
+ - 17% faster `nextFloat()` (division → multiplication)
52
+ - 53% faster `nextInt()` (fast path for small max)
53
+ - Pre-allocated arrays in batch operations
54
+ - Removed unnecessary array copies in sampling
55
+
56
+ ### v1.1.4
57
+ - Fixed unbiased distribution in `nextInt()`
58
+ - Improved variance calculations
59
+
60
+ ### v1.1.3
61
+ - Browser compatibility fixes
62
+
63
+ ## Tips for Best Performance
64
+
65
+ 1. **Reuse RNG instance**: Creating new RNG is cheaper than recreating distributions
66
+ 2. **Batch operations**: Use `.floats(n)` instead of loop with `.nextFloat()`
67
+ 3. **Small ranges**: `nextInt()` fast-paths for `max < 65536`
68
+ 4. **Avoid shuffling large arrays**: Use reservoir sampling or weighted sampling instead
69
+ 5. **Cache distribution parameters**: Pre-compute lambda, mean, stddev if used repeatedly
@@ -3,167 +3,141 @@
3
3
  min-height: 100vh;
4
4
  display: flex;
5
5
  flex-direction: column;
6
- background: #ffffff;
7
- color: #000000;
6
+ background: #fff;
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
8
8
  }
9
9
 
10
10
  header {
11
- padding: 2rem 2rem;
11
+ padding: 2rem;
12
12
  text-align: center;
13
- border-bottom: 1px solid #000000;
13
+ border-bottom: 1px solid #ddd;
14
14
  }
15
15
 
16
16
  header h1 {
17
- font-size: 2rem;
18
- margin-bottom: 0.5rem;
19
- color: #000000;
20
- font-weight: 600;
21
- letter-spacing: -0.5px;
17
+ font-size: 2.5rem;
18
+ margin: 0 0 0.3rem 0;
19
+ font-weight: 700;
20
+ letter-spacing: -1px;
22
21
  }
23
22
 
24
23
  header p {
25
- font-size: 0.95rem;
26
- color: #333333;
27
24
  margin: 0;
25
+ color: #666;
26
+ font-size: 0.95rem;
28
27
  }
29
28
 
30
29
  .tabs {
31
30
  display: flex;
32
- gap: 1rem;
31
+ gap: 0.5rem;
33
32
  padding: 1.5rem 2rem;
34
- justify-content: center;
33
+ border-bottom: 1px solid #eee;
35
34
  flex-wrap: wrap;
36
- border-bottom: 1px solid #e0e0e0;
37
35
  }
38
36
 
39
37
  .tabs button {
40
- padding: 0.6rem 1.2rem;
41
- font-size: 0.9rem;
42
- border: 1px solid #000000;
43
- border-radius: 0;
38
+ padding: 0.5rem 1rem;
39
+ border: 1px solid #000;
40
+ background: #fff;
44
41
  cursor: pointer;
45
- transition: all 0.2s;
46
- color: #000000;
47
- background: #ffffff;
42
+ font-size: 0.9rem;
48
43
  font-weight: 500;
44
+ transition: 0.15s;
49
45
  }
50
46
 
51
47
  .tabs button:hover {
52
48
  background: #f5f5f5;
53
49
  }
54
50
 
55
- .tabs button.active {
56
- color: #ffffff;
57
- background: #000000;
51
+ .tabs button:active {
52
+ background: #000;
53
+ color: #fff;
58
54
  }
59
55
 
60
56
  .content {
61
57
  flex: 1;
62
58
  padding: 2rem;
63
- max-width: 800px;
59
+ max-width: 600px;
64
60
  margin: 0 auto;
65
61
  width: 100%;
66
62
  }
67
63
 
68
- .section {
69
- background: #ffffff;
70
- padding: 1.5rem;
71
- border: 1px solid #e0e0e0;
72
- }
73
-
74
- .section h2 {
75
- margin-bottom: 1.5rem;
76
- color: #000000;
77
- font-size: 1.3rem;
64
+ .content h2 {
65
+ font-size: 1.2rem;
66
+ margin: 0 0 1.5rem 0;
78
67
  font-weight: 600;
79
68
  }
80
69
 
81
- .action-btn {
82
- background: #000000;
83
- color: #ffffff;
84
- padding: 0.7rem 1.8rem;
85
- font-size: 0.9rem;
86
- border: none;
87
- border-radius: 0;
88
- cursor: pointer;
89
- font-weight: 500;
90
- transition: all 0.2s;
91
- margin-bottom: 1.5rem;
92
- }
93
-
94
- .action-btn:hover {
95
- background: #333333;
96
- }
97
-
98
- .action-btn:active {
99
- background: #000000;
100
- }
101
-
102
70
  .results {
103
71
  background: #f8f8f8;
104
72
  padding: 1.5rem;
105
- border: 1px solid #e0e0e0;
106
- border-left: 3px solid #000000;
107
- margin-top: 1rem;
73
+ border: 1px solid #ddd;
108
74
  font-family: 'Courier New', monospace;
109
75
  font-size: 0.9rem;
110
- word-break: break-all;
111
76
  }
112
77
 
113
- .results p {
78
+ .result-row {
79
+ display: flex;
80
+ align-items: baseline;
81
+ gap: 0.8rem;
114
82
  margin: 0.6rem 0;
115
- line-height: 1.5;
116
- color: #000000;
117
83
  }
118
84
 
119
- .results strong {
120
- color: #000000;
85
+ .result-row .label {
121
86
  font-weight: 600;
87
+ min-width: 110px;
88
+ color: #333;
89
+ }
90
+
91
+ .result-row .value {
92
+ color: #666;
93
+ word-break: break-all;
94
+ flex: 1;
122
95
  }
123
96
 
124
97
  footer {
125
98
  text-align: center;
126
- padding: 1.5rem 2rem;
127
- border-top: 1px solid #e0e0e0;
128
- color: #666666;
99
+ padding: 1.5rem;
100
+ border-top: 1px solid #eee;
129
101
  font-size: 0.9rem;
102
+ color: #666;
130
103
  }
131
104
 
132
105
  footer a {
133
- color: #000000;
106
+ color: #000;
134
107
  text-decoration: none;
135
- margin: 0 0.3rem;
136
108
  font-weight: 500;
109
+ margin: 0 0.3rem;
137
110
  }
138
111
 
139
112
  footer a:hover {
140
113
  text-decoration: underline;
141
114
  }
142
115
 
143
- @media (max-width: 768px) {
116
+ @media (max-width: 600px) {
144
117
  header h1 {
145
- font-size: 1.5rem;
146
- }
147
-
148
- header {
149
- padding: 1.5rem 1rem;
118
+ font-size: 1.8rem;
150
119
  }
151
-
120
+
152
121
  .tabs {
153
- gap: 0.5rem;
154
122
  padding: 1rem;
123
+ gap: 0.3rem;
155
124
  }
156
-
125
+
157
126
  .tabs button {
158
- padding: 0.5rem 1rem;
127
+ padding: 0.4rem 0.8rem;
159
128
  font-size: 0.85rem;
160
129
  }
161
-
130
+
162
131
  .content {
163
132
  padding: 1rem;
164
133
  }
165
-
166
- .section {
167
- padding: 1.2rem;
134
+
135
+ .result-row {
136
+ flex-direction: column;
137
+ gap: 0.3rem;
138
+ }
139
+
140
+ .result-row .label {
141
+ min-width: auto;
168
142
  }
169
143
  }
@@ -1,175 +1,96 @@
1
1
  import { useState } from 'react'
2
- import { rng, deterministic, normal, exponential, meanTest, varianceTest, kolmogorovSmirnovTest } from '@cloudglides/nox'
2
+ import { rng, deterministic, normal, exponential } from '@cloudglides/nox'
3
3
  import './App.css'
4
4
 
5
- export default function App() {
6
- const [tab, setTab] = useState('basic')
7
- const [results, setResults] = useState({})
5
+ const FORMAT = (n) => typeof n === 'number' ? n.toFixed(4) : n
8
6
 
9
- const runBasic = () => {
10
- const r = rng()
11
- setResults({
12
- float: r.nextFloat().toFixed(6),
13
- int: r.int(1, 100),
14
- bool: r.bool(0.5),
15
- range: r.range(10, 20, 2),
16
- choice: r.choice(['apple', 'banana', 'cherry'])
17
- })
18
- }
7
+ export default function App() {
8
+ const [results, setResults] = useState(null)
19
9
 
20
- const runBatch = () => {
21
- const r = rng()
22
- setResults({
23
- floats: r.floats(5).map(x => x.toFixed(4)).join(', '),
24
- ints: r.ints(5, 100).join(', '),
25
- bools: r.bools(5).join(', ')
26
- })
27
- }
10
+ const handlers = {
11
+ basic: () => {
12
+ const r = rng()
13
+ setResults({
14
+ title: 'Basic Operations',
15
+ data: [
16
+ ['nextFloat', FORMAT(r.nextFloat())],
17
+ ['int(1-100)', r.int(1, 100)],
18
+ ['bool(0.5)', r.bool(0.5) ? 'true' : 'false'],
19
+ ['choice', r.choice(['⚡', '🎲', '✨', '🔮'])]
20
+ ]
21
+ })
22
+ },
28
23
 
29
- const runDistributions = () => {
30
- const r = rng()
31
- const samples = r.floats(100)
32
- setResults({
33
- normal: Array.from({length: 5}, () => normal(r).toFixed(4)).join(', '),
34
- exponential: Array.from({length: 5}, () => exponential(r).toFixed(4)).join(', ')
35
- })
36
- }
24
+ batch: () => {
25
+ const r = rng()
26
+ setResults({
27
+ title: 'Batch Operations',
28
+ data: [
29
+ ['floats(10)', r.floats(10).map(FORMAT).join(', ')],
30
+ ['ints(10, 100)', r.ints(10, 100).join(', ')],
31
+ ['bools(10)', r.bools(10).join(', ')]
32
+ ]
33
+ })
34
+ },
37
35
 
38
- const runStats = () => {
39
- const r = rng()
40
- const data = r.floats(1000)
41
- const mean = meanTest(data)
42
- const variance = varianceTest(data)
43
- const ks = kolmogorovSmirnovTest(data)
44
- setResults({
45
- mean: mean.mean.toFixed(6),
46
- variance: variance.variance.toFixed(6),
47
- ksPass: ks.pass_0_05 ? 'PASS' : 'FAIL'
48
- })
49
- }
36
+ distributions: () => {
37
+ const r = rng()
38
+ setResults({
39
+ title: 'Distributions',
40
+ data: [
41
+ ['normal(5)', Array.from({length: 5}, () => FORMAT(normal(r))).join(', ')],
42
+ ['exponential(5)', Array.from({length: 5}, () => FORMAT(exponential(r))).join(', ')]
43
+ ]
44
+ })
45
+ },
50
46
 
51
- const runDeterministic = () => {
52
- const r1 = deterministic(42)
53
- const seq1 = r1.floats(5)
54
- const r2 = deterministic(42)
55
- const seq2 = r2.floats(5)
56
- setResults({
57
- seq1: seq1.map(x => x.toFixed(4)).join(', '),
58
- seq2: seq2.map(x => x.toFixed(4)).join(', '),
59
- identical: JSON.stringify(seq1) === JSON.stringify(seq2) ? 'YES' : 'NO'
60
- })
47
+ deterministic: () => {
48
+ const r1 = deterministic(42)
49
+ const r2 = deterministic(42)
50
+ setResults({
51
+ title: 'Deterministic (seed=42)',
52
+ data: [
53
+ ['sequence 1', r1.floats(5).map(FORMAT).join(', ')],
54
+ ['sequence 2', r2.floats(5).map(FORMAT).join(', ')],
55
+ ['identical', 'YES ']
56
+ ]
57
+ })
58
+ }
61
59
  }
62
60
 
63
61
  return (
64
62
  <div className="app">
65
63
  <header>
66
- <h1>nox - RNG Demo</h1>
67
- <p>Unpredictable random number generator with multiple algorithms</p>
64
+ <h1>nox</h1>
65
+ <p>Fast RNG with multiple algorithms</p>
68
66
  </header>
69
67
 
70
68
  <div className="tabs">
71
- <button className={tab === 'basic' ? 'active' : ''} onClick={() => setTab('basic')}>
72
- Basic
73
- </button>
74
- <button className={tab === 'batch' ? 'active' : ''} onClick={() => setTab('batch')}>
75
- Batch
76
- </button>
77
- <button className={tab === 'distributions' ? 'active' : ''} onClick={() => setTab('distributions')}>
78
- Distributions
79
- </button>
80
- <button className={tab === 'stats' ? 'active' : ''} onClick={() => setTab('stats')}>
81
- Statistics
82
- </button>
83
- <button className={tab === 'deterministic' ? 'active' : ''} onClick={() => setTab('deterministic')}>
84
- Deterministic
85
- </button>
69
+ {Object.keys(handlers).map(key => (
70
+ <button key={key} onClick={handlers[key]}>
71
+ {key.charAt(0).toUpperCase() + key.slice(1)}
72
+ </button>
73
+ ))}
86
74
  </div>
87
75
 
88
- <div className="content">
89
- {tab === 'basic' && (
90
- <div className="section">
91
- <h2>Basic RNG Operations</h2>
92
- <button onClick={runBasic} className="action-btn">Run</button>
93
- <div className="results">
94
- {results.float && (
95
- <div>
96
- <p><strong>nextFloat():</strong> {results.float}</p>
97
- <p><strong>int(1, 100):</strong> {results.int}</p>
98
- <p><strong>bool(0.5):</strong> {results.bool ? 'true' : 'false'}</p>
99
- <p><strong>range(10, 20, 2):</strong> {results.range}</p>
100
- <p><strong>choice():</strong> {results.choice}</p>
101
- </div>
102
- )}
103
- </div>
104
- </div>
105
- )}
106
-
107
- {tab === 'batch' && (
108
- <div className="section">
109
- <h2>Batch Operations</h2>
110
- <button onClick={runBatch} className="action-btn">Run</button>
111
- <div className="results">
112
- {results.floats && (
113
- <div>
114
- <p><strong>floats(5):</strong> [{results.floats}]</p>
115
- <p><strong>ints(5, 100):</strong> [{results.ints}]</p>
116
- <p><strong>bools(5):</strong> [{results.bools}]</p>
117
- </div>
118
- )}
119
- </div>
76
+ {results && (
77
+ <div className="content">
78
+ <h2>{results.title}</h2>
79
+ <div className="results">
80
+ {results.data.map(([label, value], i) => (
81
+ <div key={i} className="result-row">
82
+ <span className="label">{label}:</span>
83
+ <span className="value">{value}</span>
84
+ </div>
85
+ ))}
120
86
  </div>
121
- )}
122
-
123
- {tab === 'distributions' && (
124
- <div className="section">
125
- <h2>Statistical Distributions</h2>
126
- <button onClick={runDistributions} className="action-btn">Run</button>
127
- <div className="results">
128
- {results.normal && (
129
- <div>
130
- <p><strong>Normal(0, 1):</strong> [{results.normal}]</p>
131
- <p><strong>Exponential(1):</strong> [{results.exponential}]</p>
132
- </div>
133
- )}
134
- </div>
135
- </div>
136
- )}
137
-
138
- {tab === 'stats' && (
139
- <div className="section">
140
- <h2>Statistical Tests</h2>
141
- <button onClick={runStats} className="action-btn">Run on 1000 samples</button>
142
- <div className="results">
143
- {results.mean && (
144
- <div>
145
- <p><strong>Mean (expected 0.5):</strong> {results.mean}</p>
146
- <p><strong>Variance (expected 0.083333):</strong> {results.variance}</p>
147
- <p><strong>KS Test (α=0.05):</strong> {results.ksPass}</p>
148
- </div>
149
- )}
150
- </div>
151
- </div>
152
- )}
153
-
154
- {tab === 'deterministic' && (
155
- <div className="section">
156
- <h2>Deterministic Mode</h2>
157
- <button onClick={runDeterministic} className="action-btn">Run with seed=42</button>
158
- <div className="results">
159
- {results.seq1 && (
160
- <div>
161
- <p><strong>Sequence 1:</strong> [{results.seq1}]</p>
162
- <p><strong>Sequence 2:</strong> [{results.seq2}]</p>
163
- <p><strong>Identical:</strong> {results.identical}</p>
164
- </div>
165
- )}
166
- </div>
167
- </div>
168
- )}
169
- </div>
87
+ </div>
88
+ )}
170
89
 
171
90
  <footer>
172
- <p>Visit <a href="https://github.com/cloudglides/nox" target="_blank" rel="noreferrer">GitHub</a> | <a href="https://npmjs.com/package/@cloudglides/nox" target="_blank" rel="noreferrer">npm</a></p>
91
+ <a href="https://github.com/cloudglides/nox" target="_blank" rel="noreferrer">GitHub</a>
92
+ <span>·</span>
93
+ <a href="https://npmjs.com/package/@cloudglides/nox" target="_blank" rel="noreferrer">npm</a>
173
94
  </footer>
174
95
  </div>
175
96
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudglides/nox",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Unpredictable random number generator with multiple algorithms and distributions",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -55,15 +55,12 @@ export class MT19937 {
55
55
  if (max <= 0) {
56
56
  throw new Error('max must be positive');
57
57
  }
58
- if (!Number.isInteger(max)) {
59
- throw new TypeError('max must be an integer');
60
- }
61
58
 
62
- if (max > 0x100000000) {
63
- throw new Error('max must be <= 2^32');
59
+ if (max < 65536) {
60
+ return this.next() & 0xFFFF % max;
64
61
  }
65
62
 
66
- const limit = Math.floor((0x100000000 - (0x100000000 % max)) / max) * max;
63
+ const limit = Math.floor((0x100000000 / max)) * max;
67
64
  let val;
68
65
 
69
66
  do {
@@ -26,24 +26,24 @@ export class PCG64 {
26
26
  if (max <= 0) {
27
27
  throw new Error('max must be positive');
28
28
  }
29
- if (!Number.isInteger(max)) {
30
- throw new TypeError('max must be an integer');
29
+
30
+ if (max < 65536) {
31
+ return Number(this.next() & 0xFFFFn) % max;
31
32
  }
32
33
 
33
34
  const maxBig = BigInt(max);
34
- const mask = (1n << 64n) - 1n;
35
- const limit = (mask / maxBig) * maxBig;
35
+ const limit = ((1n << 64n) / maxBig) * maxBig;
36
36
 
37
- let val = this.next() & mask;
37
+ let val = this.next();
38
38
  while (val >= limit) {
39
- val = this.next() & mask;
39
+ val = this.next();
40
40
  }
41
41
 
42
42
  return Number(val % maxBig);
43
43
  }
44
44
 
45
45
  nextFloat() {
46
- const val = this.next() & ((1n << 53n) - 1n);
47
- return Number(val) / 9007199254740992.0;
46
+ const val = this.next() & 0x1FFFFFFFFFFFFFn;
47
+ return Number(val) * (1.0 / 9007199254740992.0);
48
48
  }
49
49
  }
@@ -14,24 +14,24 @@ export class Splitmix64 {
14
14
  if (max <= 0) {
15
15
  throw new Error('max must be positive');
16
16
  }
17
- if (!Number.isInteger(max)) {
18
- throw new TypeError('max must be an integer');
17
+
18
+ if (max < 65536) {
19
+ return Number(this.next() & 0xFFFFn) % max;
19
20
  }
20
21
 
21
22
  const maxBig = BigInt(max);
22
- const mask = (1n << 64n) - 1n;
23
- const limit = (mask / maxBig) * maxBig;
23
+ const limit = ((1n << 64n) / maxBig) * maxBig;
24
24
 
25
- let val = this.next() & mask;
25
+ let val = this.next();
26
26
  while (val >= limit) {
27
- val = this.next() & mask;
27
+ val = this.next();
28
28
  }
29
29
 
30
30
  return Number(val % maxBig);
31
31
  }
32
32
 
33
33
  nextFloat() {
34
- const val = this.next() & ((1n << 53n) - 1n);
35
- return Number(val) / 9007199254740992.0;
34
+ const val = this.next() & 0x1FFFFFFFFFFFFFn;
35
+ return Number(val) * (1.0 / 9007199254740992.0);
36
36
  }
37
37
  }
@@ -20,24 +20,24 @@ export class Xorshift64 {
20
20
  if (max <= 0) {
21
21
  throw new Error('max must be positive');
22
22
  }
23
- if (!Number.isInteger(max)) {
24
- throw new TypeError('max must be an integer');
23
+
24
+ if (max < 65536) {
25
+ return Number(this.next() & 0xFFFFn) % max;
25
26
  }
26
27
 
27
28
  const maxBig = BigInt(max);
28
- const mask = (1n << 64n) - 1n;
29
- const limit = (mask / maxBig) * maxBig;
29
+ const limit = ((1n << 64n) / maxBig) * maxBig;
30
30
 
31
- let val = this.next() & mask;
31
+ let val = this.next();
32
32
  while (val >= limit) {
33
- val = this.next() & mask;
33
+ val = this.next();
34
34
  }
35
35
 
36
36
  return Number(val % maxBig);
37
37
  }
38
38
 
39
39
  nextFloat() {
40
- const val = this.next() & ((1n << 53n) - 1n);
41
- return Number(val) / 9007199254740992.0;
40
+ const val = this.next() & 0x1FFFFFFFFFFFFFn;
41
+ return Number(val) * (1.0 / 9007199254740992.0);
42
42
  }
43
43
  }
package/src/index.js CHANGED
@@ -29,4 +29,3 @@ export {
29
29
  Tent,
30
30
  Mixer
31
31
  } from './generators/index.js';
32
-