登录
首页 >  Golang >  Go问答

对于货币应用程序来说,使用整数作为小数系数而不是浮点数是个好主意吗?

来源:stackoverflow

时间:2024-04-01 22:21:36 214浏览 收藏

Golang小白一枚,正在不断学习积累知识,现将学习到的知识记录一下,也是将我的所得分享给大家!而今天这篇文章《对于货币应用程序来说,使用整数作为小数系数而不是浮点数是个好主意吗?》带大家来了解一下##content_title##,希望对大家的知识积累有所帮助,从而弥补自己的不足,助力实战开发!


问题内容

我的应用程序需要小数数量乘以货币价值。

例如,$65.50 × 0.55 小时 = $36.025(四舍五入为 $36.03)。

我知道浮点数不应该用来表示金钱,因此我将所有货币值存储为美分。 $65.50 在上式中存储为 6550(整数)。

对于小数系数,我的问题是 0.55 没有 32 位浮点表示。在上面的用例中,0.55 小时 == 33 分钟 ,因此 0.55 是我的应用程序需要准确考虑的特定值的示例。 0.550000012 的浮点表示是不够的,因为用户不会理解附加的 0.000000012 来自哪里。我不能简单地在 0.550000012 上调用舍入函数,因为它会舍入到整数。

乘法解决方案

为了解决这个问题,我的第一个想法是将所有数量存储为整数并乘以×1000。因此用户输入的0.55在存储时将变成550(整数)。所有计算都将在没有浮点数的情况下进行,然后在向用户呈现结果时简单地除以 1000(整数除法,而不是浮点数)。

  • 我意识到这将永久限制我的小数点后 3 位 精确。如果我认为 3 足以满足我的一生 应用程序,这种方法有意义吗?

  • 如果我使用整数除法,是否存在潜在的舍入问题?

  • 这个过程有名字吗? 编辑:正如@SergGr所示,这是定点算术。

  • 有更好的方法吗?

编辑:

我应该澄清一下,这不是特定时间的。它适用于一般数量,例如 1.256 磅面粉 1 sofa0.25 小时 (想想发票)。

我在这里尝试复制的是 Postgres 的 extra_float_digits = 0 功能的更精确版本,如果用户输入 0.55 (float32),数据库将存储 0.550000012 但在查询结果时返回 0。 55 这似乎正是用户输入的内容。

我愿意将此应用程序的精度限制为小数点后 3 位(这是商业问题,而不是科学问题),因此这就是让我考虑使用 × 的原因。 1000 方法。

我正在使用 Go 编程语言,但我对通用跨语言解决方案感兴趣。


解决方案


注意:这是按照 Matt 的要求将不同的评论合并为一个连贯的答案的尝试。

TL;DR

  1. 是的,这种方法很有意义,但很可能不是最佳选择
  2. 是的,存在舍入问题,但无论您使用哪种表示形式,都不可避免地会出现一些问题
  3. 您建议使用的名称为 Decimal fixed point numbers
  4. 我认为是的,有一个更好的方法,那就是为您的语言使用一些标准或流行的 decimal floating point numbers 库(Go 不是我的母语,所以我不能推荐一个)

在 PostgreSQL 中,最好使用 numeric (例如 Numeric(15,3) 之类的东西),而不是 float4/float8extra_float_digits 的组合。实际上,这就是 PostgreSQL doc on Floating-Point Types 中的第一项所建议的内容:

  • 如果您需要精确的存储和计算(例如货币金额),请改用 numeric 类型。

有关如何存储非整数的更多详细信息

首先有一个基本事实,即 [0;1] 范围内有无限多个数字,因此显然无法将每个数字存储在任何有限的数据结构中。这意味着您必须做出一些妥协:无论您选择哪种方式,都会有一些数字您无法准确存储,因此您必须进行舍入。

另一个重要的一点是,人们习惯于基于 10 的系统,在该系统中,只有 2^a*5^b 形式的数字除法结果可以用有限位数表示。对于每个其他有理数,即使您以某种方式将其存储为精确的形式,您也必须在格式化阶段进行一些截断和舍入以供人类使用。

存储数字的方法可能有无数种。在实践中,只有少数被广泛使用:

  • floating point numbers 具有两个主要的二进制分支(这是当今大多数硬件本身实现的内容,也是大多数语言支持的内容,如 floatdouble)和 decimal。这是存储尾数和指数的格式(可以是负数),所以数字是 mantissa * base^exponent (我省略了符号,只是说它在逻辑上是尾数的一部分,尽管实际上它通常是单独存储的)。二进制与十进制由 base 指定。例如,0.5 将以二进制形式存储为一对 (1,-1),即 1*2^-1,以十进制形式存储为一对 (5,-1),即 5*10^-1。理论上,您也可以使用任何其他基数,但实际上只有 2 和 10 作为基数才有意义。

  • fixed point numbers,二进制和十进制的除法相同。这个想法与浮点数相同,但所有数字都使用一些固定指数。您建议的实际上是一个十进制定点数,指数固定为 -3。我见过在一些没有内置浮点数支持的嵌入式硬件上使用二进制定点数,因为可以使用整数算术以合理的效率实现二进制定点数。至于十进制定点数,实际上实现起来并不比十进制浮点数容易多少,但灵活性却低得多。

  • 有理数格式,即该值存储为一对 (p, q),表示 p/q(通常是 q>0,因此符号存储在 p 中,并且 p=0、q=1 表示 zqbc) zqb0或 gcd(p,q) = 1 对于每个其他数字)。通常这首先需要一些大整数算术才有用(这里是 math.big.Rat 的 Go 示例)。实际上,这对于某些问题可能是一种有用的格式,人们经常忘记这种可能性,可能是因为它通常不是标准库的一部分。另一个明显的缺点是,正如我所说,人们不习惯用有理数来思考(你能轻松比较 123/456213/789 哪个更大吗?),所以你必须将最终结果转换为其他形式。另一个缺点是,如果您有很长的计算链,内部数字(pq)可能很容易变成非常大的值,因此计算速度会很慢。存储计算的中间结果仍然可能有用。

实际上,也分为任意长度和固定长度表示。例如:

实际上,我想说,解决您的问题的最佳解决方案是一些足够大的固定长度浮点十进制表示(例如.Net decimal)。任意长度的实现也可以工作。如果您必须从头开始实现,那么您对固定长度定点十进制表示的想法可能没问题,因为它是您自己实现的最简单的事情(比以前的替代方案容易一点),但它可能会成为某些方面的负担存储结果的另一种解决方案是使用值的有理形式。您可以用两个等于 p/q 的整数值来解释该数字,这样 pq 都是整数。因此,您可以对数字进行更精确的计算,并使用两个整数格式的有理数进行一些数学运算。

今天关于《对于货币应用程序来说,使用整数作为小数系数而不是浮点数是个好主意吗?》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于的内容请关注golang学习网公众号!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>