Skip to content

Skip coerce spec for BigDecimal#div#1273

Merged
tompng merged 1 commit into
ruby:masterfrom
tompng:bigdecimal_div_no_coerce
May 30, 2025
Merged

Skip coerce spec for BigDecimal#div#1273
tompng merged 1 commit into
ruby:masterfrom
tompng:bigdecimal_div_no_coerce

Conversation

@tompng
Copy link
Copy Markdown
Member

@tompng tompng commented May 30, 2025

In bigdecimal < 3.2.0, bigdecimal.div(x, 0) called x.coerce but bigdecimal.div(x, nonzero) doesn't call coerce.
bigdecimal.div(x,0) was implemented by bigdecimal/x, and calling coerce was just a side effect.
It's not a specification.

obj = Object.new
def obj.coerce(*) = [1, 2r]

BigDecimal(1) / obj
#=> (1/2) # This is OK

BigDecimal(1).div(obj, 10)
#=> Object can't be coerced into BigDecimal # This error is expected

BigDecimal(1).div(obj, 0)
#=> (1/2) # (bigdecimal < 3.2.0) This might be a bug. Calling coerce does not make sense

In bigdecimal < 3.2.0, `bigdecimal.div(x, 0)` called `x.coerce` but `bigdecimal.div(x, nonzero)` doesn't call `coerce`.
`bigdecimal.div(x,0)` was implemented by `bigdecimal/x`, and calling coerce was just a side effect. It's not a spec.
@tompng tompng force-pushed the bigdecimal_div_no_coerce branch from 282e024 to 28679fe Compare May 30, 2025 19:07
@tompng tompng merged commit f3a071a into ruby:master May 30, 2025
12 checks passed
@tompng tompng deleted the bigdecimal_div_no_coerce branch May 30, 2025 19:12
@eregon
Copy link
Copy Markdown
Member

eregon commented Jun 2, 2025

Why do you think that case is a bug?
I think it's natural to expect coercion for all arithmetic operations.

The docs of BigDecimal.div say:

If digits is 0, the result is the same as for the / operator or #quo.

So if that holds then BigDecimal(1).div(obj, 0) should be equivalent to BigDecimal(1) / obj, and both should support coerce (or both not).

@eregon
Copy link
Copy Markdown
Member

eregon commented Jun 2, 2025

IOW:

BigDecimal(1).div(2, 0)
=> 0.5e0

So if obj coerces to 2 I think it should return the same result:

irb(main):002> obj = Object.new
=> #<Object:0x00007f8b1aa9d218>
irb(main):003> def obj.coerce(other) = [other, 2]
=> :coerce
irb(main):004> BigDecimal(1).div(obj, 0)
=> 0.5e0

In light of that I think we should revert this change, but maybe I'm missing some context? Are you changing something related in BigDecimal? Is it specifically about a Rational in coerce?

Re Rational I see in Ruby 3.3.5 / bigdecimal 3.1.5:

irb(main):005> BigDecimal(1).div(2r, 0)
=> 0.5e0
irb(main):006> def obj.coerce(other) = [other, 2r]
=> :coerce
irb(main):007> BigDecimal(1).div(obj, 0)
=> 0.5e0

That seems fully expected to me.

@tompng
Copy link
Copy Markdown
Member Author

tompng commented Jun 3, 2025

I expect BigDecimal(1).div(2i, any_number) to return BigDecimal or fail to calculate.
But BigDecimal(1).div(2i, 0) == (0.0-0.5e0i) in v3.2.0.

Considering BigDecimal#div(value, digits) is a method intended to calculate in bigdecimal, the call-seq part of the document makes sense to me. Expected return type of bigdecimal.div(value, 0) and bigdecimal / value are different.

* Document-method: BigDecimal#div
*
* call-seq:
*   div(value)  -> integer
*   div(value, digits)  -> bigdecimal or integer

(note: div(value, nil) returns integer)

I think document of digits==0 is just an implementation detail.

* If digits is 0, the result is the same as for the / operator
* or #quo.

I guess what it wanted to say is:

* If digits is 0, precision is automatically determined just like
* / operator or #quo.

Of cource BigDecimal#div coerces the value into BigDecimal but without calling coerce method for now.
Calling value.coerce only when digits=0 shouldn't be tested as a specification.

For future improvement, we can change div(obj, 0) and div(obj, 10) to both call obj.coerce and raise an error if it's not coerced to [BigDecimal, BigDecimal]. But currently it is not what BigDecimal#div ensures.

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