如何在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
核心问题
- 为什么这个宏无法影响精度?如何全局设置Decimal精度?
- 当前Decimal认为不同精度的数值不相等(比如
Decimal.new(1) != Decimal.new("1.0")),希望通过全局固定精度解决这个问题。
附加问题(货币处理)
需要处理货币数值,要求始终保留两位小数,但Decimal的precision是设置总精度(比如3456.123会被处理为3500),有没有更简洁的货币数值处理方式?
解决方案
一、全局设置Decimal精度的正确方式
你的宏无效的原因是:Decimal.Context.set/1是进程内生效的,而__using__宏只会在编译时执行一次,作用域仅限于当前模块的编译进程,运行时的业务进程、测试进程并不会继承这个设置。
正确的全局设置方法有两种:
应用配置文件全局指定
在config/config.exs中添加默认上下文配置,所有进程启动时会自动加载:config :decimal, :default_context, precision: 2, rounding: :half_up应用启动时动态初始化
如果需要根据环境动态调整精度,可以在应用启动函数中设置: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是总精度(整数位+小数位),不适合直接处理固定小数位的货币需求,推荐两种方案:
基于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"))使用专门的货币处理库
如果需要多币种、汇率转换等复杂逻辑,推荐使用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
相关产品推荐
相关产品推荐

