Skip to content

fix: floor/ceil/round_ used as iteratees silently pass index as precision - #248

Merged
dgilland merged 2 commits into
dgilland:developfrom
gaoflow:fix-floor-ceil-round-iteratee-argcount
Jul 6, 2026
Merged

fix: floor/ceil/round_ used as iteratees silently pass index as precision#248
dgilland merged 2 commits into
dgilland:developfrom
gaoflow:fix-floor-ceil-round-iteratee-argcount

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Bug

pydash.floor, pydash.ceil, and pydash.round_ each accept an optional
precision argument. helpers.getargcount() counts all positional
parameters and therefore returns 2 for these functions. When callit()
invokes them inside iteriteratee() it passes (element, index, collection)
truncated to argcount=2, so the collection index is forwarded as precision.
This produces silently wrong results whenever these functions are used directly
as iteratees.

Reproducer

import pydash as _

# Reported expected: {6.0: 2, 4.0: 1}
_.count_by([6.1, 4.2, 6.3], _.floor)
# Actual:   {6.0: 1, 4.2: 1, 6.3: 1}
#
# floor(6.1, 0) == 6.0  ✓  (index 0 → precision 0, happens to be correct)
# floor(4.2, 1) == 4.2  ✗  (index 1 → precision 1, rounds to 1 decimal)
# floor(6.3, 2) == 6.3  ✗  (index 2 → precision 2, rounds to 2 decimals)

The same bug affects _.ceil and _.round_. Equivalent lambdas work fine
because lambda x: _.floor(x) only has one positional parameter.

Fix

Set _argcount = 1 on floor, ceil, and round_ after their definitions.
This is the existing optimization mechanism already used by iteratees returned
from utilities.iteratee() and the wrappers in functions.py; it tells
callit() to supply only the element value.

Normal two-argument use (floor(3.75, 1)) is completely unaffected.

Tests

Three new parametrize cases added to test_count_by — one per affected
function — covering the previously broken behavior.

gaoflow and others added 2 commits June 24, 2026 16:21
pydash.floor, pydash.ceil, and pydash.round_ each accept an optional
`precision` argument.  helpers.getargcount() counts all positional
parameters, so it returns 2 for these functions.  When callit() then
invokes them with (element, index, collection) it passes the collection
index as `precision`, producing wrong grouping keys.

For example:
    count_by([6.1, 4.2, 6.3], floor)
returned {6.0: 1, 4.2: 1, 6.3: 1} instead of {6.0: 2, 4.0: 1}
because floor(4.2, 1) == 4.2 and floor(6.3, 2) == 6.3.

Setting _argcount = 1 on all three functions signals callit() to supply
only the element value, matching the intent of using them as iteratees.
@dgilland
dgilland merged commit 68d3047 into dgilland:develop Jul 6, 2026
10 checks passed
@dgilland

dgilland commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Thanks for the contribution, much appreciated! 👍

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.

2 participants