Skip to content

Commit 67495c3

Browse files
committed
Implement BigMath.cbrt
Implement BigMath version of Math.cbrt(x). Added internal-use method BigMath.int_cbrt(n): cube root version of Integer.sqrt(n)
1 parent 809d064 commit 67495c3

File tree

2 files changed

+76
-0
lines changed

2 files changed

+76
-0
lines changed

lib/bigdecimal/math.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#--
66
# Contents:
77
# sqrt(x, prec)
8+
# cbrt(x, prec)
89
# hypot(x, y, prec)
910
# sin (x, prec)
1011
# cos (x, prec)
@@ -46,6 +47,61 @@ def sqrt(x, prec)
4647
x.sqrt(prec)
4748
end
4849

50+
# call-seq:
51+
# cbrt(decimal, numeric) -> BigDecimal
52+
#
53+
# Computes the cube root of +decimal+ to the specified number of digits of
54+
# precision, +numeric+.
55+
#
56+
# BigMath.cbrt(BigDecimal('2'), 16).to_s
57+
# #=> "0.125992104989487316476721060727822e1"
58+
#
59+
def cbrt(x, prec)
60+
raise ArgumentError, "Zero or negative precision for cbrt" if prec <= 0
61+
return x if x.zero? || x.infinite? || x.nan?
62+
return -cbrt(-x, prec) if x < 0
63+
64+
n_digits = x.n_significant_digits
65+
prec = [prec, n_digits].max
66+
67+
if n_digits < prec / 2
68+
# Fast path for cbrt(8e150) => 2e50
69+
ex = (n_digits - x.exponent + 2) / 3
70+
n = (x * BigDecimal("1e#{3 * ex}")).to_i
71+
cbrt = _int_cbrt(n)
72+
return BigDecimal(cbrt) * BigDecimal("1e#{-ex}") if cbrt**3 == n
73+
end
74+
75+
ex = prec + BigDecimal.double_fig - x.exponent / 3
76+
cbrt = _int_cbrt(x * BigDecimal("1e#{3 * ex}"))
77+
BigDecimal(cbrt) * BigDecimal("1e#{-ex}")
78+
end
79+
80+
# Private method used internally by `cbrt`.
81+
# Cube root version of `Intger.sqrt(n)`
82+
# Returns the largest integer whose cube is less than or equal to n if n is positive.
83+
private_class_method def _int_cbrt(n)
84+
n = n.to_i
85+
return -_int_cbrt(-n) if n < 0
86+
87+
if n <= 0xffffffff
88+
v = Math.cbrt(n).floor
89+
else
90+
shift = (n.bit_length - 1) / 6
91+
v = _int_cbrt(n >> (3 * shift)) << shift
92+
end
93+
94+
v = (2 * v + n / v / v) / 3
95+
v2 = v * v
96+
v3 = v2 * v
97+
while v3 > n
98+
v3 -= 3 * v2 - 3 * v + 1
99+
v2 -= 2 * v - 1
100+
v -= 1
101+
end
102+
v
103+
end
104+
49105
# call-seq:
50106
# hypot(x, y, numeric) -> BigDecimal
51107
#

test/bigdecimal/test_bigmath.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ def test_sqrt
3434
assert_relative_precision {|n| sqrt(BigDecimal("2e50"), n) }
3535
end
3636

37+
def test_cbrt
38+
assert_equal(1234, cbrt(BigDecimal(1234**3), N))
39+
assert_equal(-12345, cbrt(BigDecimal(-12345**3), N))
40+
assert_equal(123 ** 456, cbrt(BigDecimal(123 ** 456) ** 3, N))
41+
assert_equal(0, cbrt(BigDecimal("0"), N))
42+
assert_equal(0, cbrt(BigDecimal("-0"), N))
43+
assert_equal(PINF, cbrt(PINF, N))
44+
assert_equal(MINF, cbrt(MINF, N))
45+
46+
assert_in_delta(SQRT2, cbrt(SQRT2 ** 3, 100), BigDecimal("1e-100"))
47+
assert_in_delta(SQRT3, cbrt(SQRT3 ** 3, 100), BigDecimal("1e-100"))
48+
assert_equal(BigDecimal("3e50"), cbrt(BigDecimal("27e150"), N))
49+
assert_equal(BigDecimal("-4e50"), cbrt(BigDecimal("-64e150"), N))
50+
assert_in_epsilon(Math.cbrt(28e150), cbrt(BigDecimal("28e150"), N))
51+
assert_in_epsilon(Math.cbrt(27e151), cbrt(BigDecimal("27e151"), N))
52+
assert_relative_precision {|n| cbrt(BigDecimal("2"), n) }
53+
assert_relative_precision {|n| cbrt(BigDecimal("2e-50"), n) }
54+
assert_relative_precision {|n| cbrt(BigDecimal("2e50"), n) }
55+
end
56+
3757
def test_hypot
3858
assert_in_delta(SQRT2, hypot(BigDecimal("1"), BigDecimal("1"), 100), BigDecimal("1e-100"))
3959
assert_in_delta(SQRT5, hypot(SQRT2, SQRT3, 100), BigDecimal("1e-100"))

0 commit comments

Comments
 (0)