RJ
RJ2y ago

Arbitrary-precision decimals without `Math.random`

I needed to perform some arithmetic (safely)—specifically, I needed to convert a JS number representing dollars into a JS number representing cents. I tried using https://github.com/MikeMcl/bignumber.js for this in a mutation, but discovered that it imports Math.random. I ended up settling for https://github.com/MikeMcl/decimal.js-light, which is just a trimmed-down version of https://github.com/MikeMcl/decimal.js (yes, these are all written by the same person—and those aren't even all of the arbitrary-precision decimal libraries he maintains!), as it happens to not import Math.random, and still accomplishes all of what I need. I guess this is either a question or an offering of advice—the question is: is anyone aware of another library I ought to have considered for this task (I can't imagine I'll be the only Convex user to have this requirement)? And the offering is: if anyone is looking for such a library, I can confirm that https://github.com/MikeMcl/decimal.js-light works!
7 Replies
sshader
sshader2y ago
What's the concern with using Math.random here? Convex queries and mutations should provide Math.random (but we just use the same seed each time we run a particular query / mutation)
RJ
RJOP2y ago
Oh, I guess I misunderstood the constraints here, then. The concern was that Convex bundling was failing, with some language about Math.random! I can test it again in a little bit and report back with the actual error message I was observing Here it is:
400 Bad Request: InvalidModules: Loading the pushed modules encountered the following
error:
Failed to analyze mutations/internal/insertPurchaseOrders.js: Uncaught Error: Math.random unsupported at import time
at performOp (../../udf-system/src/syscall.ts:43:20)
at global.Math.random [as random] (../../udf-system/src/setup.ts:102:2)
at <anonymous> (../../../node_modules/bignumber.js/bignumber.mjs:668:43)
at clone (../../../node_modules/bignumber.js/bignumber.mjs:799:2)
at <anonymous> (../../../node_modules/bignumber.js/bignumber.mjs:2888:7)

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error
at WatchContext.fatalError (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:251172:11)
at Command2.<anonymous> (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:251286:20)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Command2.parseAsync (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:1034:9)
at async main2 (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:251507:5)
400 Bad Request: InvalidModules: Loading the pushed modules encountered the following
error:
Failed to analyze mutations/internal/insertPurchaseOrders.js: Uncaught Error: Math.random unsupported at import time
at performOp (../../udf-system/src/syscall.ts:43:20)
at global.Math.random [as random] (../../udf-system/src/setup.ts:102:2)
at <anonymous> (../../../node_modules/bignumber.js/bignumber.mjs:668:43)
at clone (../../../node_modules/bignumber.js/bignumber.mjs:799:2)
at <anonymous> (../../../node_modules/bignumber.js/bignumber.mjs:2888:7)

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error
at WatchContext.fatalError (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:251172:11)
at Command2.<anonymous> (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:251286:20)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Command2.parseAsync (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:1034:9)
at async main2 (/Users/rjdellecese/code/globecommerce/node_modules/convex/dist/cli.bundle.cjs:251507:5)
lee
lee2y ago
thanks for sharing! this part looks interesting https://github.com/MikeMcl/bignumber.js/blob/master/bignumber.mjs#L665. we currently don't allow Math.random() at import time, because nondeterminism could mess with our function analysis. It's a contrived example, but const myFunction = Math.random() > 0.5 ? query(...) : mutation(...) would be hard to detect & handle. We've received this feature request before though, and we would like to make npm packages like bignumber libraries work. will keep you updated 🙂
GitHub
bignumber.js/bignumber.mjs at master · MikeMcl/bignumber.js
A JavaScript library for arbitrary-precision decimal and non-decimal arithmetic - bignumber.js/bignumber.mjs at master · MikeMcl/bignumber.js
RJ
RJOP2y ago
I see, so Math.random() being invoked at import time is not permitted, but Math.random() invocations at runtime are? I suppose I forgot that JS imports might evaluate code 😰
sshader
sshader2y ago
so Math.random() being invoked at import time is not permitted, but Math.random() invocations at runtime are?
yup exactly Sounds like you have the trimmed down version that does what you want, but another potential workaround would be doing something like require("bignumber.js") within your mutation so it executes all those Math.random calls while executing your mutation (I tried this successfully now, but don't fully understand whether this would always work)
RJ
RJOP2y ago
I do, but good to keep in mind as another possible option!
presley
presley2y ago
We now allow using Math.random at import time. The randomness for the global scope is frozen at deploy time. Hopefully, a lot of the libraries you tried before just work now in Convex's JS environment without additional gymnastics.

Did you find this page helpful?