防止 ActiveRecord 舍入小的数字属性 (< 1e-20)?

Posted

技术标签:

【中文标题】防止 ActiveRecord 舍入小的数字属性 (< 1e-20)?【英文标题】:Prevent ActiveRecord from rounding small numerical attributes ( < 1e-20)? 【发布时间】:2021-11-08 03:20:17 【问题描述】:

我需要我的 Rails 应用程序处理一些非常小的数字,即小于 10e-20。为此,我需要在三个不同的系统之间进行交互,我的数据库 (Postgres)、ActiveRecord 和 Ruby 本身。 Ruby 和 Postgres 似乎都乐于处理小到 1e-307 的数字。但是我正在努力让 ActiveRecord 发挥作用。

Postgres documentation

双精度类型的范围通常在 1E-307 到 1E+308 左右,精度至少为 15 位

Ruby documentation

最小值 双精度浮点中的最小正归一化数。通常默认为 2.2250738585072014e-308。

所以 Ruby 和 Postgres 都应该可以处理像 10e-307 这样小的数字并且大约有 15 位小数。

考虑以下记录

我的“项目”记录有一个排名属性。

  # schema.rb
  create_table "items", id: :uuid, default: ->  "gen_random_uuid()" , force: :cascade do |t|
    t.float "rank"
    ...
  end

我可以从 PSQL 中看到一条特定记录的 this 值是1.24324e-20

ActiveRecord 模糊了精度

但是,当我通过 ActiveRecord 读取此值时,它会将其四舍五入为 1 个有效数字:

myItem = Item.find('a60e5947-6e75-4c4e-8b54-c13887ad6bab')
myItem.rank
# => 0.1e-19
# (this should be 0.124324e-19)

我可以使用原始 SQL 查询再次确认精确值在其中:

ActiveRecord::Base.connection.execute("select rank from items where id = 'a60e5947-6e75-4c4e-8b54-c13887ad6bab'").values
#=> [[1.24324e-20]]

这不是红宝石问题...

我想排除 Ruby 没有对数字进行四舍五入的可能性,所以我将存储在 Postgres 中的值直接打印到控制台中以检查我是否可以操作它:

1.24324e-20 + 1.1e-20
# => 2.34324e-20 (correct)

这不是 Rails 命令行格式问题

由于 Rails 用于打印到命令行的格式有时会掩盖值,因此我也想检查一下。为了确认这不仅仅是格式问题,我尝试将数字乘以 10e20 并添加另一个数字,以查看精度是否只是隐藏在 Rails 格式中的某个位置:

myItem.rank 
# => 0.1e-19
i.rank * 1e20 + 1.001
# =>  2.001
# (the answer should be 2.244239)

计算中忽略原始数字 (1.34324) 的精度。所以这不是命令行格式问题。

为什么 ActiveRecord 不尊重原始精度?

我需要做些什么才能让 ActiveRecord 跟上 Postgres 和 Ruby 的精度?

请注意:我不想切换数据库列类型

8 位浮点列类型非常适合我希望存储的数字。我不需要疯狂的精确度,我只需要非常非常小的数字。我可以将数据库列切换为decimalnumeric,但存储的数据量完全没有必要。

Float 非常适合我的需要 - 我只需要 ActiveRecord 才能从数据库中正确读取它......

【问题讨论】:

您使用的是什么版本?我刚刚用 Ruby 2.7.4 和 Rails 6.1.4.1 尝试过这个,似乎保留了像 1.24324e-20 这样的小数字的精度。 我正在使用 Ruby 2.6.6 和 Rails 6.1.4.1... 咳咳——我知道问题出在哪里了……在下面回答 【参考方案1】:

咳咳。所以事实证明,在 rank 属性的先前数据库迁移期间,我已为其分配了具有固定精度的 decimal 类型。

虽然我已将数据库迁移到使用 float 作为该属性,但我没有重新加载我的控制台,因此它正在打印出被之前的 decimal 精度规范截断的值。

令我惊讶的是,不仅控制台需要重新启动,我的开发服务器也需要重新启动,我以为它自己更新了,但显然它没有。

无论如何。谜团已揭开。呵呵。

【讨论】:

强烈建议避免使用这种精度的浮点数,而是使用小数并设置适当的精度。如果精度很重要,浮点数学是一个非常糟糕的主意 不幸的是,我真的需要花车的速度。如果我使用小数(在 javascript 中),那么与字符串之间的转换确实会减慢一切。所以我的方法是监控区分项目所需的精度,然后在低于临界水平时重新排列它们

以上是关于防止 ActiveRecord 舍入小的数字属性 (< 1e-20)?的主要内容,如果未能解决你的问题,请参考以下文章

防止 MATLAB 舍入数字并设置小数精度

防止实例上的 ActiveRecord save()

数字日期和时间

如何防止Java自动舍入

Math.round() 函数返回一个数字四舍五入后最接近的整数。

如何使 UISlider 以指数方式输出漂亮的舍入数字?