Skip to content

Commit 3a818de

Browse files
fix(v4): handle multi-digit exponents in floatSafeRemainder (#5687)
* fix(v4): handle multi-digit exponents in floatSafeRemainder The regex `/\d?e-\d?/` only matches single-digit exponents, causing `multipleOf` validation to fail for steps like `1e-10` or smaller. For example, with `step = 1e-10`: - The regex matched `"1e-1"` instead of `"1e-10"` - Captured exponent was `"1"` instead of `"10"` - This caused incorrect decimal count, leading to NaN remainder Changed `\d?` to `\d+` to capture multi-digit exponents correctly. Bug discovered by whiterose (https://github.com/shakecodeslikecray/whiterose) * test: add regression test for multipleOf with scientific notation Ensures multipleOf correctly handles steps with multi-digit exponents like 1e-10 and 1e-15. This test would fail before the regex fix in floatSafeRemainder.
1 parent 55747b3 commit 3a818de

File tree

2 files changed

+18
-2
lines changed

2 files changed

+18
-2
lines changed

packages/zod/src/v4/classic/tests/number.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,22 @@ test(".multipleOf() with negative divisor", () => {
153153
expect(() => schema.parse(7.5)).toThrow();
154154
});
155155

156+
test(".multipleOf() with scientific notation (multi-digit exponents)", () => {
157+
// Regression test for https://github.com/colinhacks/zod/pull/5687
158+
// The regex was using \d? which only matches single-digit exponents
159+
const schema = z.number().multipleOf(1e-10);
160+
161+
// These should all pass - they are valid multiples of 1e-10
162+
expect(schema.parse(1e-10)).toEqual(1e-10);
163+
expect(schema.parse(5e-10)).toEqual(5e-10);
164+
expect(schema.parse(1e-9)).toEqual(1e-9); // 10 * 1e-10
165+
166+
// Test with 1e-15 (exponent = 15, two digits)
167+
const schema15 = z.number().multipleOf(1e-15);
168+
expect(schema15.parse(1e-15)).toEqual(1e-15);
169+
expect(schema15.parse(3e-15)).toEqual(3e-15);
170+
});
171+
156172
test(".step() validation", () => {
157173
const schemaPointOne = z.number().step(0.1);
158174
const schemaPointZeroZeroZeroOne = z.number().step(0.0001);

packages/zod/src/v4/core/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ export function floatSafeRemainder(val: number, step: number): number {
248248
const valDecCount = (val.toString().split(".")[1] || "").length;
249249
const stepString = step.toString();
250250
let stepDecCount = (stepString.split(".")[1] || "").length;
251-
if (stepDecCount === 0 && /\d?e-\d?/.test(stepString)) {
252-
const match = stepString.match(/\d?e-(\d?)/);
251+
if (stepDecCount === 0 && /\d?e-\d+/.test(stepString)) {
252+
const match = stepString.match(/\d?e-(\d+)/);
253253
if (match?.[1]) {
254254
stepDecCount = Number.parseInt(match[1]);
255255
}

0 commit comments

Comments
 (0)