From 57d93972fcafdbfe260d3a06a7c363d96f2841c6 Mon Sep 17 00:00:00 2001
From: Quinton Miller <nicetas.c@gmail.com>
Date: Fri, 3 Nov 2023 07:03:55 +0800
Subject: [PATCH] Add `Number#integer?` (#13936)

---
 spec/std/big/big_decimal_spec.cr  | 12 ++++++++++++
 spec/std/big/big_float_spec.cr    |  6 ++++++
 spec/std/big/big_int_spec.cr      |  5 +++++
 spec/std/big/big_rational_spec.cr |  9 +++++++++
 spec/std/float_spec.cr            | 17 +++++++++++++++++
 spec/std/int_spec.cr              |  7 +++++++
 src/big/big_decimal.cr            |  6 ++++++
 src/big/big_float.cr              |  3 ++-
 src/big/big_rational.cr           |  7 +++++++
 src/int.cr                        |  7 +++++++
 src/number.cr                     | 16 ++++++++++++++++
 11 files changed, 94 insertions(+), 1 deletion(-)

diff --git a/spec/std/big/big_decimal_spec.cr b/spec/std/big/big_decimal_spec.cr
index dd307ee9812f..f6b0834ed2be 100644
--- a/spec/std/big/big_decimal_spec.cr
+++ b/spec/std/big/big_decimal_spec.cr
@@ -969,6 +969,18 @@ describe BigDecimal do
         (-1.to_big_d - BigDecimal.new(50000, 204)).round(200, mode: :ties_even).should eq(-1.to_big_d - BigDecimal.new(5, 200))
       end
     end
+
+    describe "#integer?" do
+      it { BigDecimal.new(0, 0).integer?.should be_true }
+      it { BigDecimal.new(1, 0).integer?.should be_true }
+      it { BigDecimal.new(10, 0).integer?.should be_true }
+      it { BigDecimal.new(-10, 1).integer?.should be_true }
+      it { BigDecimal.new(10000, 4).integer?.should be_true }
+
+      it { BigDecimal.new(1, 1).integer?.should be_false }
+      it { BigDecimal.new(13, 2).integer?.should be_false }
+      it { BigDecimal.new(-2400, 3).integer?.should be_false }
+    end
   end
 
   describe "#inspect" do
diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr
index 5c98beb320ca..679f271c5f3b 100644
--- a/spec/std/big/big_float_spec.cr
+++ b/spec/std/big/big_float_spec.cr
@@ -526,6 +526,12 @@ describe "BigFloat" do
     end
   end
 
+  describe "#integer?" do
+    it { BigFloat.zero.integer?.should be_true }
+    it { 1.to_big_f.integer?.should be_true }
+    it { 1.2.to_big_f.integer?.should be_false }
+  end
+
   it "#hash" do
     b = 123.to_big_f
     b.hash.should eq(b.to_f64.hash)
diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr
index f7eebc53ffb4..07575892bc08 100644
--- a/spec/std/big/big_int_spec.cr
+++ b/spec/std/big/big_int_spec.cr
@@ -9,6 +9,11 @@ private def it_converts_to_s(num, str, *, file = __FILE__, line = __LINE__, **op
 end
 
 describe "BigInt" do
+  describe "#integer?" do
+    it { BigInt.zero.integer?.should be_true }
+    it { 12345.to_big_i.integer?.should be_true }
+  end
+
   it "creates with a value of zero" do
     BigInt.new.to_s.should eq("0")
   end
diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr
index 2c9b138ba357..7b28a4938966 100644
--- a/spec/std/big/big_rational_spec.cr
+++ b/spec/std/big/big_rational_spec.cr
@@ -393,6 +393,15 @@ describe BigRational do
     end
   end
 
+  describe "#integer?" do
+    it { br(0, 1).integer?.should be_true }
+    it { br(1, 1).integer?.should be_true }
+    it { br(9, 3).integer?.should be_true }
+    it { br(-126, 7).integer?.should be_true }
+    it { br(5, 2).integer?.should be_false }
+    it { br(7, -3).integer?.should be_false }
+  end
+
   it "#hash" do
     b = br(10, 3)
     hash = b.hash
diff --git a/spec/std/float_spec.cr b/spec/std/float_spec.cr
index 4ddfd194313d..9a6ebecd3a2f 100644
--- a/spec/std/float_spec.cr
+++ b/spec/std/float_spec.cr
@@ -76,6 +76,23 @@ describe "Float" do
     it { 2.9.ceil.should eq(3) }
   end
 
+  describe "#integer?" do
+    it { 1.0_f32.integer?.should be_true }
+    it { 1.0_f64.integer?.should be_true }
+
+    it { 1.2_f32.integer?.should be_false }
+    it { 1.2_f64.integer?.should be_false }
+
+    it { Float32::MAX.integer?.should be_true }
+    it { Float64::MAX.integer?.should be_true }
+
+    it { Float32::INFINITY.integer?.should be_false }
+    it { Float64::INFINITY.integer?.should be_false }
+
+    it { Float32::NAN.integer?.should be_false }
+    it { Float64::NAN.integer?.should be_false }
+  end
+
   describe "fdiv" do
     it { 1.0.fdiv(1).should eq 1.0 }
     it { 1.0.fdiv(2).should eq 0.5 }
diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr
index 4f377b385206..a4740521a7ec 100644
--- a/spec/std/int_spec.cr
+++ b/spec/std/int_spec.cr
@@ -13,6 +13,13 @@ private macro it_converts_to_s(num, str, **opts)
 end
 
 describe "Int" do
+  describe "#integer?" do
+    {% for int in BUILTIN_INTEGER_TYPES %}
+      it { {{ int }}::MIN.integer?.should be_true }
+      it { {{ int }}::MAX.integer?.should be_true }
+    {% end %}
+  end
+
   describe "**" do
     it "with positive Int32" do
       x = 2 ** 2
diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr
index 376192c5124b..ab42c64bf919 100644
--- a/src/big/big_decimal.cr
+++ b/src/big/big_decimal.cr
@@ -459,6 +459,12 @@ struct BigDecimal < Number
     BigDecimal.new(mantissa, 0)
   end
 
+  # :inherit:
+  def integer? : Bool
+    factor_powers_of_ten
+    scale == 0
+  end
+
   def round(digits : Number, base = 10, *, mode : RoundingMode = :ties_even) : BigDecimal
     return self if zero?
 
diff --git a/src/big/big_float.cr b/src/big/big_float.cr
index 33231b422db3..5464d948931b 100644
--- a/src/big/big_float.cr
+++ b/src/big/big_float.cr
@@ -411,7 +411,8 @@ struct BigFloat < Float
     end
   end
 
-  protected def integer?
+  # :inherit:
+  def integer? : Bool
     !LibGMP.mpf_integer_p(mpf).zero?
   end
 
diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr
index 6b6a6c0bb0b2..4b81db43f46b 100644
--- a/src/big/big_rational.cr
+++ b/src/big/big_rational.cr
@@ -179,6 +179,13 @@ struct BigRational < Number
     x
   end
 
+  # :inherit:
+  def integer? : Bool
+    # since all `BigRational`s are canonicalized, the denominator must be
+    # positive and coprime with the numerator
+    denominator == 1
+  end
+
   # Divides the rational by (2 ** *other*)
   #
   # ```
diff --git a/src/int.cr b/src/int.cr
index 4f10b543fc67..3c78877c74be 100644
--- a/src/int.cr
+++ b/src/int.cr
@@ -274,6 +274,13 @@ struct Int
     self
   end
 
+  # :inherit:
+  #
+  # Always returns `true` for `Int`.
+  def integer? : Bool
+    true
+  end
+
   # Returns the value of raising `self` to the power of *exponent*.
   #
   # Raises `ArgumentError` if *exponent* is negative: if this is needed,
diff --git a/src/number.cr b/src/number.cr
index 9aae19e1eb8a..9ec542f86ef7 100644
--- a/src/number.cr
+++ b/src/number.cr
@@ -343,6 +343,22 @@ struct Number
     end
   end
 
+  # Returns `true` if `self` is an integer.
+  #
+  # Non-integer types may return `true` as long as `self` denotes a finite value
+  # without any fractional parts.
+  #
+  # ```
+  # 1.integer?       # => true
+  # 1.0.integer?     # => true
+  # 1.2.integer?     # => false
+  # (1 / 0).integer? # => false
+  # (0 / 0).integer? # => false
+  # ```
+  def integer? : Bool
+    self % 1 == 0
+  end
+
   # Returns `true` if `self` is equal to zero.
   #
   # ```