Skip to content

Commit

Permalink
🚚 Split ArrayInt into two files (#737)
Browse files Browse the repository at this point in the history
* 🚚 Split ArrayInt into two files

* fix tests
  • Loading branch information
dubzzz authored Sep 24, 2024
1 parent 2c94832 commit e1758c0
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 159 deletions.
4 changes: 2 additions & 2 deletions src/distribution/UnsafeUniformIntDistribution.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RandomGenerator } from '../types/RandomGenerator';
import { unsafeUniformIntDistributionInternal } from './internals/UnsafeUniformIntDistributionInternal';
import type { ArrayInt64 } from './internals/ArrayInt';
import { fromNumberToArrayInt64, substractArrayInt64 } from './internals/ArrayInt';
import type { ArrayInt64 } from './internals/ArrayInt64';
import { fromNumberToArrayInt64, substractArrayInt64 } from './internals/ArrayInt64';
import { unsafeUniformArrayIntDistributionInternal } from './internals/UnsafeUniformArrayIntDistributionInternal';

const safeNumberMaxSafeInteger = Number.MAX_SAFE_INTEGER;
Expand Down
71 changes: 0 additions & 71 deletions src/distribution/internals/ArrayInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,74 +123,3 @@ export function trimArrayIntInplace(arrayInt: ArrayInt) {
data.splice(0, firstNonZero);
return arrayInt;
}

// Helpers specific to 64 bits versions

/** @internal */
export type ArrayInt64 = Required<ArrayInt> & { data: [number, number] };

/**
* We only accept safe integers here
* @internal
*/
export function fromNumberToArrayInt64(out: ArrayInt64, n: number): ArrayInt64 {
if (n < 0) {
const posN = -n;
out.sign = -1;
out.data[0] = ~~(posN / 0x100000000);
out.data[1] = posN >>> 0;
} else {
out.sign = 1;
out.data[0] = ~~(n / 0x100000000);
out.data[1] = n >>> 0;
}
return out;
}

/**
* Substract two ArrayInt of 64 bits on 64 bits.
* With arrayIntA - arrayIntB >= 0
* @internal
*/
export function substractArrayInt64(out: ArrayInt64, arrayIntA: ArrayInt64, arrayIntB: ArrayInt64): ArrayInt64 {
const lowA = arrayIntA.data[1];
const highA = arrayIntA.data[0];
const signA = arrayIntA.sign;
const lowB = arrayIntB.data[1];
const highB = arrayIntB.data[0];
const signB = arrayIntB.sign;

// Requirement: arrayIntA - arrayIntB >= 0
out.sign = 1;

if (signA === 1 && signB === -1) {
// Operation is a simple sum of arrayIntA + abs(arrayIntB)
const low = lowA + lowB;
const high = highA + highB + (low > 0xffffffff ? 1 : 0);
out.data[0] = high >>> 0;
out.data[1] = low >>> 0;
return out;
}
// signA === -1 with signB === 1 is impossible given: arrayIntA - arrayIntB >= 0

// Operation is a substraction
let lowFirst = lowA;
let highFirst = highA;
let lowSecond = lowB;
let highSecond = highB;
if (signA === -1) {
lowFirst = lowB;
highFirst = highB;
lowSecond = lowA;
highSecond = highA;
}
let reminderLow = 0;
let low = lowFirst - lowSecond;
if (low < 0) {
reminderLow = 1;
low = low >>> 0;
}
out.data[0] = highFirst - highSecond - reminderLow;
out.data[1] = low;
return out;
}
72 changes: 72 additions & 0 deletions src/distribution/internals/ArrayInt64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { ArrayInt } from './ArrayInt';

// Helpers specific to 64 bits versions

/** @internal */
export type ArrayInt64 = Required<ArrayInt> & { data: [number, number] };

/**
* We only accept safe integers here
* @internal
*/
export function fromNumberToArrayInt64(out: ArrayInt64, n: number): ArrayInt64 {
if (n < 0) {
const posN = -n;
out.sign = -1;
out.data[0] = ~~(posN / 0x100000000);
out.data[1] = posN >>> 0;
} else {
out.sign = 1;
out.data[0] = ~~(n / 0x100000000);
out.data[1] = n >>> 0;
}
return out;
}

/**
* Substract two ArrayInt of 64 bits on 64 bits.
* With arrayIntA - arrayIntB >= 0
* @internal
*/
export function substractArrayInt64(out: ArrayInt64, arrayIntA: ArrayInt64, arrayIntB: ArrayInt64): ArrayInt64 {
const lowA = arrayIntA.data[1];
const highA = arrayIntA.data[0];
const signA = arrayIntA.sign;
const lowB = arrayIntB.data[1];
const highB = arrayIntB.data[0];
const signB = arrayIntB.sign;

// Requirement: arrayIntA - arrayIntB >= 0
out.sign = 1;

if (signA === 1 && signB === -1) {
// Operation is a simple sum of arrayIntA + abs(arrayIntB)
const low = lowA + lowB;
const high = highA + highB + (low > 0xffffffff ? 1 : 0);
out.data[0] = high >>> 0;
out.data[1] = low >>> 0;
return out;
}
// signA === -1 with signB === 1 is impossible given: arrayIntA - arrayIntB >= 0

// Operation is a substraction
let lowFirst = lowA;
let highFirst = highA;
let lowSecond = lowB;
let highSecond = highB;
if (signA === -1) {
lowFirst = lowB;
highFirst = highB;
lowSecond = lowA;
highSecond = highA;
}
let reminderLow = 0;
let low = lowFirst - lowSecond;
if (low < 0) {
reminderLow = 1;
low = low >>> 0;
}
out.data[0] = highFirst - highSecond - reminderLow;
out.data[1] = low;
return out;
}
86 changes: 0 additions & 86 deletions test/unit/distribution/internals/ArrayInt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import {
addArrayIntToNew,
addOneToPositiveArrayInt,
ArrayInt,
ArrayInt64,
fromNumberToArrayInt64,
substractArrayInt64,
substractArrayIntToNew,
trimArrayIntInplace,
} from '../../../../src/distribution/internals/ArrayInt';
Expand Down Expand Up @@ -83,75 +80,6 @@ describe('ArrayInt', () => {
}),
));
});

describe('fromNumberToArrayInt64', () => {
it('Should be able to convert any 32 bits positive integer to an ArrayInt64', () =>
fc.assert(
fc.property(fc.integer({ min: 0, max: 0xffffffff }), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value);
expect(arrayInt).toEqual({ sign: 1, data: [0, value] });
}),
));

it('Should be able to convert any 32 bits negative integer to an ArrayInt64', () =>
fc.assert(
fc.property(fc.integer({ min: 1, max: 0xffffffff }), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), -value);
expect(arrayInt).toEqual({ sign: -1, data: [0, value] });
}),
));

it('Should be able to convert any safe integer to an ArrayInt64', () =>
fc.assert(
fc.property(fc.maxSafeInteger(), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value);

expect(arrayInt.sign).toBe(value < 0 ? -1 : 1);
expect(arrayInt.data).toHaveLength(2);

const arrayIntHexaRepr =
arrayInt.data[0].toString(16).padStart(8, '0') + arrayInt.data[1].toString(16).padStart(8, '0');
const valueHexaRepr = Math.abs(value).toString(16).padStart(16, '0');
expect(arrayIntHexaRepr).toBe(valueHexaRepr);
}),
));

it('Should be able to read back itself using toNumber', () =>
fc.assert(
fc.property(fc.maxSafeInteger(), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value);
expect(toNumber(arrayInt)).toBe(value);
}),
));
});

describe('substractArrayInt64', () => {
// Skip next tests if BigInt is not supported
if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true));

const fromBigIntToArrayInt64 = (n: bigint): ArrayInt64 => {
const posN = n < BigInt(0) ? -n : n;
return {
sign: n < BigInt(0) ? -1 : 1,
data: [Number(posN >> BigInt(32)), Number(posN % (BigInt(1) << BigInt(32)))],
};
};

it('Should be able to substract two non-overflowing ArrayInt64', () =>
fc.assert(
fc.property(fc.bigIntN(64), fc.bigIntN(64), (a, b) => {
const min = a < b ? a : b;
const max = a < b ? b : a;
const result = max - min;
fc.pre(result < BigInt(1) << BigInt(64));

const minArrayInt = fromBigIntToArrayInt64(min);
const maxArrayInt = fromBigIntToArrayInt64(max);
const resultArrayInt = fromBigIntToArrayInt64(result);
expect(substractArrayInt64(arrayInt64Buffer(), maxArrayInt, minArrayInt)).toEqual(resultArrayInt);
}),
));
});
});

// Helpers
Expand All @@ -170,17 +98,3 @@ function arrayIntToBigInt(arrayInt: ArrayInt): bigint {
}
return current * BigInt(arrayInt.sign);
}

function arrayInt64Buffer(): ArrayInt64 {
return { sign: 1, data: [0, 0] };
}

function toNumber(arrayInt: ArrayInt): number {
let current = arrayInt.data[0];
const arrayIntLength = arrayInt.data.length;
for (let index = 1; index < arrayIntLength; ++index) {
current *= 0x100000000;
current += arrayInt.data[index];
}
return current * (arrayInt.sign || 1);
}
94 changes: 94 additions & 0 deletions test/unit/distribution/internals/ArrayInt64.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as fc from 'fast-check';

import {
ArrayInt64,
fromNumberToArrayInt64,
substractArrayInt64,
} from '../../../../src/distribution/internals/ArrayInt64';

describe('ArrayInt64', () => {
describe('fromNumberToArrayInt64', () => {
it('Should be able to convert any 32 bits positive integer to an ArrayInt64', () =>
fc.assert(
fc.property(fc.integer({ min: 0, max: 0xffffffff }), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value);
expect(arrayInt).toEqual({ sign: 1, data: [0, value] });
}),
));

it('Should be able to convert any 32 bits negative integer to an ArrayInt64', () =>
fc.assert(
fc.property(fc.integer({ min: 1, max: 0xffffffff }), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), -value);
expect(arrayInt).toEqual({ sign: -1, data: [0, value] });
}),
));

it('Should be able to convert any safe integer to an ArrayInt64', () =>
fc.assert(
fc.property(fc.maxSafeInteger(), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value);

expect(arrayInt.sign).toBe(value < 0 ? -1 : 1);
expect(arrayInt.data).toHaveLength(2);

const arrayIntHexaRepr =
arrayInt.data[0].toString(16).padStart(8, '0') + arrayInt.data[1].toString(16).padStart(8, '0');
const valueHexaRepr = Math.abs(value).toString(16).padStart(16, '0');
expect(arrayIntHexaRepr).toBe(valueHexaRepr);
}),
));

it('Should be able to read back itself using toNumber', () =>
fc.assert(
fc.property(fc.maxSafeInteger(), (value) => {
const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value);
expect(toNumber(arrayInt)).toBe(value);
}),
));
});

describe('substractArrayInt64', () => {
// Skip next tests if BigInt is not supported
if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true));

const fromBigIntToArrayInt64 = (n: bigint): ArrayInt64 => {
const posN = n < BigInt(0) ? -n : n;
return {
sign: n < BigInt(0) ? -1 : 1,
data: [Number(posN >> BigInt(32)), Number(posN % (BigInt(1) << BigInt(32)))],
};
};

it('Should be able to substract two non-overflowing ArrayInt64', () =>
fc.assert(
fc.property(fc.bigIntN(64), fc.bigIntN(64), (a, b) => {
const min = a < b ? a : b;
const max = a < b ? b : a;
const result = max - min;
fc.pre(result < BigInt(1) << BigInt(64));

const minArrayInt = fromBigIntToArrayInt64(min);
const maxArrayInt = fromBigIntToArrayInt64(max);
const resultArrayInt = fromBigIntToArrayInt64(result);
expect(substractArrayInt64(arrayInt64Buffer(), maxArrayInt, minArrayInt)).toEqual(resultArrayInt);
}),
));
});
});

// Helpers

function arrayInt64Buffer(): ArrayInt64 {
return { sign: 1, data: [0, 0] };
}

function toNumber(arrayInt: ArrayInt64): number {
let current = arrayInt.data[0];
const arrayIntLength = arrayInt.data.length;
for (let index = 1; index < arrayIntLength; ++index) {
current *= 0x100000000;
current += arrayInt.data[index];
}
return current * (arrayInt.sign || 1);
}

0 comments on commit e1758c0

Please sign in to comment.