Skip to content

Commit

Permalink
Implement floating-point manipulation functions for BigFloat (#11007)
Browse files Browse the repository at this point in the history
`BigFloat` already has an implementation of `frexp` because `hash` needs it (I think); this PR adds the remaining floating-point manipulation operations.

Methods that take an additional integer accept an `Int` directly, so this takes care of the allowed conversions in #10907.

`BigFloat` from GMP doesn't have the notion of signed zeros, so `copysign` is only an approximation. (As usual, MPFR floats feature signed zeros.)
  • Loading branch information
HertzDevil authored Sep 10, 2024
1 parent ce76bf5 commit fdbaeb4
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 0 deletions.
54 changes: 54 additions & 0 deletions spec/std/big/big_float_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,48 @@ describe "BigFloat" do
end

describe "BigFloat Math" do
it ".ilogb" do
Math.ilogb(0.2.to_big_f).should eq(-3)
Math.ilogb(123.45.to_big_f).should eq(6)
Math.ilogb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000)
Math.ilogb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000)
Math.ilogb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000)
expect_raises(ArgumentError) { Math.ilogb(0.to_big_f) }
end

it ".logb" do
Math.logb(0.2.to_big_f).should eq(-3.to_big_f)
Math.logb(123.45.to_big_f).should eq(6.to_big_f)
Math.logb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000.to_big_f)
Math.logb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000.to_big_f)
Math.logb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000.to_big_f)
expect_raises(ArgumentError) { Math.logb(0.to_big_f) }
end

it ".ldexp" do
Math.ldexp(0.2.to_big_f, 2).should eq(0.8.to_big_f)
Math.ldexp(0.2.to_big_f, -2).should eq(0.05.to_big_f)
Math.ldexp(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000)
Math.ldexp(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000)
Math.ldexp(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000)
end

it ".scalbn" do
Math.scalbn(0.2.to_big_f, 2).should eq(0.8.to_big_f)
Math.scalbn(0.2.to_big_f, -2).should eq(0.05.to_big_f)
Math.scalbn(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000)
Math.scalbn(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000)
Math.scalbn(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000)
end

it ".scalbln" do
Math.scalbln(0.2.to_big_f, 2).should eq(0.8.to_big_f)
Math.scalbln(0.2.to_big_f, -2).should eq(0.05.to_big_f)
Math.scalbln(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000)
Math.scalbln(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000)
Math.scalbln(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000)
end

it ".frexp" do
Math.frexp(0.to_big_f).should eq({0.0, 0})
Math.frexp(1.to_big_f).should eq({0.5, 1})
Expand All @@ -574,6 +616,18 @@ describe "BigFloat Math" do
Math.frexp(-(2.to_big_f ** -0x100000000)).should eq({-0.5, -0xFFFFFFFF})
end

it ".copysign" do
Math.copysign(3.to_big_f, 2.to_big_f).should eq(3.to_big_f)
Math.copysign(3.to_big_f, 0.to_big_f).should eq(3.to_big_f)
Math.copysign(3.to_big_f, -2.to_big_f).should eq(-3.to_big_f)
Math.copysign(0.to_big_f, 2.to_big_f).should eq(0.to_big_f)
Math.copysign(0.to_big_f, 0.to_big_f).should eq(0.to_big_f)
Math.copysign(0.to_big_f, -2.to_big_f).should eq(0.to_big_f)
Math.copysign(-3.to_big_f, 2.to_big_f).should eq(3.to_big_f)
Math.copysign(-3.to_big_f, 0.to_big_f).should eq(3.to_big_f)
Math.copysign(-3.to_big_f, -2.to_big_f).should eq(-3.to_big_f)
end

it ".sqrt" do
Math.sqrt(BigFloat.new("1" + "0"*48)).should eq(BigFloat.new("1" + "0"*24))
end
Expand Down
52 changes: 52 additions & 0 deletions src/big/big_float.cr
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,47 @@ class String
end

module Math
# Returns the unbiased base 2 exponent of the given floating-point *value*.
#
# Raises `ArgumentError` if *value* is zero.
def ilogb(value : BigFloat) : Int64
raise ArgumentError.new "Cannot get exponent of zero" if value.zero?
leading_zeros = value.@mpf._mp_d[value.@mpf._mp_size.abs - 1].leading_zeros_count
8_i64 * sizeof(LibGMP::MpLimb) * value.@mpf._mp_exp - leading_zeros - 1
end

# Returns the unbiased radix-independent exponent of the given floating-point *value*.
#
# For `BigFloat` this is equivalent to `ilogb`.
#
# Raises `ArgumentError` is *value* is zero.
def logb(value : BigFloat) : BigFloat
ilogb(value).to_big_f
end

# Multiplies the given floating-point *value* by 2 raised to the power *exp*.
def ldexp(value : BigFloat, exp : Int) : BigFloat
BigFloat.new do |mpf|
if exp >= 0
LibGMP.mpf_mul_2exp(mpf, value, exp.to_u64)
else
LibGMP.mpf_div_2exp(mpf, value, exp.abs.to_u64)
end
end
end

# Returns the floating-point *value* with its exponent raised by *exp*.
#
# For `BigFloat` this is equivalent to `ldexp`.
def scalbn(value : BigFloat, exp : Int) : BigFloat
ldexp(value, exp)
end

# :ditto:
def scalbln(value : BigFloat, exp : Int) : BigFloat
ldexp(value, exp)
end

# Decomposes the given floating-point *value* into a normalized fraction and an integral power of two.
def frexp(value : BigFloat) : {BigFloat, Int64}
return {BigFloat.zero, 0_i64} if value.zero?
Expand All @@ -608,6 +649,17 @@ module Math
{frac, exp}
end

# Returns the floating-point value with the magnitude of *value1* and the sign of *value2*.
#
# `BigFloat` does not support signed zeros; if `value2 == 0`, the returned value is non-negative.
def copysign(value1 : BigFloat, value2 : BigFloat) : BigFloat
if value1.negative? != value2.negative? # opposite signs
-value1
else
value1
end
end

# Calculates the square root of *value*.
#
# ```
Expand Down

0 comments on commit fdbaeb4

Please sign in to comment.