Skip to content

Commit 8510b85

Browse files
gwhitneyjosdejong
andauthored
fix: handle bigints properly in more cases (#3345)
* fix: Handle bigints properly in more cases * Allow a bigint to appear in a max/min of nonhomogeneous arguments * If randomInt is called with a bigint or a pair of bigints, return a bigint. * Preserve uniformity of results if randomInt is called with a very large range. * Extend log, log2, and log10 to bigints * Add tests for all of the above issues. * Bonus: fix one JSDoc comment issue. If every PR fixes one, we will soon get through the 150+ * fix: Refactors per comments and 1 bonus doc test (isInteger) * chore: fix lint --------- Co-authored-by: Jos de Jong <[email protected]>
1 parent b5d635e commit 8510b85

File tree

20 files changed

+224
-47
lines changed

20 files changed

+224
-47
lines changed

src/function/arithmetic/log.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { factory } from '../../utils/factory.js'
2+
import { promoteLogarithm } from '../../utils/bigint.js'
23
import { logNumber } from '../../plain/number/index.js'
34

45
const name = 'log'
56
const dependencies = ['config', 'typed', 'typeOf', 'divideScalar', 'Complex']
7+
const nlg16 = Math.log(16)
68

79
export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, typeOf, config, divideScalar, Complex }) => {
810
/**
@@ -40,26 +42,34 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, t
4042
* @return {number | BigNumber | Fraction | Complex}
4143
* Returns the logarithm of `x`
4244
*/
45+
function complexLog (c) {
46+
return c.log()
47+
}
48+
49+
function complexLogNumber (x) {
50+
return complexLog(new Complex(x, 0))
51+
}
52+
4353
return typed(name, {
4454
number: function (x) {
4555
if (x >= 0 || config.predictable) {
4656
return logNumber(x)
4757
} else {
4858
// negative value -> complex value computation
49-
return new Complex(x, 0).log()
59+
return complexLogNumber(x)
5060
}
5161
},
5262

53-
Complex: function (x) {
54-
return x.log()
55-
},
63+
bigint: promoteLogarithm(nlg16, logNumber, config, complexLogNumber),
64+
65+
Complex: complexLog,
5666

5767
BigNumber: function (x) {
5868
if (!x.isNegative() || config.predictable) {
5969
return x.ln()
6070
} else {
6171
// downgrade to number, return Complex valued result
62-
return new Complex(x.toNumber(), 0).log()
72+
return complexLogNumber(x.toNumber())
6373
}
6474
},
6575

src/function/arithmetic/log10.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { factory } from '../../utils/factory.js'
2-
import { deepMap } from '../../utils/collection.js'
31
import { log10Number } from '../../plain/number/index.js'
2+
import { promoteLogarithm } from '../../utils/bigint.js'
3+
import { deepMap } from '../../utils/collection.js'
4+
import { factory } from '../../utils/factory.js'
45

56
const name = 'log10'
67
const dependencies = ['typed', 'config', 'Complex']
8+
const log16 = log10Number(16)
79

810
export const createLog10 = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, Complex }) => {
911
/**
@@ -31,26 +33,34 @@ export const createLog10 = /* #__PURE__ */ factory(name, dependencies, ({ typed,
3133
* @return {number | BigNumber | Complex | Array | Matrix}
3234
* Returns the 10-base logarithm of `x`
3335
*/
36+
37+
function complexLog (c) {
38+
return c.log().div(Math.LN10)
39+
}
40+
41+
function complexLogNumber (x) {
42+
return complexLog(new Complex(x, 0))
43+
}
3444
return typed(name, {
3545
number: function (x) {
3646
if (x >= 0 || config.predictable) {
3747
return log10Number(x)
3848
} else {
3949
// negative value -> complex value computation
40-
return new Complex(x, 0).log().div(Math.LN10)
50+
return complexLogNumber(x)
4151
}
4252
},
4353

44-
Complex: function (x) {
45-
return new Complex(x).log().div(Math.LN10)
46-
},
54+
bigint: promoteLogarithm(log16, log10Number, config, complexLogNumber),
55+
56+
Complex: complexLog,
4757

4858
BigNumber: function (x) {
4959
if (!x.isNegative() || config.predictable) {
5060
return x.log()
5161
} else {
5262
// downgrade to number, return Complex valued result
53-
return new Complex(x.toNumber(), 0).log().div(Math.LN10)
63+
return complexLogNumber(x.toNumber())
5464
}
5565
},
5666

src/function/arithmetic/log2.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { factory } from '../../utils/factory.js'
2-
import { deepMap } from '../../utils/collection.js'
31
import { log2Number } from '../../plain/number/index.js'
2+
import { promoteLogarithm } from '../../utils/bigint.js'
3+
import { deepMap } from '../../utils/collection.js'
4+
import { factory } from '../../utils/factory.js'
45

56
const name = 'log2'
67
const dependencies = ['typed', 'config', 'Complex']
@@ -31,24 +32,30 @@ export const createLog2 = /* #__PURE__ */ factory(name, dependencies, ({ typed,
3132
* @return {number | BigNumber | Complex | Array | Matrix}
3233
* Returns the 2-base logarithm of `x`
3334
*/
35+
function complexLog2Number (x) {
36+
return _log2Complex(new Complex(x, 0))
37+
}
38+
3439
return typed(name, {
3540
number: function (x) {
3641
if (x >= 0 || config.predictable) {
3742
return log2Number(x)
3843
} else {
3944
// negative value -> complex value computation
40-
return _log2Complex(new Complex(x, 0))
45+
return complexLog2Number(x)
4146
}
4247
},
4348

49+
bigint: promoteLogarithm(4, log2Number, config, complexLog2Number),
50+
4451
Complex: _log2Complex,
4552

4653
BigNumber: function (x) {
4754
if (!x.isNegative() || config.predictable) {
4855
return x.log(2)
4956
} else {
5057
// downgrade to number, return Complex valued result
51-
return _log2Complex(new Complex(x.toNumber(), 0))
58+
return complexLog2Number(x.toNumber())
5259
}
5360
},
5461

src/function/probability/randomInt.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { createRng } from './util/seededRNG.js'
44
import { isMatrix } from '../../utils/is.js'
55

66
const name = 'randomInt'
7-
const dependencies = ['typed', 'config', '?on']
7+
const dependencies = ['typed', 'config', 'log2', '?on']
88

9-
export const createRandomInt = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, on }) => {
9+
const simpleCutoff = 2n ** 30n
10+
11+
export const createRandomInt = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, log2, on }) => {
1012
// seeded pseudo random number generator
1113
let rng = createRng(config.randomSeed)
1214

@@ -24,7 +26,7 @@ export const createRandomInt = /* #__PURE__ */ factory(name, dependencies, ({ ty
2426
*
2527
* Syntax:
2628
*
27-
* math.randomInt() // generate a random integer between 0 and 1
29+
* math.randomInt() // generate either 0 or 1, randomly
2830
* math.randomInt(max) // generate a random integer between 0 and max
2931
* math.randomInt(min, max) // generate a random integer between min and max
3032
* math.randomInt(size) // generate a matrix with random integer between 0 and 1
@@ -48,9 +50,11 @@ export const createRandomInt = /* #__PURE__ */ factory(name, dependencies, ({ ty
4850
* @return {number | Array | Matrix} A random integer value
4951
*/
5052
return typed(name, {
51-
'': () => _randomInt(0, 1),
53+
'': () => _randomInt(0, 2),
5254
number: (max) => _randomInt(0, max),
5355
'number, number': (min, max) => _randomInt(min, max),
56+
bigint: (max) => _randomBigint(0n, max),
57+
'bigint, bigint': _randomBigint,
5458
'Array | Matrix': (size) => _randomIntMatrix(size, 0, 1),
5559
'Array | Matrix, number': (size, max) => _randomIntMatrix(size, 0, max),
5660
'Array | Matrix, number, number': (size, min, max) => _randomIntMatrix(size, min, max)
@@ -64,4 +68,23 @@ export const createRandomInt = /* #__PURE__ */ factory(name, dependencies, ({ ty
6468
function _randomInt (min, max) {
6569
return Math.floor(min + rng() * (max - min))
6670
}
71+
72+
function _randomBigint (min, max) {
73+
const width = max - min // number of choices
74+
if (width <= simpleCutoff) { // do it with number type
75+
return min + BigInt(_randomInt(0, Number(width)))
76+
}
77+
// Too big to choose accurately that way. Instead, choose the correct
78+
// number of random bits to cover the width, and repeat until the
79+
// resulting number falls within the width
80+
const bits = log2(width)
81+
let picked = width
82+
while (picked >= width) {
83+
picked = 0n
84+
for (let i = 0; i < bits; ++i) {
85+
picked = 2n * picked + ((rng() < 0.5) ? 0n : 1n)
86+
}
87+
}
88+
return min + picked
89+
}
6790
})

src/function/relational/larger.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ const name = 'larger'
1111
const dependencies = [
1212
'typed',
1313
'config',
14+
'bignumber',
1415
'matrix',
1516
'DenseMatrix',
1617
'concat',
1718
'SparseMatrix'
1819
]
1920

20-
export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, DenseMatrix, concat, SparseMatrix }) => {
21+
export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, bignumber, matrix, DenseMatrix, concat, SparseMatrix }) => {
2122
const matAlgo03xDSf = createMatAlgo03xDSf({ typed })
2223
const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix })
2324
const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix })
@@ -55,20 +56,30 @@ export const createLarger = /* #__PURE__ */ factory(name, dependencies, ({ typed
5556
* @param {number | BigNumber | bigint | Fraction | boolean | Unit | string | Array | Matrix} y Second value to compare
5657
* @return {boolean | Array | Matrix} Returns true when the x is larger than y, else returns false
5758
*/
59+
function bignumLarger (x, y) {
60+
return x.gt(y) && !bigNearlyEqual(x, y, config.relTol, config.absTol)
61+
}
62+
5863
return typed(
5964
name,
6065
createLargerNumber({ typed, config }),
6166
{
6267
'boolean, boolean': (x, y) => x > y,
6368

64-
'BigNumber, BigNumber': function (x, y) {
65-
return x.gt(y) && !bigNearlyEqual(x, y, config.relTol, config.absTol)
66-
},
69+
'BigNumber, BigNumber': bignumLarger,
6770

6871
'bigint, bigint': (x, y) => x > y,
6972

7073
'Fraction, Fraction': (x, y) => (x.compare(y) === 1),
7174

75+
'Fraction, BigNumber': function (x, y) {
76+
return bignumLarger(bignumber(x), y)
77+
},
78+
79+
'BigNumber, Fraction': function (x, y) {
80+
return bignumLarger(x, bignumber(y))
81+
},
82+
7283
'Complex, Complex': function () {
7384
throw new TypeError('No ordering relation is defined for complex numbers')
7485
}

src/function/relational/smaller.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ const name = 'smaller'
1111
const dependencies = [
1212
'typed',
1313
'config',
14+
'bignumber',
1415
'matrix',
1516
'DenseMatrix',
1617
'concat',
1718
'SparseMatrix'
1819
]
1920

20-
export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, matrix, DenseMatrix, concat, SparseMatrix }) => {
21+
export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, bignumber, matrix, DenseMatrix, concat, SparseMatrix }) => {
2122
const matAlgo03xDSf = createMatAlgo03xDSf({ typed })
2223
const matAlgo07xSSf = createMatAlgo07xSSf({ typed, SparseMatrix })
2324
const matAlgo12xSfs = createMatAlgo12xSfs({ typed, DenseMatrix })
@@ -55,20 +56,30 @@ export const createSmaller = /* #__PURE__ */ factory(name, dependencies, ({ type
5556
* @param {number | BigNumber | bigint | Fraction | boolean | Unit | string | Array | Matrix} y Second value to compare
5657
* @return {boolean | Array | Matrix} Returns true when the x is smaller than y, else returns false
5758
*/
59+
function bignumSmaller (x, y) {
60+
return x.lt(y) && !bigNearlyEqual(x, y, config.relTol, config.absTol)
61+
}
62+
5863
return typed(
5964
name,
6065
createSmallerNumber({ typed, config }),
6166
{
6267
'boolean, boolean': (x, y) => x < y,
6368

64-
'BigNumber, BigNumber': function (x, y) {
65-
return x.lt(y) && !bigNearlyEqual(x, y, config.relTol, config.absTol)
66-
},
69+
'BigNumber, BigNumber': bignumSmaller,
6770

6871
'bigint, bigint': (x, y) => x < y,
6972

7073
'Fraction, Fraction': (x, y) => (x.compare(y) === -1),
7174

75+
'Fraction, BigNumber': function (x, y) {
76+
return bignumSmaller(bignumber(x), y)
77+
},
78+
79+
'BigNumber, Fraction': function (x, y) {
80+
return bignumSmaller(x, bignumber(y))
81+
},
82+
7283
'Complex, Complex': function (x, y) {
7384
throw new TypeError('No ordering relation is defined for complex numbers')
7485
}

src/function/statistics/max.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export const createMax = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
8383

8484
deepForEach(array, function (value) {
8585
try {
86-
if (isNaN(value) && typeof value === 'number') {
86+
if (typeof value === 'number' && isNaN(value)) {
8787
res = NaN
8888
} else if (res === undefined || larger(value, res)) {
8989
res = value

src/function/statistics/min.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export const createMin = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
8383

8484
deepForEach(array, function (value) {
8585
try {
86-
if (isNaN(value) && typeof value === 'number') {
86+
if (typeof value === 'number' && isNaN(value)) {
8787
min = NaN
8888
} else if (min === undefined || smaller(value, min)) {
8989
min = value

src/function/string/print.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const createPrint = /* #__PURE__ */ factory(name, dependencies, ({ typed
2424
* // the following outputs: 'The value of pi is 3.141592654'
2525
* math.print('The value of pi is $pi', {pi: math.pi}, 10)
2626
*
27-
* // the following outputs: 'hello Mary! The date is 2013-03-23'
27+
* // the following outputs: 'Hello Mary! The date is 2013-03-23'
2828
* math.print('Hello $user.name! The date is $date', {
2929
* user: {
3030
* name: 'Mary',

src/function/utils/isInteger.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const createIsInteger = /* #__PURE__ */ factory(name, dependencies, ({ ty
2525
* math.isInteger(math.fraction(4)) // returns true
2626
* math.isInteger('3') // returns true
2727
* math.isInteger([3, 0.5, -2]) // returns [true, false, true]
28-
* math.isInteger(math.complex('2-4i')) // throws an error
28+
* math.isInteger(math.complex('2-4i')) // throws TypeError
2929
*
3030
* See also:
3131
*

0 commit comments

Comments
 (0)