Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Abhay Kumar
Adrian Longley
Alex Speller
Alexander Donkin
Alexander Ross
Alex Speller
Andreas Loupasakis
Andrei
Andrew White
Expand Down Expand Up @@ -49,8 +49,8 @@ Greg Byrne
Hakan Ensari
Hongli Lai
Ilia Lobsanov
Ivan Shamatov
Ingo Wichmann
Ivan Shamatov
Jack Spiva
Jacob Atzen
James Cotterill
Expand Down Expand Up @@ -83,8 +83,8 @@ Marco Otte-Witte
Mateus Gomes
Mateusz Wolsza
Matias Korhonen
Matthew McEachen
Matt Jankowski
Matthew McEachen
Max Melentiev
Michael Irwin
Michael J. Cohen
Expand All @@ -96,14 +96,15 @@ Mike Połétyn
Musannif Zahir
Neil Middleton
Nick Lozon
Nicolay Hvidsten
Nihad Abbasov
Olek Janiszewski
Orien Madgwick
Paweł Madejski
Paul McMahon
Paulo Diniz
Pavan Sudarshan
Pavel Gabriel
Paweł Madejski
pconnor
Pedro Nascimento
Pelle Braendgaard
Expand All @@ -119,9 +120,11 @@ sankaranarayanan
Scott Pierce
Semyon Perepelitsa
Shane Emmons
Simon Neutert
Simone Carletti
Spencer Rinehart
Steve Morris
Sunny Ripert
Thomas E Enebo
Thomas Weymuth
Ticean Bennett
Expand All @@ -142,5 +145,3 @@ Yuri Sidorov
Yuusuke Takizawa
Zubin Henner
Бродяной Александр
Nicolay Hvidsten
Simon Neutert
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
- Add Zimbabwe Gold (ZWG) currency
- Update thousands_separator for CHF
- Add Caribbean Guilder (XCG) as replacement for Netherlands Antillean Gulden (ANG)
- Add `Currency#cents_based?` to check if currency is cents-based
- Add `Money#to_nearest_cash_value` to return a rounded Money instance to the smallest denomination
- Deprecate `Money#round_to_nearest_cash_value` in favor of calling `to_nearest_cash_value.fractional`
- Add `Money::Currency#cents_based?` to check if currency is cents-based
- Add ability to nest `Money.with_rounding_mode` blocks
- Allow `nil` to be used as a default_currency

Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
3. Make sure everything is working: `bundle exec rake spec`
4. Make your changes
5. Test your changes
5. Create a Pull Request
6. Celebrate!!!!!
6. Create a Pull Request
7. Celebrate! 🎉

## Notes

Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ Money.from_amount(2.34567).format #=> "$2.34567"

To round to the nearest cent (or anything more precise), you can use the `round` method. However, note that the `round` method on a `Money` object does not work the same way as a normal Ruby `Float` object. Money's `round` method accepts different arguments. The first argument to the round method is the rounding mode, while the second argument is the level of precision relative to the cent.

```
```ruby
# Float
2.34567.round #=> 2
2.34567.round(2) #=> 2.35
Expand All @@ -484,10 +484,18 @@ Money.from_cents(2.34567).round(BigDecimal::ROUND_DOWN, 2).format #=> "$0.0234"
```

You can set the default rounding mode by passing one of the `BigDecimal` mode enumerables like so:

```ruby
Money.rounding_mode = BigDecimal::ROUND_HALF_EVEN
```
See [BigDecimal::ROUND_MODE](https://ruby-doc.org/3.4.1/gems/bigdecimal/BigDecimal.html#ROUND_MODE) for more information

See [BigDecimal::ROUND_MODE](https://ruby-doc.org/3.4.1/gems/bigdecimal/BigDecimal.html#ROUND_MODE) for more information.

To round to the nearest cash value in currencies without small denominations:

```ruby
Money.from_cents(11_11, "CHF").to_nearest_cash_value.format # => "CHF 11.10"
```

## Ruby on Rails

Expand Down Expand Up @@ -527,7 +535,7 @@ This will work seamlessly with [rails-i18n](https://github.com/svenfuchs/rails-i

If you wish to disable this feature and use defaults instead:

``` ruby
```ruby
Money.locale_backend = nil
```

Expand Down
22 changes: 19 additions & 3 deletions lib/money/money.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,31 @@ def fractional
#
# @see infinite_precision
def round_to_nearest_cash_value
warn "[DEPRECATION] `round_to_nearest_cash_value` is deprecated - use " \
"`to_nearest_cash_value.fractional` instead"

to_nearest_cash_value.fractional
end

# Round a given amount of money to the nearest possible money in cash value.
# For example, in Swiss franc (CHF), the smallest possible amount of cash
# value is CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and
# CHF 0.08 to CHF 0.10.
#
# @return [Money]
def to_nearest_cash_value
unless self.currency.smallest_denomination
raise UndefinedSmallestDenomination, 'Smallest denomination of this currency is not defined'
raise UndefinedSmallestDenomination,
"Smallest denomination of this currency is not defined"
end

fractional = as_d(@fractional)
smallest_denomination = as_d(self.currency.smallest_denomination)
rounded_value = (fractional / smallest_denomination).round(0, self.class.rounding_mode) * smallest_denomination
rounded_value =
(fractional / smallest_denomination)
.round(0, self.class.rounding_mode) * smallest_denomination

return_value(rounded_value)
dup_with(fractional: return_value(rounded_value))
end

# @!attribute [r] currency
Expand Down
8 changes: 8 additions & 0 deletions sig/lib/money/money.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ class Money
# @see Money.default_infinite_precision
def round_to_nearest_cash_value: () -> (Integer | BigDecimal)

# Round a given amount of money to the nearest possible money in cash value.
# For example, in Swiss franc (CHF), the smallest possible amount of cash
# value is CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and
# CHF 0.08 to CHF 0.10.
#
# @return [Money]
def to_nearest_cash_value: () -> Money

attr_reader currency: Currency

attr_reader bank: untyped
Expand Down
30 changes: 30 additions & 0 deletions spec/money_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,36 @@ def expectation.fractional
end
end

describe "#to_nearest_cash_value" do
it "rounds to the nearest possible cash value" do
expect(Money.new(23_50, "AED").to_nearest_cash_value).to eq(Money.new(23_50, "AED"))
expect(Money.new(-23_50, "AED").to_nearest_cash_value).to eq(Money.new(-23_50, "AED"))
expect(Money.new(22_13, "AED").to_nearest_cash_value).to eq(Money.new(22_25, "AED"))
expect(Money.new(-22_13, "AED").to_nearest_cash_value).to eq(Money.new(-22_25, "AED"))
expect(Money.new(22_12, "AED").to_nearest_cash_value).to eq(Money.new(22_00, "AED"))
expect(Money.new(-22_12, "AED").to_nearest_cash_value).to eq(Money.new(-22_00, "AED"))

expect(Money.new(1_78, "CHF").to_nearest_cash_value).to eq(Money.new(1_80, "CHF"))
expect(Money.new(-1_78, "CHF").to_nearest_cash_value).to eq(Money.new(-1_80, "CHF"))
expect(Money.new(1_77, "CHF").to_nearest_cash_value).to eq(Money.new(1_75, "CHF"))
expect(Money.new(-1_77, "CHF").to_nearest_cash_value).to eq(Money.new(-1_75, "CHF"))
expect(Money.new(1_75, "CHF").to_nearest_cash_value).to eq(Money.new(1_75, "CHF"))
expect(Money.new(-1_75, "CHF").to_nearest_cash_value).to eq(Money.new(-1_75, "CHF"))

expect(Money.new(2_99, "USD").to_nearest_cash_value).to eq(Money.new(2_99, "USD"))
expect(Money.new(-2_99, "USD").to_nearest_cash_value).to eq(Money.new(-2_99, "USD"))
expect(Money.new(3_00, "USD").to_nearest_cash_value).to eq(Money.new(3_00, "USD"))
expect(Money.new(-3_00, "USD").to_nearest_cash_value).to eq(Money.new(-3_00, "USD"))
expect(Money.new( 3_01, "USD").to_nearest_cash_value).to eq(Money.new(3_01, "USD"))
expect(Money.new(-3_01, "USD").to_nearest_cash_value).to eq(Money.new(-3_01, "USD"))
end

it "raises an error if smallest denomination is not defined" do
expect {Money.new(1_00, "XAG").to_nearest_cash_value}
.to raise_error(Money::UndefinedSmallestDenomination)
end
end

describe "#amount" do
it "returns the amount of cents as dollars" do
expect(Money.new(1_00).amount).to eq 1
Expand Down