如何在 Elixir 中设置小数精度?

问题描述 投票:0回答:1

我正在使用 Elixir 的 Decimal 模块:

https://hexdocs.pm/decimal/readme.html

我编写了自己的宏来更新精度:

defmodule Mars.Decimal do
  defmacro __using__(_) do
    quote do
      require Decimal

      %{Decimal.Context.get() | precision: 2}
      |> Decimal.Context.set()

  end
end

问题:

这似乎根本不影响精度。如何全局设置精度?

目前,Decimal 并不等同于混合精度,即

Decimal.new(1) != Decimal.new("1.0")

我希望如果我可以将精度设置为在任何地方都固定,我可以解决这个问题。

作为一个补充问题;我正在尝试对货币执行此操作,因此它始终是小数点后两位。 Decimal 模块的精度仅设置总精度(即最多 2 位数字,无论

.
的哪一侧,即 3456.123 -> 3500)。如果 Elixir 中有更干净的处理货币数字的方法,请告诉我!

elixir
1个回答
1
投票

根据这篇文章https://elixirforum.com/t/how-to-set-default-decimal- precision/40404,需要设置精度在您希望其生效的每个进程中 。例如,将

Decimal.Context.set(%Decimal.Context{Decimal.Context.get() | precision: 2})
粘贴到
iex
会话中,does 会影响
iex
中发生的所有后续计算,因为
iex
在其自己的进程中运行。

但是,这对回到“现实世界”没有帮助,在“现实世界”中,您的应用程序可能会在许多不同(且未知)的进程中执行。我认为为您的特定用例充实几个示例可以使文档受益。

考虑到

Decimal
将其设置存储在进程字典中的方式,您需要将所有计算隔离到单个进程中,例如在
GenServer
。然后,您与它的交互将调用该特定进程,其中上下文已设置为精确到小数点后两位。

这里是一个示例

GenServer
,将小数精度设置为 2:

defmodule Foo.Precision2 do
  use GenServer

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def init(state) do
    Decimal.Context.set(%Decimal.Context{Decimal.Context.get() | precision: 2})
    {:ok, state}
  end

  def handle_call({:div, x, y}, _from, state) do
    result = Decimal.div(x, y)
    {:reply, result, state}
  end

  def div(x, y) do
    GenServer.call(__MODULE__, {:div, x, y})
  end
end

然后您需要使用您的应用程序启动它,例如里面

application.ex
:

defmodule Foo.Application do
  @moduledoc false

  use Application

  @impl true
  def start(_type, _args) do=
    opts = [strategy: :one_for_one, name: Foo.Supervisor]

    Supervisor.start_link(
      [
        {Foo.Precision2, []}
      ],
      opts
    )
  end
end

最后,您可以将调用路由到

GenServer
的客户端函数,例如

iex> Foo.Precision2.div(100,3)
#Decimal<33>

诚然,这感觉像是一个荒谬的设置量来支持一个相当直接的用例(并且肯定有更干净的方法来实现这一点),但它似乎有效。这种方法的一个优点可能是您可以维护多个上下文,并为每个上下文设置特定的设置。

© www.soinside.com 2019 - 2024. All rights reserved.