Skip to content

fix(multipleOf): use float-safe modulo to prevent false positives with decimal requirements#1422

Open
Vadaski wants to merge 5 commits intoopen-circle:mainfrom
Vadaski:fix/multipleOf-float-precision
Open

fix(multipleOf): use float-safe modulo to prevent false positives with decimal requirements#1422
Vadaski wants to merge 5 commits intoopen-circle:mainfrom
Vadaski:fix/multipleOf-float-precision

Conversation

@Vadaski
Copy link
Copy Markdown

@Vadaski Vadaski commented Mar 12, 2026

Problem

v.multipleOf() incorrectly rejects valid inputs when the requirement is a decimal number, because JavaScript's % operator introduces floating-point precision errors.

const Length = v.pipe(v.number(), v.multipleOf(0.01));
v.parse(Length, 3); // throws — but 3 is a valid multiple of 0.01
v.parse(Length, 1.5); // throws — but 1.5 is a valid multiple of 0.01

The root cause: 3 % 0.01 in JavaScript is ~2.22e-16 instead of 0.

Reported in #1375.

Fix

Scale both operands to integers before taking the modulo:

  1. Compute the maximum decimal places of value and requirement via _getDecimalPlaces()
  2. Multiply both by 10^decimalPlaces using Math.round() to neutralize any remaining float error at the scale boundary
  3. Compare the integer remainder to 0

The bigint path is unchanged in behavior; the explicit typeof narrowing now allows !== 0n without the previous @ts-expect-error.

Tests added

// These now pass correctly:
// multipleOf(0.01) on [3, 0.1, 0.03, 1.5]
// multipleOf(0.1)  on [0.3]
// multipleOf(0.25) on [0.75]

// These still correctly fail:
// multipleOf(0.01) on [-Infinity, Infinity, NaN]

Known limitation

For pathological inputs with very high decimal precision combined with very large values (e.g. multipleOf(1e-17) on Number.MAX_SAFE_INTEGER), the scaled integer can exceed Number.MAX_SAFE_INTEGER and lose precision. This matches the inherent limitation of IEEE 754 arithmetic. Practical uses of multipleOf with reasonable decimal requirements (like 0.01, 0.1, 0.25) are not affected.

Summary by CodeRabbit

  • Bug Fixes

    • Improved multiple-of validation to correctly handle decimal precision (including scientific notation), bigint zero behavior, and special numeric values to avoid incorrect pass/fail results.
  • Tests

    • Expanded test coverage for decimal and bigint scenarios, covering fractional values, scientific-notation inputs, Infinity/NaN cases, and zero-requirement edge cases.

…h decimal requirements

JavaScript % operator produces floating-point precision errors with decimal
requirements, e.g. 3 % 0.01 is ~2.22e-16 instead of 0, causing multipleOf(0.01)
to incorrectly reject valid inputs like 3 or 1.5.

Fix by scaling both operands to integers before the modulo check:
- Compute max decimal places of value and requirement via _getDecimalPlaces()
- Multiply both by 10^decimalPlaces and round to nearest integer
- Compare integer remainder to 0

The bigint path now uses explicit typeof narrowing and compares to 0n,
removing the need for the @ts-expect-error suppression.

Fixes: open-circle#1375

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 19:27
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 12, 2026

@Vadaski is attempting to deploy a commit to the Open Circle Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 12, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds decimal-aware divisibility to multipleOf by computing decimal places and scaling numbers for integer modulus checks; preserves bigint behavior with special zero handling. Introduces a decimal-place utility and expands tests to cover decimals, scientific notation, infinities, NaN, and bigint zero cases.

Changes

Cohort / File(s) Summary
multipleOf core
library/src/actions/multipleOf/multipleOf.ts
Refactors validation to branch on value type: for number computes decimal places, scales & rounds values to perform integer modulus checks (with early-return for zero vs finite non‑zero requirement); for bigint enforces strict modulus and treats 0n requirement as immediate failure.
Decimal utility
library/src/actions/multipleOf/utils/_getDecimalPlaces.ts, library/src/actions/multipleOf/utils/index.ts
Adds _getDecimalPlaces to compute decimal places (handles decimals and scientific notation) and re-exports it from the utils index.
Tests
library/src/actions/multipleOf/multipleOf.test.ts
Expands test coverage: adds valid/invalid cases for decimal requirements (0.01, 0.1, 0.25, 1e-7), scientific notation, Infinity/-Infinity/NaN, integer/zero requirement mismatches, and bigint zero behavior; asserts specific expected format strings in issues.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I count the places, tiny and neat,
Scaling decimals on nimble feet.
Bigints stand steady, zeros draw a line,
Tests hop in order, all numbers align. 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: using float-safe modulo to prevent false positives with decimal requirements in multipleOf validation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can suggest fixes for GitHub Check annotations.

Configure the reviews.tools.github-checks setting to adjust the time to wait for GitHub Checks to complete.

@dosubot dosubot Bot added the fix A smaller enhancement or bug fix label Mar 12, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes multipleOf() validation for decimal requirements by avoiding floating-point modulo precision issues in JavaScript.

Changes:

  • Add _getDecimalPlaces() and switch number-path validation to integer-scaled modulo with rounding.
  • Keep bigint behavior while removing the prior @ts-expect-error via explicit type narrowing.
  • Add tests for valid decimal multiples and for Infinity/NaN with a decimal requirement.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
library/src/actions/multipleOf/multipleOf.ts Implements float-safer multiple checking by scaling to integers and adds _getDecimalPlaces() helper.
library/src/actions/multipleOf/multipleOf.test.ts Adds test coverage for decimal multipleOf success cases and non-finite number failures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +195 to +199
if (
Math.round(dataset.value * multiplier) %
Math.round(this.requirement * multiplier) !==
0
) {
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multiplier = 10 ** decimalPlaces can become Infinity when decimalPlaces > 308 (e.g. requirement 1e-309 => 309). In that case value * multiplier becomes NaN for 0 (0 * Infinity) and the remainder check always fails, so valid multiples like 0 or 1e-309 would incorrectly produce an issue. Consider guarding for non-finite multiplier / scaled operands and falling back to a safer check (e.g. direct % or value / requirement closeness-to-integer) when scaling isn’t possible.

Suggested change
if (
Math.round(dataset.value * multiplier) %
Math.round(this.requirement * multiplier) !==
0
) {
let isMultiple = true;
// Preserve existing behavior: if requirement is 0, always treat as invalid.
if (this.requirement === 0) {
isMultiple = false;
} else {
const scaledValue = dataset.value * multiplier;
const scaledRequirement = this.requirement * multiplier;
if (
Number.isFinite(multiplier) &&
Number.isFinite(scaledValue) &&
Number.isFinite(scaledRequirement)
) {
const roundedValue = Math.round(scaledValue);
const roundedRequirement = Math.round(scaledRequirement);
// If roundedRequirement becomes 0 due to rounding, fall back to division check.
if (roundedRequirement !== 0) {
isMultiple = roundedValue % roundedRequirement === 0;
} else {
const quotient = dataset.value / this.requirement;
if (Number.isFinite(quotient)) {
const nearest = Math.round(quotient);
const EPSILON = 1e-12;
isMultiple = Math.abs(quotient - nearest) <= EPSILON;
} else {
isMultiple = false;
}
}
} else {
// Fallback when scaling is not possible (e.g., multiplier is Infinity).
const quotient = dataset.value / this.requirement;
if (Number.isFinite(quotient)) {
const nearest = Math.round(quotient);
const EPSILON = 1e-12;
isMultiple = Math.abs(quotient - nearest) <= EPSILON;
} else {
isMultiple = false;
}
}
}
if (!isMultiple) {

Copilot uses AI. Check for mistakes.
typeof this.requirement === 'bigint' &&
dataset.value % this.requirement !== 0n
) {
_addIssue(this, 'multiple', dataset, config);
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new type narrowing, mismatched runtime types (e.g. dataset.value is a number but requirement is a bigint, or vice versa) will now skip validation entirely and return the dataset without an issue. Previously, the unguarded % would throw on mixed number/bigint, preventing a silent pass. It would be safer to explicitly handle the mixed-type case (e.g. add an issue or throw a clear error) so incorrect schema/action combinations don’t become false negatives.

Suggested change
_addIssue(this, 'multiple', dataset, config);
_addIssue(this, 'multiple', dataset, config);
} else {
// Mismatched or unsupported typed values: treat as a validation failure
_addIssue(this, 'multiple', dataset, config);

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +72
test('for valid decimal numbers', () => {
expectNoActionIssue(multipleOf(0.01), [3, 0.1, 0.03, 1.5]);
expectNoActionIssue(multipleOf(0.1), [0.3]);
expectNoActionIssue(multipleOf(0.25), [0.75]);
});
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new tests cover valid decimal multiples and non-finite inputs, but there’s no coverage for invalid decimal multiples (e.g. multipleOf(0.01) rejecting values like 0.005 / 0.015 / 1.234). Adding at least one failing case would help ensure the integer-scaling modulo logic doesn’t accidentally accept non-multiples due to rounding.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
library/src/actions/multipleOf/multipleOf.test.ts (1)

68-72: Good test coverage for decimal precision fix.

The test cases directly verify the fix for the floating-point precision issue (e.g., 0.3 % 0.1 would incorrectly fail with naive modulo).

Consider adding negative decimal values for completeness (e.g., -0.1 with multipleOf(0.01)), though Math.abs() in _getDecimalPlaces should handle them correctly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library/src/actions/multipleOf/multipleOf.test.ts` around lines 68 - 72, Add
negative decimal cases to the existing decimal-precision test to cover negative
values: update the test 'for valid decimal numbers' to include negative numbers
(e.g., call expectNoActionIssue(multipleOf(0.01), [..., -0.1]) and similar for
other multiples) so that multipleOf and the helper _getDecimalPlaces are
exercised with negative inputs; ensure you add negative counterparts for the
existing positive test vectors (for example -0.1 with multipleOf(0.01), -0.3 for
multipleOf(0.1), -0.75 for multipleOf(0.25)) using the same expectNoActionIssue
helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@library/src/actions/multipleOf/multipleOf.test.ts`:
- Around line 68-72: Add negative decimal cases to the existing
decimal-precision test to cover negative values: update the test 'for valid
decimal numbers' to include negative numbers (e.g., call
expectNoActionIssue(multipleOf(0.01), [..., -0.1]) and similar for other
multiples) so that multipleOf and the helper _getDecimalPlaces are exercised
with negative inputs; ensure you add negative counterparts for the existing
positive test vectors (for example -0.1 with multipleOf(0.01), -0.3 for
multipleOf(0.1), -0.75 for multipleOf(0.25)) using the same expectNoActionIssue
helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f518431-d2e0-4b39-8a9f-8ce1f5b0ceff

📥 Commits

Reviewing files that changed from the base of the PR and between 3430f07 and 1731b1a.

📒 Files selected for processing (2)
  • library/src/actions/multipleOf/multipleOf.test.ts
  • library/src/actions/multipleOf/multipleOf.ts

Extend decimal-precision test cases with negative counterparts to
exercise the Math.abs() path in _getDecimalPlaces() more thoroughly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fabian-hiller
Copy link
Copy Markdown
Member

Thank you for creating a PR. Here some feedback:

  1. Performance — recomputing on every call: _getDecimalPlaces(this.requirement) runs inside ~run, meaning it's recalculated on every validation even though the requirement is constant. Should be precomputed once in the factory and closed over.
  2. Helper function placement: Defining const _getDecimalPlaces at module scope is unusual for this codebase. Actions with helpers use a utils/ subfolder (like isbn/utils/) and the function keyword.
  3. Missing test edge cases: No tests for values that should fail decimal checks (e.g., multipleOf(0.01) with 0.001), division by zero (multipleOf(0)), or integer requirement with decimal value (this is AI feedback, not sure if it is true and relevant).

@fabian-hiller
Copy link
Copy Markdown
Member

fabian-hiller commented Mar 13, 2026

Current _getDecimalPlaces allocates heavily: toString() + 2x split() creates ~5 heap objects (1 string, 2 arrays, sub-strings) per call. In a hot validation path (e.g., validating array items), this adds GC pressure.

Alternative A — Optimized string (drop-in replacement):
Use indexOf + slice instead of split to avoid array allocations (~2 allocations instead of ~5):

const _getDecimalPlaces = (value: number): number => {
  const s = String(Math.abs(value));
  const dotIndex = s.indexOf('.');
  const eIndex = s.indexOf('e');
  if (dotIndex === -1 && eIndex === -1) return 0;
  const decimals = dotIndex === -1 ? 0 : (eIndex === -1 ? s.length : eIndex) - dotIndex - 1;
  if (eIndex === -1) return decimals;
  return Math.max(0, decimals - Number(s.slice(eIndex + 1)));
};

Alternative B — Epsilon-based (eliminates _getDecimalPlaces entirely):
Zero allocations, pure arithmetic. Replace the entire scaling approach with a remainder + epsilon check:

const remainder = Math.abs(value % requirement);
const tolerance = Number.EPSILON * Math.max(Math.abs(value), Math.abs(requirement));
if (remainder > tolerance && Math.abs(remainder - Math.abs(requirement)) > tolerance) {
  _addIssue(this, 'multiple', dataset, config);
}

The double check is needed because % can produce a result near 0 OR near requirement due to float rounding. For the motivating case: 3 % 0.01 ≈ 2.22e-16, and Number.EPSILON * 3 ≈ 6.66e-16, so 2.22e-16 < tolerance → correctly passes.

Trade-off summary:

Current (PR) Alt A (indexOf) Alt B (epsilon)
Allocations/call ~5 ~2 0
Correctness Exact (within MAX_SAFE_INTEGER) Same Approximate
Complexity Medium Medium Low

This was created by Claude Code 🤖. Let me know if it is wrong or complicates the code.

@fabian-hiller fabian-hiller self-assigned this Mar 13, 2026
@fabian-hiller fabian-hiller added this to the v1.4 milestone Mar 13, 2026
…laces, add tests

Address 3 mandatory points from PR open-circle#1422 maintainer review:

1. Extract _getDecimalPlaces to utils/_getDecimalPlaces.ts following isbn/utils/
   pattern; use function keyword and indexOf/slice to reduce heap allocations.

2. Precompute _getDecimalPlaces(requirement) once in the factory closure instead
   of recalculating on every validation call.

3. Add missing test cases: invalid decimal multiples, decimal values with integer
   requirement, and zero requirement (division by zero always invalid).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Mar 14, 2026
@Vadaski
Copy link
Copy Markdown
Author

Vadaski commented Mar 14, 2026

Good catches, thank you.

All three points addressed in the latest commit:

  • Helper placement: _getDecimalPlaces is now in utils/_getDecimalPlaces.ts using the function keyword, mirroring the isbn/utils/ pattern. Also switched to indexOf/slice instead of split to reduce allocations.
  • Precomputed in factory: _getDecimalPlaces(requirement) is now computed once before return {} and closed over — ~run only calls it for dataset.value.
  • Missing tests: Added three test cases — invalid decimal multiples (0.001, 0.015, 1.234 against multipleOf(0.01)), decimal values with an integer requirement (1.5, 0.5 against multipleOf(2)), and zero requirement (always invalid).

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
library/src/actions/multipleOf/multipleOf.test.ts (1)

68-72: Add one regression case for scientific-notation inputs.

Given _getDecimalPlaces has a dedicated exponent path (Line 12-31 in library/src/actions/multipleOf/utils/_getDecimalPlaces.ts), add at least one passing and one failing e-notation case (e.g., multipleOf(1e-7) with 2e-7 and 3e-8) to lock that branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library/src/actions/multipleOf/multipleOf.test.ts` around lines 68 - 72, Add
regression tests exercising the exponent branch of _getDecimalPlaces by adding
scientific-notation cases to multipleOf.test.ts: call
expectNoActionIssue(multipleOf(1e-7), [2e-7]) as a passing case and
expectActionIssue or the inverse helper with [3e-8] as a failing case (or
similar pair like multipleOf(1e-3) with 2e-3 pass and 3e-4 fail); use the
existing helpers expectNoActionIssue/expectActionIssue and the multipleOf
factory so the e-notation path in _getDecimalPlaces is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@library/src/actions/multipleOf/multipleOf.test.ts`:
- Around line 68-72: Add regression tests exercising the exponent branch of
_getDecimalPlaces by adding scientific-notation cases to multipleOf.test.ts:
call expectNoActionIssue(multipleOf(1e-7), [2e-7]) as a passing case and
expectActionIssue or the inverse helper with [3e-8] as a failing case (or
similar pair like multipleOf(1e-3) with 2e-3 pass and 3e-4 fail); use the
existing helpers expectNoActionIssue/expectActionIssue and the multipleOf
factory so the e-notation path in _getDecimalPlaces is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: feacd407-302b-4e99-b38c-bbc845fae9db

📥 Commits

Reviewing files that changed from the base of the PR and between 1bfc01b and 9808b6d.

📒 Files selected for processing (4)
  • library/src/actions/multipleOf/multipleOf.test.ts
  • library/src/actions/multipleOf/multipleOf.ts
  • library/src/actions/multipleOf/utils/_getDecimalPlaces.ts
  • library/src/actions/multipleOf/utils/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • library/src/actions/multipleOf/multipleOf.ts

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 15, 2026

Open in StackBlitz

npm i https://pkg.pr.new/valibot@1422

commit: 9808b6d

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +166 to +176
const decimalPlaces = Math.max(
_getDecimalPlaces(dataset.value),
requirementDecimalPlaces
);
const multiplier = 10 ** decimalPlaces;

if (
Math.round(dataset.value * multiplier) %
Math.round(this.requirement * multiplier) !==
0
) {
Comment on lines +181 to +184
typeof this.requirement === 'bigint' &&
dataset.value % this.requirement !== 0n
) {
_addIssue(this, 'multiple', dataset, config);
expectNoActionIssue(multipleOf(0.1), [0.3, -0.3]);
expectNoActionIssue(multipleOf(0.25), [0.75, -0.75]);
});

- Prevent RangeError when requirement is 0n by treating it as always invalid
- Short-circuit value === 0 for finite nonzero requirements to avoid
  Infinity multiplier overflow in decimal scaling path
- Add scientific-notation test coverage for _getDecimalPlaces exponent branch
- Add zero bigint requirement regression test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Vadaski
Copy link
Copy Markdown
Author

Vadaski commented Mar 20, 2026

Latest push addresses two edge cases I missed:

  • bigint 0n requirement: value % 0n throws a RangeError. Now guarded — multipleOf(0n) treats all values as invalid, matching the number 0 behavior.
  • Numeric zero with extreme requirements: 0 is always a valid multiple of any finite nonzero number, so it now short-circuits before the decimal scaling path. The guard explicitly checks Number.isFinite(requirement) to avoid silently passing 0 for NaN/Infinity requirements.

Also added scientific-notation test cases (1e-7) to cover the exponent parsing branch in _getDecimalPlaces.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@library/src/actions/multipleOf/multipleOf.ts`:
- Around line 166-179: The scaled-modulo can overflow because multiplier = 10 **
decimalPlaces may be Infinity; update the multiple check in the multipleOf logic
to guard for that: compute decimalPlaces via _getDecimalPlaces(...) and
multiplier as before, then if Number.isFinite(multiplier) perform the existing
Math.round(dataset.value * multiplier) % Math.round(this.requirement *
multiplier) check, otherwise fall back to a native modulo check (e.g.,
dataset.value % this.requirement === 0 or an appropriate tolerance-based
comparison) so the function (references: dataset.value, this.requirement,
_getDecimalPlaces, decimalPlaces, multiplier) returns correct results for
subnormal/small values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cdc5d356-ce6a-4d3c-8710-68e05e76cdc8

📥 Commits

Reviewing files that changed from the base of the PR and between 9808b6d and 7bad2e7.

📒 Files selected for processing (2)
  • library/src/actions/multipleOf/multipleOf.test.ts
  • library/src/actions/multipleOf/multipleOf.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • library/src/actions/multipleOf/multipleOf.test.ts

Comment thread library/src/actions/multipleOf/multipleOf.ts
When decimalPlaces > 308 (e.g. requirement = 5e-324), 10 ** decimalPlaces
overflows to Infinity, causing Math.round(value * Infinity) to produce NaN
and incorrectly reject valid multiples. Fall back to native % operator
when the multiplier is not finite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
library/src/actions/multipleOf/multipleOf.ts (1)

166-186: Consider explicit guard for requirement === 0 with numbers for consistency.

The bigint path explicitly guards against 0n requirement (lines 191-194), but the number path relies on implicit NaN behavior when 0 % 0 is computed. While functionally correct (NaN fails the check), explicit handling would improve clarity and consistency:

          if (dataset.value === 0 && this.requirement !== 0 && Number.isFinite(this.requirement)) {
            return dataset;
          }
+
+          if (this.requirement === 0) {
+            _addIssue(this, 'multiple', dataset, config);
+            return dataset;
+          }

          const decimalPlaces = Math.max(

This also avoids unnecessary _getDecimalPlaces computation when the requirement is zero.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library/src/actions/multipleOf/multipleOf.ts` around lines 166 - 186, Add an
explicit guard for the numeric-path when this.requirement === 0 (mirroring the
bigint branch) before calling _getDecimalPlaces: if this.requirement === 0 then
return dataset when dataset.value === 0, otherwise call _addIssue(this,
'multiple', dataset, config); place this check before computing
decimalPlaces/multiplier to avoid unnecessary work and to make behavior
consistent with the bigint 0n handling (symbols: this.requirement,
dataset.value, _getDecimalPlaces, multiplier, _addIssue).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@library/src/actions/multipleOf/multipleOf.ts`:
- Around line 166-186: Add an explicit guard for the numeric-path when
this.requirement === 0 (mirroring the bigint branch) before calling
_getDecimalPlaces: if this.requirement === 0 then return dataset when
dataset.value === 0, otherwise call _addIssue(this, 'multiple', dataset,
config); place this check before computing decimalPlaces/multiplier to avoid
unnecessary work and to make behavior consistent with the bigint 0n handling
(symbols: this.requirement, dataset.value, _getDecimalPlaces, multiplier,
_addIssue).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d37fd08-4048-4f03-b918-ca062e81a061

📥 Commits

Reviewing files that changed from the base of the PR and between 7bad2e7 and 7991a83.

📒 Files selected for processing (1)
  • library/src/actions/multipleOf/multipleOf.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix A smaller enhancement or bug fix size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants