|
1 | 1 | defmodule Money do |
2 | | - import Kernel, except: [abs: 1, round: 1] |
| 2 | + import Kernel, except: [abs: 1, round: 1, div: 2] |
3 | 3 |
|
4 | 4 | @moduledoc """ |
5 | 5 | Defines a `Money` struct along with convenience methods for working with currencies. |
@@ -510,19 +510,57 @@ defmodule Money do |
510 | 510 |
|
511 | 511 | @spec divide(t, integer) :: [t] |
512 | 512 | @doc ~S""" |
513 | | - Divides up `Money` by an amount |
| 513 | + Divides up `Money` into a list of `Money` structs split as evenly as possible. |
| 514 | +
|
| 515 | + This function splits a Money amount into the specified number of parts, returning |
| 516 | + a list of Money structs. When the amount cannot be divided evenly, the remainder |
| 517 | + is distributed by adding 1 to the first N Money structs in the returned list, |
| 518 | + where N is the remainder amount. Negative denominators are supported and will flip |
| 519 | + the sign of all amounts. |
| 520 | +
|
| 521 | + ## Warning |
| 522 | +
|
| 523 | + **Memory Usage**: This function creates a list containing `denominator` number of |
| 524 | + Money structs. For large denominators (e.g., > 100,000), this can consume |
| 525 | + significant memory and potentially lead to out-of-memory (OOM) errors. |
| 526 | + Consider using `Money.div/2` instead if you only need a single divided amount |
514 | 527 |
|
515 | 528 | ## Examples |
516 | 529 |
|
| 530 | + # Even division |
517 | 531 | iex> Money.divide(Money.new(100, :USD), 2) |
518 | 532 | [%Money{amount: 50, currency: :USD}, %Money{amount: 50, currency: :USD}] |
519 | 533 |
|
| 534 | + # Uneven division - remainder distributed to first struct |
520 | 535 | iex> Money.divide(Money.new(101, :USD), 2) |
521 | 536 | [%Money{amount: 51, currency: :USD}, %Money{amount: 50, currency: :USD}] |
522 | 537 |
|
| 538 | + # Multiple parts with remainder distribution |
| 539 | + iex> Money.divide(Money.new(100, :USD), 3) |
| 540 | + [%Money{amount: 34, currency: :USD}, %Money{amount: 33, currency: :USD}, %Money{amount: 33, currency: :USD}] |
| 541 | +
|
| 542 | + # Negative amounts |
| 543 | + iex> Money.divide(Money.new(-7, :USD), 2) |
| 544 | + [%Money{amount: -4, currency: :USD}, %Money{amount: -3, currency: :USD}] |
| 545 | +
|
| 546 | + # Negative denominators flip the sign |
| 547 | + iex> Money.divide(Money.new(-7, :USD), -2) |
| 548 | + [%Money{amount: 4, currency: :USD}, %Money{amount: 3, currency: :USD}] |
| 549 | +
|
| 550 | + # Large denominators can consume significant memory - USE WITH CAUTION! |
| 551 | + # Money.divide(Money.new(1000000, :USD), 100000) # Creates 100,000 Money structs (~10MB)! |
| 552 | +
|
| 553 | + ## See Also |
| 554 | +
|
| 555 | + For simple division that returns a single Money struct, use `Money.div/2`. |
| 556 | +
|
523 | 557 | """ |
| 558 | + def divide(%Money{}, 0) do |
| 559 | + raise ArithmeticError, "division by zero" |
| 560 | + end |
| 561 | + |
524 | 562 | def divide(%Money{amount: amount, currency: cur}, denominator) when is_integer(denominator) do |
525 | | - value = div(amount, denominator) |
| 563 | + value = Kernel.div(amount, denominator) |
526 | 564 | rem = rem(amount, denominator) |
527 | 565 | do_divide(cur, value, rem, denominator, []) |
528 | 566 | end |
@@ -551,6 +589,71 @@ defmodule Money do |
551 | 589 | defp decrement_abs(n) when n >= 0, do: n - 1 |
552 | 590 | defp decrement_abs(n) when n < 0, do: n + 1 |
553 | 591 |
|
| 592 | + @spec div(t, integer | float | Decimal.t()) :: t |
| 593 | + @doc """ |
| 594 | + Divides a `Money` struct by a number and returns a single `Money` struct. |
| 595 | +
|
| 596 | + The divisor can be a number (integer or float) or a Decimal. The calculation is performed |
| 597 | + in integer arithmetic with half-up rounding. |
| 598 | +
|
| 599 | + ## Examples |
| 600 | +
|
| 601 | + iex> Money.div(Money.new(100, :USD), 2) |
| 602 | + %Money{amount: 50, currency: :USD} |
| 603 | +
|
| 604 | + iex> Money.div(Money.new(151, :USD), 2) |
| 605 | + %Money{amount: 76, currency: :USD} |
| 606 | +
|
| 607 | + iex> Money.div(Money.new(100, :USD), 3) |
| 608 | + %Money{amount: 33, currency: :USD} |
| 609 | +
|
| 610 | + iex> Money.div(Money.new(-151, :USD), 2) |
| 611 | + %Money{amount: -76, currency: :USD} |
| 612 | +
|
| 613 | + iex> Money.div(Money.new(100, :USD), Decimal.new("1.5")) |
| 614 | + %Money{amount: 67, currency: :USD} |
| 615 | +
|
| 616 | + iex> Money.div(Money.new(151, :USD), Decimal.new("2")) |
| 617 | + %Money{amount: 76, currency: :USD} |
| 618 | +
|
| 619 | + Division by zero raises an ArithmeticError: |
| 620 | +
|
| 621 | + iex> Money.div(Money.new(100, :USD), 0) |
| 622 | + ** (ArithmeticError) division by zero |
| 623 | +
|
| 624 | + """ |
| 625 | + def div(%Money{}, divisor) when divisor == +0.0 or divisor == -0.0 or divisor == 0 do |
| 626 | + raise ArithmeticError, "division by zero" |
| 627 | + end |
| 628 | + |
| 629 | + def div(%Money{amount: amount, currency: cur}, divisor) when is_integer(divisor) do |
| 630 | + result = amount / divisor |
| 631 | + rounded_amount = Kernel.round(result) |
| 632 | + Money.new(rounded_amount, cur) |
| 633 | + end |
| 634 | + |
| 635 | + def div(%Money{amount: amount, currency: cur}, divisor) when is_float(divisor) do |
| 636 | + result = amount / divisor |
| 637 | + rounded_amount = Kernel.round(result) |
| 638 | + Money.new(rounded_amount, cur) |
| 639 | + end |
| 640 | + |
| 641 | + if Code.ensure_loaded?(Decimal) do |
| 642 | + def div(%Money{amount: amount, currency: cur}, %Decimal{} = divisor) do |
| 643 | + if Decimal.equal?(divisor, Decimal.new("0")) do |
| 644 | + raise ArithmeticError, "division by zero" |
| 645 | + else |
| 646 | + result = |
| 647 | + amount |
| 648 | + |> Decimal.div(divisor) |
| 649 | + |> Decimal.round(0, Decimal.Context.get().rounding) |
| 650 | + |> Decimal.to_integer() |
| 651 | + |
| 652 | + Money.new(result, cur) |
| 653 | + end |
| 654 | + end |
| 655 | + end |
| 656 | + |
554 | 657 | @spec to_string(t, Keyword.t()) :: String.t() |
555 | 658 | @doc ~S""" |
556 | 659 | Converts a `Money` struct to a string representation |
|
0 commit comments