|
1 | 1 | import { engine as ce } from '../../utils'; |
| 2 | +import { ComputeEngine } from '../../../src/compute-engine'; |
2 | 3 |
|
3 | 4 | describe('constructor', () => { |
4 | 5 | it('should create from an integer', () => { |
@@ -180,3 +181,74 @@ describe('power', () => { |
180 | 181 | expect(b.toString()).toMatchInlineSnapshot(`192`); |
181 | 182 | }); |
182 | 183 | }); |
| 184 | + |
| 185 | +// Regressions for the numeric-value bugs reported in REVIEW.md (D1–D4, D10). |
| 186 | +describe('Numeric-value correctness (REVIEW.md D1–D4)', () => { |
| 187 | + // `BigDecimal.precision` is a global static, so a machine-precision engine is |
| 188 | + // created in beforeAll (not at collection time) and the global precision is |
| 189 | + // restored in afterAll — otherwise it leaks into other tests. |
| 190 | + let machine: ComputeEngine; |
| 191 | + let savedPrecision: number; |
| 192 | + beforeAll(() => { |
| 193 | + savedPrecision = ce.precision; |
| 194 | + machine = new ComputeEngine({ precision: 'machine' }); |
| 195 | + }); |
| 196 | + afterAll(() => { |
| 197 | + ce.precision = savedPrecision; |
| 198 | + }); |
| 199 | + |
| 200 | + // D1: complex pow used De Moivre's `argument ** exponent` instead of |
| 201 | + // `argument * exponent` (machine precision). |
| 202 | + it('D1: machine complex pow (i^2 = -1)', () => { |
| 203 | + const r = machine._numericValue(machine.complex(0, 1)).pow(2); |
| 204 | + expect(r.re).toBeCloseTo(-1, 12); |
| 205 | + expect(r.im).toBeCloseTo(0, 12); |
| 206 | + }); |
| 207 | + |
| 208 | + // D10: negative exponent on a complex base dropped the imaginary part. |
| 209 | + it('D10: machine complex negative pow ((1+i)^-2 = -0.5i)', () => { |
| 210 | + const r = machine._numericValue(machine.complex(1, 1)).pow(-2); |
| 211 | + expect(r.re).toBeCloseTo(0, 12); |
| 212 | + expect(r.im).toBeCloseTo(-0.5, 12); |
| 213 | + }); |
| 214 | + |
| 215 | + // D2: complex inv divided the conjugate by |z| instead of |z|². |
| 216 | + it('D2: machine complex inv (1/(2i) = -0.5i)', () => { |
| 217 | + const r = machine._numericValue(machine.complex(0, 2)).inv(); |
| 218 | + expect(r.re).toBeCloseTo(0, 12); |
| 219 | + expect(r.im).toBeCloseTo(-0.5, 12); |
| 220 | + }); |
| 221 | + |
| 222 | + it('D2: bignum complex inv (1/(2i) = -0.5i)', () => { |
| 223 | + const r = ce._numericValue(ce.complex(0, 2)).inv(); |
| 224 | + expect(r.re).toBeCloseTo(0, 12); |
| 225 | + expect(r.im).toBeCloseTo(-0.5, 12); |
| 226 | + }); |
| 227 | + |
| 228 | + // D3: exact pow with a 1/n exponent took the n-th root of the numerator |
| 229 | + // (always 1) instead of the denominator, so it returned the base unchanged. |
| 230 | + it('D3: exact n-th root (8^(1/3) = 2, 27^(1/3) = 3)', () => { |
| 231 | + expect(ce._numericValue(8).pow(ce._numericValue([1, 3])).toString()).toEqual( |
| 232 | + '2' |
| 233 | + ); |
| 234 | + expect( |
| 235 | + ce._numericValue(27).pow(ce._numericValue([1, 3])).toString() |
| 236 | + ).toEqual('3'); |
| 237 | + }); |
| 238 | + |
| 239 | + // D4: floor/ceil/round routed exact integers/rationals through a float, |
| 240 | + // losing digits beyond 2^53. They now compute exactly with bigints. |
| 241 | + it('D4: exact floor of a large integer keeps every digit', () => { |
| 242 | + const big = 123456789012345678901234567890n; |
| 243 | + expect(ce._numericValue(big).floor().toString()).toEqual(big.toString()); |
| 244 | + }); |
| 245 | + |
| 246 | + it('D4: exact floor/ceil/round of rationals', () => { |
| 247 | + expect(ce._numericValue([7, 2]).floor().toString()).toEqual('3'); |
| 248 | + expect(ce._numericValue([-7, 2]).floor().toString()).toEqual('-4'); |
| 249 | + expect(ce._numericValue([7, 2]).ceil().toString()).toEqual('4'); |
| 250 | + expect(ce._numericValue([-7, 2]).ceil().toString()).toEqual('-3'); |
| 251 | + expect(ce._numericValue([5, 2]).round().toString()).toEqual('3'); |
| 252 | + expect(ce._numericValue([-5, 2]).round().toString()).toEqual('-2'); |
| 253 | + }); |
| 254 | +}); |
0 commit comments