Skip to content

Commit

Permalink
Implement rounding mode for Number#round (#10413)
Browse files Browse the repository at this point in the history
  • Loading branch information
straight-shoota authored Feb 28, 2021
1 parent eef60c4 commit b018f28
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 16 deletions.
220 changes: 215 additions & 5 deletions spec/std/number_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,32 @@ describe "Number" do
15.151.round.should eq(15)
end

it "infinity" do
Float64::INFINITY.round.infinite?.should eq(1)
Float32::INFINITY.round.infinite?.should eq(1)
(-Float64::INFINITY).round.infinite?.should eq(-1)
(-Float32::INFINITY).round.infinite?.should eq(-1)
it "infinity Float64" do
Float64::INFINITY.round.should eq Float64::INFINITY
Float64::INFINITY.round(digits: 0).should eq Float64::INFINITY
Float64::INFINITY.round(digits: 3).should eq Float64::INFINITY
Float64::INFINITY.round(digits: -3).should eq Float64::INFINITY
(-Float64::INFINITY).round.should eq -Float64::INFINITY
(-Float64::INFINITY).round(digits: 0).should eq -Float64::INFINITY
(-Float64::INFINITY).round(digits: 3).should eq -Float64::INFINITY
(-Float64::INFINITY).round(digits: -3).should eq -Float64::INFINITY
end

{% if compare_versions(Crystal::VERSION, "0.36.1") > 0 %}
it "infinity Float32" do
Float32::INFINITY.round.should eq Float32::INFINITY
Float32::INFINITY.round(digits: 0).should eq Float32::INFINITY
Float32::INFINITY.round(digits: 3).should eq Float32::INFINITY
Float32::INFINITY.round(digits: -3).should eq Float32::INFINITY
(-Float32::INFINITY).round.should eq -Float32::INFINITY
(-Float32::INFINITY).round(digits: 0).should eq -Float32::INFINITY
(-Float32::INFINITY).round(digits: 3).should eq -Float32::INFINITY
(-Float32::INFINITY).round(digits: -3).should eq -Float32::INFINITY
end
{% else %}
pending "infinity Float32"
{% end %}

it "nan" do
Float64::NAN.round.nan?.should be_true
Float32::NAN.round.nan?.should be_true
Expand Down Expand Up @@ -116,6 +135,155 @@ describe "Number" do
6543210987654321.0.round(-15).should eq(7000000000000000.0)
end

describe "rounding modes" do
it "to_zero" do
-1.5.round(:to_zero).should eq -1.0
-1.0.round(:to_zero).should eq -1.0
-0.9.round(:to_zero).should eq 0.0
-0.5.round(:to_zero).should eq 0.0
-0.1.round(:to_zero).should eq 0.0
0.0.round(:to_zero).should eq 0.0
0.1.round(:to_zero).should eq 0.0
0.5.round(:to_zero).should eq 0.0
0.9.round(:to_zero).should eq 0.0
1.0.round(:to_zero).should eq 1.0
1.5.round(:to_zero).should eq 1.0
end

it "to_positive" do
-1.5.round(:to_positive).should eq -1.0
-1.0.round(:to_positive).should eq -1.0
-0.9.round(:to_positive).should eq 0.0
-0.5.round(:to_positive).should eq 0.0
-0.1.round(:to_positive).should eq 0.0
0.0.round(:to_positive).should eq 0.0
0.1.round(:to_positive).should eq 1.0
0.5.round(:to_positive).should eq 1.0
0.9.round(:to_positive).should eq 1.0
1.0.round(:to_positive).should eq 1.0
1.5.round(:to_positive).should eq 2.0
end

it "to_negative" do
-1.5.round(:to_negative).should eq -2.0
-1.0.round(:to_negative).should eq -1.0
-0.9.round(:to_negative).should eq -1.0
-0.5.round(:to_negative).should eq -1.0
-0.1.round(:to_negative).should eq -1.0
0.0.round(:to_negative).should eq 0.0
0.1.round(:to_negative).should eq 0.0
0.5.round(:to_negative).should eq 0.0
0.9.round(:to_negative).should eq 0.0
1.0.round(:to_negative).should eq 1.0
1.5.round(:to_negative).should eq 1.0
end

it "ties_even" do
-2.5.round(:ties_even).should eq -2.0
-1.5.round(:ties_even).should eq -2.0
-1.0.round(:ties_even).should eq -1.0
-0.9.round(:ties_even).should eq -1.0
-0.5.round(:ties_even).should eq 0.0
-0.1.round(:ties_even).should eq 0.0
0.0.round(:ties_even).should eq 0.0
0.1.round(:ties_even).should eq 0.0
0.5.round(:ties_even).should eq 0.0
0.9.round(:ties_even).should eq 1.0
1.0.round(:ties_even).should eq 1.0
1.5.round(:ties_even).should eq 2.0
2.5.round(:ties_even).should eq 2.0
end

it "ties_away" do
-2.5.round(:ties_away).should eq -3.0
-1.5.round(:ties_away).should eq -2.0
-1.0.round(:ties_away).should eq -1.0
-0.9.round(:ties_away).should eq -1.0
-0.5.round(:ties_away).should eq -1.0
-0.1.round(:ties_away).should eq 0.0
0.0.round(:ties_away).should eq 0.0
0.1.round(:ties_away).should eq 0.0
0.5.round(:ties_away).should eq 1.0
0.9.round(:ties_away).should eq 1.0
1.0.round(:ties_away).should eq 1.0
1.5.round(:ties_away).should eq 2.0
2.5.round(:ties_away).should eq 3.0
end

it "default (=ties_away)" do
-2.5.round.should eq -3.0
-1.5.round.should eq -2.0
-1.0.round.should eq -1.0
-0.9.round.should eq -1.0
-0.5.round.should eq -1.0
-0.1.round.should eq 0.0
0.0.round.should eq 0.0
0.1.round.should eq 0.0
0.5.round.should eq 1.0
0.9.round.should eq 1.0
1.0.round.should eq 1.0
1.5.round.should eq 2.0
2.5.round.should eq 3.0
end
end

describe "with digits" do
it "to_zero" do
12.345.round(-1, mode: :to_zero).should eq 10
12.345.round(0, mode: :to_zero).should eq 12
12.345.round(1, mode: :to_zero).should eq 12.3
12.345.round(2, mode: :to_zero).should eq 12.34
-12.345.round(-1, mode: :to_zero).should eq -10
-12.345.round(0, mode: :to_zero).should eq -12
-12.345.round(1, mode: :to_zero).should eq -12.3
-12.345.round(2, mode: :to_zero).should eq -12.34
end

it "to_positive" do
12.345.round(-1, mode: :to_positive).should eq 20
12.345.round(0, mode: :to_positive).should eq 13
12.345.round(1, mode: :to_positive).should eq 12.4
12.345.round(2, mode: :to_positive).should eq 12.35
-12.345.round(-1, mode: :to_positive).should eq -10
-12.345.round(0, mode: :to_positive).should eq -12
-12.345.round(1, mode: :to_positive).should eq -12.3
-12.345.round(2, mode: :to_positive).should eq -12.34
end

it "to_negative" do
12.345.round(-1, mode: :to_negative).should eq 10
12.345.round(0, mode: :to_negative).should eq 12
12.345.round(1, mode: :to_negative).should eq 12.3
12.345.round(2, mode: :to_negative).should eq 12.34
-12.345.round(-1, mode: :to_negative).should eq -20
-12.345.round(0, mode: :to_negative).should eq -13
-12.345.round(1, mode: :to_negative).should eq -12.4
-12.345.round(2, mode: :to_negative).should eq -12.35
end

it "ties_away" do
13.825.round(-1, mode: :ties_away).should eq 10
13.825.round(0, mode: :ties_away).should eq 14
13.825.round(1, mode: :ties_away).should eq 13.8
13.825.round(2, mode: :ties_away).should eq 13.83
-13.825.round(-1, mode: :ties_away).should eq -10
-13.825.round(0, mode: :ties_away).should eq -14
-13.825.round(1, mode: :ties_away).should eq -13.8
-13.825.round(2, mode: :ties_away).should eq -13.83
end

it "ties_even" do
15.255.round(-1, mode: :ties_even).should eq 20
15.255.round(0, mode: :ties_even).should eq 15
15.255.round(1, mode: :ties_even).should eq 15.3
15.255.round(2, mode: :ties_even).should eq 15.26
-15.255.round(-1, mode: :ties_even).should eq -20
-15.255.round(0, mode: :ties_even).should eq -15
-15.255.round(1, mode: :ties_even).should eq -15.3
-15.255.round(2, mode: :ties_even).should eq -15.26
end
end

describe "base" do
it "2" do
-1763.116.round(2, base: 2).should eq(-1763.0)
Expand All @@ -132,6 +300,48 @@ describe "Number" do
end
end

describe "#round_even" do
-2.5.round_even.should eq -2.0
-1.5.round_even.should eq -2.0
-1.0.round_even.should eq -1.0
-0.9.round_even.should eq -1.0
-0.5.round_even.should eq -0.0
-0.1.round_even.should eq 0.0
0.0.round_even.should eq 0.0
0.1.round_even.should eq 0.0
0.5.round_even.should eq 0.0
0.9.round_even.should eq 1.0
1.0.round_even.should eq 1.0
1.5.round_even.should eq 2.0
2.5.round_even.should eq 2.0

1.round_even.should eq 1
1.round_even.should be_a(Int32)
1_u8.round_even.should be_a(UInt8)
1_f32.round_even.should be_a(Float32)
end

describe "#round_away" do
-2.5.round_away.should eq -3.0
-1.5.round_away.should eq -2.0
-1.0.round_away.should eq -1.0
-0.9.round_away.should eq -1.0
-0.5.round_away.should eq -1.0
-0.1.round_away.should eq 0.0
0.0.round_away.should eq 0.0
0.1.round_away.should eq 0.0
0.5.round_away.should eq 1.0
0.9.round_away.should eq 1.0
1.0.round_away.should eq 1.0
1.5.round_away.should eq 2.0
2.5.round_away.should eq 3.0

1.round_away.should eq 1
1.round_away.should be_a(Int32)
1_u8.round_away.should be_a(UInt8)
1_f32.round_away.should be_a(Float32)
end

it "gives the absolute value" do
123.abs.should eq(123)
-123.abs.should eq(123)
Expand Down
32 changes: 30 additions & 2 deletions src/float.cr
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,21 @@ struct Float32
LibM.floor_f32(self)
end

def round
# Rounds towards the nearest integer. If both neighboring integers are equidistant,
# rounds towards the even neighbor (Banker's rounding).
def round_even : self
# LLVM 11 introduced llvm.roundeven.* intrinsics which may replace rint in
# the future.
{% if compare_versions(Crystal::LLVM_VERSION, "11.0.0") >= 0 %}
LibM.roundeven_f32(self)
{% else %}
LibM.rint_f32(self)
{% end %}
end

# Rounds towards the nearest integer. If both neighboring integers are equidistant,
# rounds away from zero.
def round_away
LibM.round_f32(self)
end

Expand Down Expand Up @@ -238,7 +252,21 @@ struct Float64
LibM.floor_f64(self)
end

def round
# Rounds towards the nearest integer. If both neighboring integers are equidistant,
# rounds towards the even neighbor (Banker's rounding).
def round_even : self
# LLVM 11 introduced llvm.roundeven.* intrinsics which may replace rint in
# the future.
{% if compare_versions(Crystal::LLVM_VERSION, "11.0.0") >= 0 %}
LibM.roundeven_f64(self)
{% else %}
LibM.rint_f64(self)
{% end %}
end

# Rounds towards the nearest integer. If both neighboring integers are equidistant,
# rounds away from zero.
def round_away
LibM.round_f64(self)
end

Expand Down
14 changes: 12 additions & 2 deletions src/int.cr
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ struct Int
self >= 0 ? self : -self
end

def round(mode : RoundingMode) : self
self
end

def ceil
self
end
Expand All @@ -249,11 +253,17 @@ struct Int
self
end

def round
def trunc
self
end

# Returns `self`.
def round_even : self
self
end

def trunc
# Returns `self`.
def round_away
self
end

Expand Down
6 changes: 6 additions & 0 deletions src/math/libm.cr
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ lib LibM
{% end %}
fun round_f32 = "llvm.round.f32"(value : Float32) : Float32
fun round_f64 = "llvm.round.f64"(value : Float64) : Float64
{% if compare_versions(Crystal::LLVM_VERSION, "11.0.0") >= 0 %}
fun roundeven_f32 = "llvm.roundeven.f32"(value : Float32) : Float32
fun roundeven_f64 = "llvm.roundeven.f64"(value : Float64) : Float64
{% end %}
fun rint_f32 = "llvm.rint.f32"(value : Float32) : Float32
fun rint_f64 = "llvm.rint.f64"(value : Float64) : Float64
fun sin_f32 = "llvm.sin.f32"(value : Float32) : Float32
fun sin_f64 = "llvm.sin.f64"(value : Float64) : Float64
fun sqrt_f32 = "llvm.sqrt.f32"(value : Float32) : Float32
Expand Down
Loading

0 comments on commit b018f28

Please sign in to comment.