Skip to content

Rewrite BigDecimal#sqrt in ruby with improved Newton's method #381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

tompng
Copy link
Member

@tompng tompng commented Jul 18, 2025

Implement sqrt in ruby with faster Newton's method implementation.

Related to #323 (doing the same rewrite with Integer.sqrt)

Example calculating BigDecimal(2).sqrt in 116 precision

x = BigDecimal(2)
y = BigDecimal(Math.sqrt(x.to_f))
y = (y+x.div(y, 18)).div(2, 18)
y = (y+x.div(y, 32)).div(2, 32)
y = (y+x.div(y, 60)).div(2, 60)
y = (y+x.div(y, 116)).div(2, 116)

Minimum loop count, minimum precision in each step.
The original c implementation was slow for large precision because it was not doing this.

Small precision: gets slower
Large precision: faster

# SQRT2
two = BigDecimal(2);

10000.times { two.sqrt(10) }
# processing time: 0.007694s → 0.073325s

10000.times { two.sqrt(32) }
# processing time: 0.020750s → 0.091189s

10000.times { two.sqrt(100) }
# processing time: 0.049758s → 0.113781s

10000.times { two.sqrt(1000) }
# processing time: 2.865216s → 0.777328s

two.sqrt(10000)
# processing time: 0.995833s → 0.017588s

# Fast path
BigDecimal(121).sqrt(10000)
# processing time: 0.000140s → 0.000154s

# Many digits
BigDecimal(2).div(7, 10000).sqrt(10000)
# processing time: 0.980485s → 0.014949s
# note: using Integer.sqrt is faster(0.002656s) in this case

Copy link
Member

@mrkn mrkn left a comment

Choose a reason for hiding this comment

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

@tompng I like this approach. Please check a comment I left.

Comment on lines 14 to 139
if infinite? == 1
exception_mode = BigDecimal.mode(BigDecimal::EXCEPTION_ALL)
raise FloatDomainError, "Computation results in 'Infinity'" if exception_mode.anybits?(BigDecimal::EXCEPTION_INFINITY)
Copy link
Member

Choose a reason for hiding this comment

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

@tompng, how do you think about extracting the infinity check and exception-raising part as a method?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is sqrt-specific condition. For example, Math.atan(infinity)=π/2, `"Computation results in 'Infinity'" is not a correct message.

We can re-consider adding a common infinity check method later, for unbounded monotonic increasing function and other types, but I think it's too early for now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry I misread the comment. Changed to:

return BigMath._infinity_computation_result if infinite? == 1

This method is added in #389 and used in BigDecimal#power, BigMath.exp and BigMath.log.

prec = [prec, n_digits].max

ex = exponent / 2
x = self * BigDecimal("1e#{-ex * 2}")
Copy link
Member

Choose a reason for hiding this comment

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

@tompng, I think this square root calculation can be faster if we use decimal shift operations here and on line 37, as we previously discussed. What about this?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's good to use decimal shift operator. I've updated #324 as ready for review.

@tompng tompng force-pushed the sqrt_in_bigdecimal_ruby branch from f045650 to db8554d Compare July 19, 2025 08:51
@tompng tompng force-pushed the sqrt_in_bigdecimal_ruby branch from db8554d to 27d554a Compare July 28, 2025 13:01
@tompng tompng force-pushed the sqrt_in_bigdecimal_ruby branch from 27d554a to a8976b6 Compare August 14, 2025 13:34
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