Skip to content

Commit

Permalink
Add support for negative *digits* value in BigDecimal#round
Browse files Browse the repository at this point in the history
  • Loading branch information
Sija committed Jul 7, 2020
1 parent b6c6058 commit f761e16
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 7 deletions.
8 changes: 8 additions & 0 deletions spec/std/big/big_decimal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,19 @@ describe BigDecimal do
.should eq(BigDecimal.new(BigInt.new({{sign.id}}5), 1))
BigDecimal.new("{{sign.id}}2.00").round(10)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}20), 1))
BigDecimal.new("{{sign.id}}0.00").round(-10)
.should eq(BigDecimal.new(BigInt.zero))
end

it "rounds :up" do
BigDecimal.new("{{sign.id}}000.5").round(0, :up)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}1)))
BigDecimal.new("{{sign.id}}979797799999666.1").round(0, :up)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}979797799999667)))
BigDecimal.new("{{sign.id}}123456.789").round(-3, :up)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}124000)))
BigDecimal.new("{{sign.id}}987654.321").round(-10, :up)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}0.1e11)))
end

it "rounds :down" do
Expand All @@ -232,6 +238,8 @@ describe BigDecimal do
.should eq(BigDecimal.new(BigInt.new({{sign.id}}979797799999666)))
BigDecimal.new("{{sign.id}}979797799999666.5").round(0, :half_even)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}979797799999666)))
BigDecimal.new("{{sign.id}}12345678.999").round(-2, :half_even)
.should eq(BigDecimal.new(BigInt.new({{sign.id}}12345700)))
end

it "rounds :ceil" do
Expand Down
29 changes: 22 additions & 7 deletions src/big/big_decimal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,18 @@ struct BigDecimal < Number
# Rounds to the nearest integer (by default), returning the result as a `BigDecimal`.
#
# ```
# BigDecimal.new("3.14159").round(2) # => 3.14
# BigDecimal.new("8.7").round # => 9
# BigDecimal.new("-9.9").round # => -10
# BigDecimal.new("3.14159").round(2) # => 3.14
# BigDecimal.new("8.7").round # => 9
# BigDecimal.new("-9.9").round # => -10
# BigDecimal.new("13345.234").round(-2) # => 13300
# ```
#
# If *digits* is specified and positive, the fractional part of the result
# has no more than that many digits.
#
# If *digits* is specified and negative, at least that many digits to the left of
# the decimal point will be `0` in the result.
#
# The value of the optional *mode* argument can be used to determine how
# rounding is performed.
def round(digits : Int = 0, mode : RoundingMode = BigDecimal.rounding_mode) : BigDecimal
Expand All @@ -328,24 +332,35 @@ struct BigDecimal < Number
negative = @value < 0
scale = @scale.to_i - digits

value = n_digits[0...-scale].join.to_big_i
value = self.class.new(value, digits)
if digits < 0 && n_digits.size <= scale
value = self.class.new(n_digits.first, 1)
value *= -1 if negative
value = value.round(0, mode)
value *= 10.to_big_i ** digits.abs
return value
end

value = (n_digits[0...-scale].join.presence || 0).to_big_i
value = self.class.new(value, digits < 0 ? 0 : digits)
value *= -1 if negative

msd = n_digits[-scale]
return value if msd == 0
if msd == 0
return value
end

round_up = case mode
in .up? then true
in .down? then false
in .half_up? then msd >= 5
in .half_down? then msd > 5
in .half_even? then msd == 5 ? n_digits[-2].odd? : msd > 5
in .half_even? then msd == 5 ? (n_digits[-2]? || 0).odd? : msd > 5
in .ceil? then !negative
in .floor? then negative
end

value += (negative ? -1 : 1) if round_up
value *= 10.to_big_i ** digits.abs if digits < 0
value
end

Expand Down

0 comments on commit f761e16

Please sign in to comment.