You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Elixir中全局设置Decimal精度?求货币数值处理方案

关于Elixir Decimal模块的两个问题:全局精度设置与货币数值处理

问题背景

我正在使用Elixir的Decimal模块,写了一个自定义宏试图全局设置精度,但完全不起作用:

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

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

  end
end

核心问题

  1. 为什么这个宏无法影响精度?如何全局设置Decimal精度?
  2. 当前Decimal认为不同精度的数值不相等(比如Decimal.new(1) != Decimal.new("1.0")),希望通过全局固定精度解决这个问题。

附加问题(货币处理)

需要处理货币数值,要求始终保留两位小数,但Decimal的precision是设置总精度(比如3456.123会被处理为3500),有没有更简洁的货币数值处理方式?


解决方案

一、全局设置Decimal精度的正确方式

你的宏无效的原因是:Decimal.Context.set/1是进程内生效的,而__using__宏只会在编译时执行一次,作用域仅限于当前模块的编译进程,运行时的业务进程、测试进程并不会继承这个设置。

正确的全局设置方法有两种:

  1. 应用配置文件全局指定
    在config/config.exs中添加默认上下文配置,所有进程启动时会自动加载:

    config :decimal, :default_context,
      precision: 2,
      rounding: :half_up
    
  2. 应用启动时动态初始化
    如果需要根据环境动态调整精度,可以在应用启动函数中设置:

    defmodule Mars.Application do
      use Application
    
      def start(_type, _args) do
        # 覆盖默认上下文
        Decimal.Context.set(%Decimal.Context{Decimal.Context.get() | precision: 2, rounding: :half_up})
    
        children = [
          # 你的业务子进程
        ]
    
        Supervisor.start_link(children, strategy: :one_for_one)
      end
    end
    

关于数值相等的问题

即使设置了全局精度,Decimal.new(1)和Decimal.new("1.0")默认仍不相等——因为它们的scale(小数位数)不同。解决方式是统一格式化后再比较:

def equal_currency?(a, b) do
  Decimal.round(a, 2) == Decimal.round(b, 2)
end

可以把这个逻辑封装到自定义模块,全局调用即可。

二、货币数值的简洁处理方式

Decimal的precision是总精度(整数位+小数位),不适合直接处理固定小数位的货币需求,推荐两种方案:

  1. 基于Decimal封装自定义货币函数
    手动固定小数位为2,每次运算后强制格式化:

    defmodule Mars.Currency do
      @scale 2
      @rounding :half_up
    
      # 格式化货币:保留两位小数,四舍五入
      def format(decimal) do
        Decimal.round(decimal, @scale, rounding: @rounding)
      end
    
      # 封装加法,自动格式化结果
      def add(a, b) do
        Decimal.add(a, b) |> format()
      end
    end
    
    # 示例:3456.123 → 3456.12
    Mars.Currency.format(Decimal.new("3456.123"))
    
  2. 使用专门的货币处理库
    如果需要多币种、汇率转换等复杂逻辑,推荐使用money库——它基于Decimal实现,原生支持固定小数位、四舍五入等货币规则:

    # 在mix.exs添加依赖
    defp deps do
      [
        {:money, "~> 1.12"}
      ]
    end
    
    # 用法示例:单位为最小货币单位(分)
    Money.new(100, :USD) # 代表$1.00
    Money.add(Money.new(50, :USD), Money.new(75, :USD)) # 得到$1.25
    

内容的提问来源于stack exchange,提问作者cjm2671

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:20:39