Skip to content

fix: number.floor rounds toward -infinity (not toward zero)#5048

Open
mvanhorn wants to merge 1 commit intoobjectionary:masterfrom
mvanhorn:osc/5043-floor-semantics
Open

fix: number.floor rounds toward -infinity (not toward zero)#5048
mvanhorn wants to merge 1 commit intoobjectionary:masterfrom
mvanhorn:osc/5043-floor-semantics

Conversation

@mvanhorn
Copy link
Copy Markdown

@mvanhorn mvanhorn commented Apr 24, 2026

Summary

Changes number.floor in eo-runtime/src/main/eo/number.eo from truncation-toward-zero to mathematical floor (round toward -∞), matching the existing doc comment ("less than or equal to it") and the behavior of Math.floor in Java / Python / JS / Ruby.

Fixes #5043.

Why this matters

The issue (syncended) traces it to the doc/implementation mismatch:

number.floor performs truncation toward zero, but its own documentation and its name imply mathematical floor (rounding toward -∞).

Input Expected Before
3.7.floor 3 3
-3.7.floor -4 -3 ← wrong
-0.1.floor -1 0 ← wrong
floor. 13.div -5 -3 -2 ← wrong

PR #5042 (merged this morning) refactored the Java atom into an EO expression (as-i64.as-number) but preserved the truncation-toward-zero behavior — that PR's description explicitly notes "See also: #5043 (separate behavioral fix for floor rounding semantics)." This PR is that separate behavioral fix.

Change

eo-runtime/src/main/eo/number.eo floor body from:

[] > floor
  as-i64.as-number > @

to:

[] > floor
  if. > @
    is-finite.not
    $
    if.
      trunc.gt $
      trunc.minus 1
      trunc
  as-i64.as-number > trunc

Non-finite inputs (NaN, +∞, -∞) short-circuit via is-finite.not and are returned unchanged — this preserves the behavior the existing tests-positive-infinity-floor-is-equal-to-self and tests-negative-infinity-floor-is-equal-to-self tests codify. is-nan is not a separate case because is-finite already returns false for NaN, and the $-return branch returns NaN unchanged.

For finite inputs: trunc is the integer part (toward zero). trunc > $ is true only for negative non-integers (-3.7 → trunc=-3 > -3.7); in that one case subtract 1 to round toward -∞. For positive non-integers, zero, and integers, trunc is already the correct floor.

is-finite is safe to reuse here because its definition at number.eo:23-28 (is-nan.not and. not. or. eq positive-infinity eq negative-infinity) does not recurse through floor. The issue explicitly warns against reusing is-integer because that one is defined as is-finite and eq floor and would recurse.

Tests

Two existing tests codified the buggy truncation-toward-zero behavior — both needed updating:

  • tests-div-with-remainder: floor(13 / -5) now expects -3 (was -2)
  • tests-floor-negative-fraction: -3.7.floor now expects -4 (was -3); comment updated from "truncates toward zero" to "rounds toward -infinity"

Two new regression tests added next to the existing floor tests:

  • tests-floor-small-negative-fraction: -0.1.floor = -1 (guards the case where truncation would hit zero)
  • tests-floor-of-negative-integer: -42.floor = -42 (guards against over-eager subtraction in the integer case)

Existing tests-floor-positive-fraction (3.7.floor = 3) and tests-floor-of-integer (42.floor = 42) continue to pass unchanged.

Testing

I was not able to complete a full local mvn test -Dtest=EOnumberEOAtomTest run in a reasonable window on my environment (~10+ min into the build without finishing). Trusting upstream CI to catch any EO syntax issues in the new if. nesting. Happy to iterate on the review.

AI disclosure

This contribution was developed with AI assistance (Claude Code).

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced the floor function to correctly handle edge cases and improve mathematical accuracy. Non-finite numbers are now returned unchanged, while negative numbers and fractions are properly rounded toward negative infinity, ensuring correct behavior across all input types.

The current floor uses as-i64.as-number which truncates toward zero.
Mathematical floor rounds toward -infinity, which differs from
truncation only for negative non-integers.

Wrap the truncation in a correction step: if trunc is strictly greater
than the original (only possible when the input is a negative
non-integer), return trunc - 1; otherwise return trunc. Non-finite
inputs (NaN, +infinity, -infinity) short-circuit via is-finite.not
and are returned unchanged.

Also updates two existing tests (tests-div-with-remainder and
tests-floor-negative-fraction) that codified the incorrect
toward-zero behavior, and adds regression tests for the small
negative fraction (-0.1 -> -1) and negative-integer cases.

is-finite is safe to reuse here because its definition at
number.eo:23-28 does not recurse through floor (unlike is-integer,
which is defined as is-finite and eq floor).

Fixes objectionary#5043
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

The floor function in number.eo was updated to correctly implement mathematical flooring (rounding toward negative infinity) for negative decimal numbers, resolving discrepancies with its documentation. The implementation now explicitly handles non-finite numbers and adjusts truncated values conditionally. Related test cases were updated to reflect correct behavior.

Changes

Cohort / File(s) Summary
Floor Function and Tests
eo-runtime/src/main/eo/number.eo
Updated floor method to handle non-finite numbers by returning unchanged input; for finite numbers, computes truncated value and conditionally subtracts 1 when truncated result exceeds original (ensures correct floor behavior for negative inputs). Updated tests: corrected expected remainder for 13.div -5 from -2 to -3, adjusted negative-fraction expectations including -3.7.floor, and added tests for -0.1.floor and -42.floor.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • #5042: Both PRs modify the same number.floor function; this PR fixes the behavioral issue by implementing true mathematical floor, while #5042 introduced the truncation-based implementation that required correction.

Suggested labels

core

Poem

🐰 A floor that once stumbled on minus,
Now gracefully descends toward minus-infinity's guidance,
Negative decimals no longer betray,
Mathematical truth wins the day! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing number.floor to round toward -infinity instead of toward zero.
Linked Issues check ✅ Passed The pull request implementation fully satisfies the requirements in issue #5043: implements mathematical floor (round toward -∞), handles non-finite inputs by returning unchanged, avoids recursion with is-integer, updates incorrect tests, and adds regression tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the number.floor implementation and its test suite as specified in issue #5043; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@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 `@eo-runtime/src/main/eo/number.eo`:
- Around line 115-124: The floor method is incorrectly using bare `$` which
refers to the method object dataization and can create cycles; change those `$`
references inside the floor body to reference the enclosing number (use `^` or
bind the input to a name like `original` before transformations) so the
non-finite branch returns the original numeric value and the comparison
`trunc.gt` compares against that original number; update references in the floor
implementation (symbols: floor, trunc, is-finite, as-i64.as-number) and re-run
nan.floor, positive-infinity.floor and negative-infinity.floor tests on both JVM
and eo2js to confirm correct behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92c14285-06f5-440b-8a75-bfbe6aa48ae1

📥 Commits

Reviewing files that changed from the base of the PR and between 91db6ec and 1f89efd.

📒 Files selected for processing (1)
  • eo-runtime/src/main/eo/number.eo

Comment on lines +115 to +124
# For non-finite inputs (NaN, ±infinity) the input is returned unchanged.
[] > floor
as-i64.as-number > @
if. > @
is-finite.not
$
if.
trunc.gt $
trunc.minus 1
trunc
as-i64.as-number > trunc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) Find any use of a bare `$` as a value inside method bodies in .eo files.
rg -nP --type-add 'eo:*.eo' --type=eo -C3 '^\s*\$\s*$|[^\w$]\$(\s|$)' eo-runtime/src/main/eo

# 2) Find uses of `^` (parent reference) to see the established idiom for
#    "refer to the enclosing object from inside a method body".
rg -nP --type-add 'eo:*.eo' --type=eo -C3 '(^|[^\w])\^(\.|\s|$)' eo-runtime/src/main/eo | head -n 200

# 3) Surface the exact test objects that rely on floor for non-finite inputs,
#    to confirm they actually invoke `.floor` (and are not shadowed by `.@`):
rg -nP --type-add 'eo:*.eo' --type=eo -C2 'floor' eo-runtime/src/main/eo/positive-infinity.eo eo-runtime/src/main/eo/negative-infinity.eo

Repository: objectionary/eo

Length of output: 23296


Verify that $ refers to the enclosing number, not to the floor method itself.

In EO, ^ is the established pattern for referencing the enclosing parent object in method bodies. The implementation at lines 119 and 121 uses bare $, which is not found elsewhere in the codebase in this style.

Two locations are affected:

  • Line 119 ($ as the non-finite branch result): if $ dataizes through floor.@, this creates a cycle.
  • Line 121 (trunc.gt $): comparing trunc against $ dataizes the floor method rather than the input number.

Tests for negative-infinity.floor and positive-infinity.floor exist (negative-infinity.eo:502–503, positive-infinity.eo:515–516), but they must be confirmed to exercise the floor method correctly. If $ is incorrect, replace it with ^ (or an explicit binding like as-bytes.as-number > original). Verify that nan.floor, positive-infinity.floor, and negative-infinity.floor evaluate correctly on both JVM and eo2js runtimes before merging.

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

In `@eo-runtime/src/main/eo/number.eo` around lines 115 - 124, The floor method is
incorrectly using bare `$` which refers to the method object dataization and can
create cycles; change those `$` references inside the floor body to reference
the enclosing number (use `^` or bind the input to a name like `original` before
transformations) so the non-finite branch returns the original numeric value and
the comparison `trunc.gt` compares against that original number; update
references in the floor implementation (symbols: floor, trunc, is-finite,
as-i64.as-number) and re-run nan.floor, positive-infinity.floor and
negative-infinity.floor tests on both JVM and eo2js to confirm correct behavior.

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Performance Analysis

All benchmarks are within the acceptable range. No critical degradation detected (threshold is 100%). Please refer to the detailed report for more information.

Click to see the detailed report
Test Base Score PR Score Change % Change Unit Mode
benchmarks.XmirBench.xmirToEO 186.949 170.571 -16.378 -8.76% ms/op Average Time

✅ Performance gain: benchmarks.XmirBench.xmirToEO is faster by 16.378 ms/op (8.76%)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

number.floor wrongly truncates towards zero for negative decimal numbers, contrary to documentation

1 participant