Skip to content

Commit

Permalink
Add BigDecimal#round(digits, mode) overload
Browse files Browse the repository at this point in the history
  • Loading branch information
Sija committed Nov 29, 2018
1 parent dd26748 commit 589add7
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
33 changes: 33 additions & 0 deletions spec/std/big/big_decimal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,39 @@ describe BigDecimal do
(-BigDecimal.new(3)).should eq(BigDecimal.new(-3))
end

describe "#round" do
{% for sign in %w[+ -] %}
context "(with {{sign.id}} sign)" do
it "rounds :up" do
BigDecimal.new("{{sign.id}}2.1").round(0, :up)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}3)))
end

it "rounds :down" do
BigDecimal.new("{{sign.id}}2.9").round(0, :down)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}2)))
end

it "rounds :half_up" do
BigDecimal.new("{{sign.id}}2.5").round(0, :half_up)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}3)))
end

it "rounds :half_down" do
BigDecimal.new("{{sign.id}}2.5").round(0, :half_down)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}2)))
end

it "rounds :half_even" do
BigDecimal.new("{{sign.id}}1.5").round(0, :half_even)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}2)))
BigDecimal.new("{{sign.id}}2.5").round(0, :half_even)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}2)))
end
end
{% end %}
end

it "performs arithmetic with other number types" do
(1.to_big_d + 2).should eq(BigDecimal.new("3.0"))
(2 + 1.to_big_d).should eq(BigDecimal.new("3.0"))
Expand Down
43 changes: 43 additions & 0 deletions src/big/big_decimal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,49 @@ struct BigDecimal < Number
end
end

def round(digits = 0, mode = :half_up) : BigDecimal
return self.class.new(value, @scale) if @scale <= digits

n_scale = @scale - digits
n_digits = value.digits

# Workaround for "-3" in digits array
if n_digits.first?.try &.< 0
negative = true
n_digits.shift
end

msd = 0
n_scale.times do
msd = n_digits.pop? || 0
end
n_digits << 0 if n_digits.empty?
str_value = String.build do |str|
n_digits.each &.to_s(str)
end

n_value = BigInt.new(str_value)
return self.class.new(n_value, digits) if msd.zero?

round_up = case mode
when :up then true
when :down then false
when :half_up then msd >= 5
when :half_down then msd > 5
when :half_even then msd == 5 ? !n_digits.last.even? : msd > 5
when :ceil then !negative
when :floor then negative
else
raise ArgumentError.new("Unsupported mode: #{mode.inspect}")
end

bd = n_value
bd += 1 if round_up
bd = self.class.new(bd, digits)
bd *= -1 if negative
bd
end

def <=>(other : Int | Float | BigRational)
self <=> BigDecimal.new(other)
end
Expand Down

0 comments on commit 589add7

Please sign in to comment.