context.ex (4899B)
1 defmodule Decimal.Context do 2 import Decimal.Macros 3 alias Decimal.Context 4 5 @moduledoc """ 6 The context is kept in the process dictionary. It can be accessed with 7 `get/0` and `set/1`. 8 9 The default context has a precision of 28, the rounding algorithm is 10 `:half_up`. The set trap enablers are `:invalid_operation` and 11 `:division_by_zero`. 12 13 ## Fields 14 15 * `precision` - maximum number of decimal digits in the coefficient. If an 16 operation result has more digits it will be rounded to `precision` 17 digits with the rounding algorithm in `rounding`. 18 * `rounding` - the rounding algorithm used when the coefficient's number of 19 exceeds `precision`. Strategies explained below. 20 * `flags` - a list of signals that for which the flag is sent. When an 21 exceptional condition is signalled its flag is set. The flags are sticky 22 and will be set until explicitly cleared. 23 * `traps` - a list of set trap enablers for signals. When a signal's trap 24 enabler is set the condition causes `Decimal.Error` to be raised. 25 26 ## Rounding algorithms 27 28 * `:down` - round toward zero (truncate). Discarded digits are ignored, 29 result is unchanged. 30 * `:half_up` - if the discarded digits is greater than or equal to half of 31 the value of a one in the next left position then the coefficient will be 32 incremented by one (rounded up). Otherwise (the discarded digits are less 33 than half) the discarded digits will be ignored. 34 * `:half_even` - also known as "round to nearest" or "banker's rounding". If 35 the discarded digits is greater than half of the value of a one in the 36 next left position then the coefficient will be incremented by one 37 (rounded up). If they represent less than half discarded digits will be 38 ignored. Otherwise (exactly half), the coefficient is not altered if it's 39 even, or incremented by one (rounded up) if it's odd (to make an even 40 number). 41 * `:ceiling` - round toward +Infinity. If all of the discarded digits are 42 zero or the sign is negative the result is unchanged. Otherwise, the 43 coefficient will be incremented by one (rounded up). 44 * `:floor` - round toward -Infinity. If all of the discarded digits are zero 45 or the sign is positive the result is unchanged. Otherwise, the sign is 46 negative and coefficient will be incremented by one. 47 * `:half_down` - if the discarded digits is greater than half of the value 48 of a one in the next left position then the coefficient will be 49 incremented by one (rounded up). Otherwise (the discarded digits are half 50 or less) the discarded digits are ignored. 51 * `:up` - round away from zero. If all discarded digits are zero the 52 coefficient is not changed, otherwise it is incremented by one (rounded 53 up). 54 55 This table shows the results of rounding operations for all the rounding 56 algorithms: 57 58 Rounding algorithm | 5.5 | 2.5 | 1.6 | 1.1 | 1.0 | -1.0 | -1.1 | -1.6 | -2.5 | -5.5 59 :----------------- | :-- | :-- | :-- | :-- | :-- | :--- | :--- | :--- | :--- | :--- 60 `:up` | 6 | 3 | 2 | 2 | 1 | -1 | -2 | -2 | -3 | -6 61 `:down` | 5 | 2 | 1 | 1 | 1 | -1 | -1 | -1 | -2 | -5 62 `:ceiling` | 6 | 3 | 2 | 2 | 1 | -1 | -1 | -1 | -2 | -5 63 `:floor` | 5 | 2 | 1 | 1 | 1 | -1 | -2 | -2 | -3 | -6 64 `:half_up` | 6 | 3 | 2 | 1 | 1 | -1 | -1 | -2 | -3 | -6 65 `:half_down` | 5 | 2 | 2 | 1 | 1 | -1 | -1 | -2 | -2 | -5 66 `:half_even` | 6 | 2 | 2 | 1 | 1 | -1 | -1 | -2 | -2 | -6 67 68 """ 69 @type t :: %__MODULE__{ 70 precision: pos_integer, 71 rounding: Decimal.rounding(), 72 flags: [Decimal.signal()], 73 traps: [Decimal.signal()] 74 } 75 76 defstruct precision: 28, 77 rounding: :half_up, 78 flags: [], 79 traps: [:invalid_operation, :division_by_zero] 80 81 @context_key :"$decimal_context" 82 83 @doc """ 84 Runs function with given context. 85 """ 86 doc_since("1.9.0") 87 @spec with(t(), (() -> x)) :: x when x: var 88 def with(%Context{} = context, fun) when is_function(fun, 0) do 89 old = Process.put(@context_key, context) 90 91 try do 92 fun.() 93 after 94 set(old || %Context{}) 95 end 96 end 97 98 @doc """ 99 Gets the process' context. 100 """ 101 doc_since("1.9.0") 102 @spec get() :: t() 103 def get() do 104 Process.get(@context_key, %Context{}) 105 end 106 107 @doc """ 108 Set the process' context. 109 """ 110 doc_since("1.9.0") 111 @spec set(t()) :: :ok 112 def set(%Context{} = context) do 113 Process.put(@context_key, context) 114 :ok 115 end 116 117 @doc """ 118 Update the process' context. 119 """ 120 doc_since("1.9.0") 121 @spec update((t() -> t())) :: :ok 122 def update(fun) when is_function(fun, 1) do 123 get() |> fun.() |> set() 124 end 125 end