为什么BigFloat.to_s不够精确?

Iho*_*alo 7 gmp crystal-lang

我不确定这是不是一个bug.但我一直在玩,big我不明白为什么这段代码以这种方式工作:

https://carc.in/#/r/2w96

require "big"

x = BigInt.new(1<<30) * (1<<30) * (1<<30)
puts "BigInt: #{x}"

x = BigFloat.new(1<<30) * (1<<30) * (1<<30) 
puts "BigFloat: #{x}"
puts "BigInt from BigFloat: #{x.to_big_i}"
Run Code Online (Sandbox Code Playgroud)

产量

BigInt: 1237940039285380274899124224
BigFloat: 1237940039285380274900000000
BigInt from BigFloat: 1237940039285380274899124224
Run Code Online (Sandbox Code Playgroud)

首先,我认为BigFloat需要更改 BigFloat.default_precision为更大的数字.但是从这段代码看起来它只在尝试输出#to_s值时才有意义.

与BigFloat的精度设置为1024(https://carc.in/#/r/2w98)相同:

产量

BigInt: 1237940039285380274899124224
BigFloat: 1237940039285380274899124224
BigInt from BigFloat: 1237940039285380274899124224
Run Code Online (Sandbox Code Playgroud)

BigFloat.to_s用途LibGMP.mpf_get_str(nil, out expptr, 10, 0, self).GMP在哪里说:

mpf_get_str (char *str, mp_exp_t *expptr, int base, size_t n_digits, const mpf_t op)

将op转换为基数中的数字字符串.基本参数可以在2到62之间或从-2到-36之间变化.最多将生成n_digits数字.不返回尾随零.不会产生比op准确表示的数字更多的数字.如果n_digits为0,则生成准确的最大位数.

谢谢.

Top*_*ret 1

在 GMP 中(它适用于所有语言,而不仅仅是 Crystal),整数(C mpz_t、 Crystal BigInt)和浮点数(C mpf_t、 Crystal BigFloat)具有单独的默认精度。

另请注意,使用显式精度比设置默认精度更好,因为默认精度可能不可重入(它取决于 -timeconfigure开关)。此外,如果有人只读取代码的一部分,他们可能会跳过设置默认精度的部分并假设错误的精度。尽管我不太了解 Crystal 绑定,但我认为此类功能已在某处公开。

传递给的零参数mpf_get_str意味着根据精度猜测值。我知道有效位数成正比并且接近precision / log2(10)。浮点数的精度有限。在这种情况下,并不是mpf_get_str调用使最后一位数字为零——而是内部表示没有保留此类数据。看起来您的(默认)精度太小,无法存储所有必要的数字。

总结一下,解决方案有两种:

  • 设置全局默认精度。尽管这种方法可行,但需要经常更改默认精度,或者在整个程序中使用默认精度。无论哪种方式,默认精度的方法都是一种拖延形式,稍后会受到报复。
  • 在变量的基础上设置精度。这是比前者更好的解决方案。尽管它需要更多代码(每个变量初始化多 1-2 行),但稍后会得到回报。例如,在空间物体跟踪系统中,物理计算必须超精确,但其他系统可以使用较低精度的数字来提高速度和节省内存。

我仍然不确定是什么导致转换BigFloat --> BigInt产生缺失的数字。