-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🚚 Split ArrayInt into two files (#737)
* 🚚 Split ArrayInt into two files * fix tests
- Loading branch information
Showing
5 changed files
with
168 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |