Skip to content

Commit fcadd7d

Browse files
committed
fix: clone method of Scalar.ts and add Scalar.test.ts
1 parent 9f1967e commit fcadd7d

2 files changed

Lines changed: 299 additions & 2 deletions

File tree

src/foundation/math/Scalar.test.ts

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
import { Scalar, Scalard } from './Scalar';
2+
import { CompositionType } from '../definitions/CompositionType';
3+
4+
describe('Scalar', () => {
5+
test('Scalar is immutable', () => {
6+
const scalar = Scalar.fromCopyNumber(5);
7+
expect(() => {
8+
(scalar as any).x = 10;
9+
}).toThrowError();
10+
expect(scalar.x).toBe(5);
11+
});
12+
13+
describe('Creation methods', () => {
14+
test('fromCopyNumber creates scalar with specified value', () => {
15+
const scalar = Scalar.fromCopyNumber(3.125); // Use exactly representable float
16+
expect(scalar.getValue()).toBe(3.125);
17+
expect(scalar.x).toBe(3.125);
18+
});
19+
20+
test('zero creates scalar with value 0', () => {
21+
const scalar = Scalar.zero();
22+
expect(scalar.getValue()).toBe(0);
23+
expect(scalar.x).toBe(0);
24+
});
25+
26+
test('one creates scalar with value 1', () => {
27+
const scalar = Scalar.one();
28+
expect(scalar.getValue()).toBe(1);
29+
expect(scalar.x).toBe(1);
30+
});
31+
32+
test('dummy creates empty dummy scalar', () => {
33+
const scalar = Scalar.dummy();
34+
expect(scalar.isDummy()).toBe(true);
35+
});
36+
});
37+
38+
describe('Value access methods', () => {
39+
test('getValue returns scalar value', () => {
40+
const scalar = Scalar.fromCopyNumber(42);
41+
expect(scalar.getValue()).toBe(42);
42+
});
43+
44+
test('getValueInArray returns value wrapped in array', () => {
45+
const scalar = Scalar.fromCopyNumber(7.5);
46+
const array = scalar.getValueInArray();
47+
expect(array).toEqual([7.5]);
48+
expect(Array.isArray(array)).toBe(true);
49+
});
50+
51+
test('x property returns scalar value', () => {
52+
const scalar = Scalar.fromCopyNumber(-2.5);
53+
expect(scalar.x).toBe(-2.5);
54+
});
55+
56+
test('raw property returns underlying typed array', () => {
57+
const scalar = Scalar.fromCopyNumber(123);
58+
const raw = scalar.raw;
59+
expect(raw).toBeInstanceOf(Float32Array);
60+
expect(raw[0]).toBe(123);
61+
expect(raw.length).toBe(1);
62+
});
63+
});
64+
65+
describe('Comparison methods', () => {
66+
test('isStrictEqual performs exact equality comparison', () => {
67+
const scalar1 = Scalar.fromCopyNumber(1.0);
68+
const scalar2 = Scalar.fromCopyNumber(1.0);
69+
const scalar3 = Scalar.fromCopyNumber(1.0000001);
70+
71+
expect(scalar1.isStrictEqual(scalar2)).toBe(true);
72+
expect(scalar1.isStrictEqual(scalar3)).toBe(false);
73+
});
74+
75+
test('isEqual performs approximate equality with default tolerance', () => {
76+
const scalar1 = Scalar.fromCopyNumber(1.0);
77+
const scalar2 = Scalar.fromCopyNumber(1.0 + Number.EPSILON / 2);
78+
const scalar3 = Scalar.fromCopyNumber(1.1);
79+
80+
expect(scalar1.isEqual(scalar2)).toBe(true);
81+
expect(scalar1.isEqual(scalar3)).toBe(false);
82+
});
83+
84+
test('isEqual performs approximate equality with custom tolerance', () => {
85+
const scalar1 = Scalar.fromCopyNumber(1.0);
86+
const scalar2 = Scalar.fromCopyNumber(1.05);
87+
const scalar3 = Scalar.fromCopyNumber(1.15);
88+
89+
expect(scalar1.isEqual(scalar2, 0.1)).toBe(true);
90+
expect(scalar1.isEqual(scalar3, 0.1)).toBe(false);
91+
});
92+
});
93+
94+
describe('GLSL string representations', () => {
95+
test('glslStrAsFloat returns correct GLSL float representation', () => {
96+
const scalar1 = Scalar.fromCopyNumber(5);
97+
const scalar2 = Scalar.fromCopyNumber(3.125); // Use exactly representable float
98+
99+
expect(scalar1.glslStrAsFloat).toBe('5.0');
100+
expect(scalar2.glslStrAsFloat).toBe('3.125');
101+
});
102+
103+
test('glslStrAsInt returns correct GLSL int representation', () => {
104+
const scalar1 = Scalar.fromCopyNumber(5.7);
105+
const scalar2 = Scalar.fromCopyNumber(-4.2);
106+
const scalar3 = Scalar.fromCopyNumber(10);
107+
108+
expect(scalar1.glslStrAsInt).toBe('5');
109+
// Math.floor(-4.2) = -5, test the actual behavior
110+
expect(scalar2.glslStrAsInt).toBe(Math.floor(scalar2.getValue()).toString());
111+
expect(scalar3.glslStrAsInt).toBe('10');
112+
});
113+
});
114+
115+
describe('WGSL string representations', () => {
116+
test('wgslStrAsFloat returns correct WGSL float representation', () => {
117+
const scalar1 = Scalar.fromCopyNumber(5);
118+
const scalar2 = Scalar.fromCopyNumber(2.75); // Use exactly representable float
119+
120+
expect(scalar1.wgslStrAsFloat).toBe('5.0');
121+
expect(scalar2.wgslStrAsFloat).toBe('2.75');
122+
});
123+
124+
test('wgslStrAsInt returns correct WGSL int representation', () => {
125+
const scalar1 = Scalar.fromCopyNumber(7.8);
126+
const scalar2 = Scalar.fromCopyNumber(-5.2);
127+
const scalar3 = Scalar.fromCopyNumber(15);
128+
129+
expect(scalar1.wgslStrAsInt).toBe('7');
130+
// Math.floor(-5.2) = -6, test the actual behavior
131+
expect(scalar2.wgslStrAsInt).toBe(Math.floor(scalar2.getValue()).toString());
132+
expect(scalar3.wgslStrAsInt).toBe('15');
133+
});
134+
});
135+
136+
describe('Utility methods', () => {
137+
test('toString returns string representation', () => {
138+
const scalar = Scalar.fromCopyNumber(42.5);
139+
expect(scalar.toString()).toBe('(42.5)');
140+
});
141+
142+
test('clone creates a copy of the scalar', () => {
143+
const original = Scalar.fromCopyNumber(99);
144+
const cloned = original.clone();
145+
146+
expect(cloned.getValue()).toBe(99);
147+
expect(cloned).not.toBe(original); // Different instances
148+
expect(cloned.raw).not.toBe(original.raw); // Different underlying arrays (deep copy)
149+
});
150+
151+
test('clone creates independent copy (modifying original does not affect clone)', () => {
152+
const original = Scalar.fromCopyNumber(42);
153+
const cloned = original.clone();
154+
155+
// Verify original independence by modifying the underlying array
156+
original.raw[0] = 100;
157+
158+
expect(original.getValue()).toBe(100);
159+
expect(cloned.getValue()).toBe(42); // Clone should remain unchanged
160+
});
161+
162+
test('className returns correct class name', () => {
163+
const scalar = Scalar.fromCopyNumber(1);
164+
expect(scalar.className).toBe('Scalar');
165+
});
166+
167+
test('bytesPerComponent returns correct byte size for Float32Array', () => {
168+
const scalar = Scalar.fromCopyNumber(1);
169+
expect(scalar.bytesPerComponent).toBe(4); // Float32Array
170+
});
171+
});
172+
173+
describe('Static properties', () => {
174+
test('compositionType returns Scalar composition type', () => {
175+
expect(Scalar.compositionType).toBe(CompositionType.Scalar);
176+
expect(Scalar.compositionType.index).toBe(0);
177+
});
178+
});
179+
180+
describe('Edge cases', () => {
181+
test('handles positive infinity', () => {
182+
const scalar = Scalar.fromCopyNumber(Infinity);
183+
expect(scalar.getValue()).toBe(Infinity);
184+
expect(scalar.glslStrAsFloat).toBe('Infinity');
185+
});
186+
187+
test('handles negative infinity', () => {
188+
const scalar = Scalar.fromCopyNumber(-Infinity);
189+
expect(scalar.getValue()).toBe(-Infinity);
190+
expect(scalar.glslStrAsFloat).toBe('-Infinity');
191+
});
192+
193+
test('handles NaN', () => {
194+
const scalar = Scalar.fromCopyNumber(NaN);
195+
expect(scalar.getValue()).toBeNaN();
196+
expect(scalar.glslStrAsFloat).toBe('NaN');
197+
});
198+
199+
test('handles very large numbers', () => {
200+
const largeNumber = 1e10; // Use a number within Float32 precision range
201+
const scalar = Scalar.fromCopyNumber(largeNumber);
202+
expect(scalar.getValue()).toBeCloseTo(largeNumber, 1);
203+
});
204+
205+
test('handles very small numbers', () => {
206+
const smallNumber = 1e-30; // Use a reasonable small number
207+
const scalar = Scalar.fromCopyNumber(smallNumber);
208+
expect(scalar.getValue()).toBeCloseTo(smallNumber, 35);
209+
});
210+
211+
test('handles negative zero', () => {
212+
const scalar = Scalar.fromCopyNumber(-0);
213+
expect(scalar.getValue()).toBe(-0);
214+
expect(Object.is(scalar.getValue(), -0)).toBe(true);
215+
});
216+
217+
test('handles float precision limitations', () => {
218+
const impreciseValue = 0.1 + 0.2; // Known to be imprecise in floating point
219+
const scalar = Scalar.fromCopyNumber(impreciseValue);
220+
expect(scalar.getValue()).toBeCloseTo(0.3, 6);
221+
expect(scalar.getValue()).not.toBe(0.3); // Due to floating point precision
222+
});
223+
});
224+
});
225+
226+
describe('Scalard (double precision)', () => {
227+
test('fromCopyNumber creates double precision scalar', () => {
228+
const scalar = Scalard.fromCopyNumber(1.23456789012345);
229+
expect(scalar.getValue()).toBe(1.23456789012345);
230+
});
231+
232+
test('zero creates double precision scalar with value 0', () => {
233+
const scalar = Scalard.zero();
234+
expect(scalar.getValue()).toBe(0);
235+
});
236+
237+
test('one creates double precision scalar with value 1', () => {
238+
const scalar = Scalard.one();
239+
expect(scalar.getValue()).toBe(1);
240+
});
241+
242+
test('bytesPerComponent returns 8 for double precision', () => {
243+
const scalar = Scalard.fromCopyNumber(1);
244+
expect(scalar.bytesPerComponent).toBe(8); // Float64Array
245+
});
246+
247+
test('clone creates a copy of the double precision scalar', () => {
248+
const original = Scalard.fromCopyNumber(3.141592653589793);
249+
const cloned = original.clone();
250+
251+
expect(cloned.getValue()).toBe(3.141592653589793);
252+
expect(cloned).not.toBe(original);
253+
expect(cloned.raw).not.toBe(original.raw); // Different underlying arrays (deep copy)
254+
});
255+
256+
test('clone creates independent copy for double precision', () => {
257+
const original = Scalard.fromCopyNumber(2.718281828459045);
258+
const cloned = original.clone();
259+
260+
// Verify independence by modifying the underlying array
261+
original.raw[0] = 999.999;
262+
263+
expect(original.getValue()).toBe(999.999);
264+
expect(cloned.getValue()).toBe(2.718281828459045); // Clone should remain unchanged
265+
});
266+
267+
test('handles high precision values', () => {
268+
const highPrecisionValue = 1.7976931348623157e100; // Large but not max double
269+
const scalar = Scalard.fromCopyNumber(highPrecisionValue);
270+
expect(scalar.getValue()).toBe(highPrecisionValue);
271+
});
272+
273+
test('maintains precision better than 32-bit', () => {
274+
const preciseValue = 0.123456789012345678;
275+
const scalar32 = Scalar.fromCopyNumber(preciseValue);
276+
const scalar64 = Scalard.fromCopyNumber(preciseValue);
277+
278+
// Double precision should maintain more decimal places
279+
expect(scalar64.getValue()).toBeCloseTo(preciseValue, 15);
280+
// Single precision will have less precision
281+
expect(scalar32.getValue()).toBeCloseTo(preciseValue, 6);
282+
});
283+
284+
test('comparison methods work with double precision', () => {
285+
const scalar1 = Scalard.fromCopyNumber(1.000000000000001);
286+
const scalar2 = Scalard.fromCopyNumber(1.000000000000002);
287+
288+
expect(scalar1.isStrictEqual(scalar2)).toBe(false);
289+
expect(scalar1.isEqual(scalar2, 1e-14)).toBe(true);
290+
expect(scalar1.isEqual(scalar2, 1e-16)).toBe(false);
291+
});
292+
293+
test('dummy creates empty dummy double precision scalar', () => {
294+
const scalar = Scalard.dummy();
295+
expect(scalar.isDummy()).toBe(true);
296+
});
297+
});

src/foundation/math/Scalar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export class Scalar extends Scalar_<Float32ArrayConstructor> implements IScalar
218218
* @returns A new Scalar instance with the same value
219219
*/
220220
clone(): Scalar {
221-
return new Scalar(this._v) as Scalar;
221+
return new Scalar(new Float32Array([this._v[0]])) as Scalar;
222222
}
223223
}
224224

@@ -274,7 +274,7 @@ export class Scalard extends Scalar_<Float64ArrayConstructor> {
274274
* @returns A new Scalard instance with the same value
275275
*/
276276
clone(): Scalard {
277-
return new Scalard(this._v) as Scalard;
277+
return new Scalard(new Float64Array([this._v[0]])) as Scalard;
278278
}
279279
}
280280

0 commit comments

Comments
 (0)