为什么在使用 Ruby Rational 时会出现舍入错误?

Why am I getting rounding errors when using Ruby Rational?

提问人:bodacious 提问时间:1/9/2014 更新时间:1/9/2014 访问量:108

问:

我正在以编程方式计算给定音符的频率。

快速介绍:

  • 音符 A4 有一个频率为 440Hz
  • 高一个八度的音符是同一音符在较低八度频率的两(因此 A3 为 220Hz,A2 为 110Hz)
  • 一个音符与下一个半音之间的差异是 log 2 到 12 的底部。
  • C3 = B3 x 2 ^ (1/12)

用 Ruby 编写这个公式时,我想出了以下几点:

# Starting from the note A4 (440Hz)
A4 = 440.to_f
# I want the frequencies of each note over the next 3 octaves
number_of_octaves = 3
# There are 12 semitones per octave
SEMITIONES_PER_OCTAVE = 12

current_freq = A4
(number_of_octaves * SEMITIONES_PER_OCTAVE).times do |i|
  puts "-----" if i % 12 == 0 # separate each octave with dashes
  puts current_freq
  current_freq = current_freq * 2 ** Rational('1/12')
end

不过,我得到的结果并不完美。A 音符似乎比预期的要高一点:

-----
440.0
466.1637615180899
493.8833012561241
523.2511306011974
554.3652619537443
587.3295358348153
622.253967444162
659.2551138257401
698.456462866008
739.988845423269
783.9908719634989
830.6093951598906
-----
880.0000000000003
932.3275230361802
987.7666025122486
1046.502261202395
1108.7305239074888
1174.6590716696307
1244.5079348883241
1318.5102276514804
1396.9129257320162
1479.9776908465383
1567.981743926998
1661.2187903197814
-----
1760.000000000001
1864.6550460723606
1975.5332050244976
2093.0045224047904
2217.4610478149784
2349.3181433392624
2489.0158697766497
2637.020455302962
2793.825851464034
2959.9553816930784
3135.963487853998
3322.4375806395647

请注意 A 频率 - 它们不是 880、1760,而是高。

我认为 Ruby 的 Rational 应该给出准确的计算,并避免使用浮点数的舍入错误。

谁能解释一下:

  1. 为什么这个结果不准确?
  2. 如何改进上述代码以获得真正准确的结果?
Ruby 数学 浮点精度 有理数

评论


答:

0赞 Scott Hunter 1/9/2014 #1

虽然我确信它准确地代表了 1/12(作为分数),但一旦你将其用作指数,你就会回到浮点数,并且有可能获得四舍五入的回报。

我想你写了你自己的幂函数,它检查指数是否是整数并显式使用乘法;这至少可以照顾到你的 A。

5赞 lurker 1/9/2014 #2

我不清楚在这个表达式中:Ruby 是否将整个计算保持在理性领域。在 Ruby 中,您可以获得:current_freq * 2 ** Rational('1/12')

2.0.0p195 :001 > current_freq = 440
 => 440
2.0.0p195 :002 >  current_freq * 2 ** Rational('1/12')
 => 466.1637615180899

计算生成的是浮点数,而不是 Rational。如果我们保持理性,它看起来像:

2.0.0p195 :005 > Rational( current_freq * 2 ** Rational('1/12'))
 => (4100419809895505/8796093022208)

即使你这样做:

2.0.0p195 :010 > Rational(2) ** Rational(1,12)
 => 1.0594630943592953

Ruby 从 Rational 变为 float。Rational 上的 Ruby 文档没有清楚地描述这一点,但是当将 rational 转换为不是整数的分数指数时,给出的示例表明了这一点。这是有道理的,因为当你把一个有理数变成一个有理数(分数,非整数)指数时,你很可能会得到一个无理数。 就是其中之一。2**(1/12)

因此,为了保持准确性,您需要始终将所有内容都保持在理性领域中,一旦达到无理数,这实际上是不可能的。正如 Scott Hunter 所建议的那样,您可以使用一些自定义函数来缩小字段范围,以控制不准确性。目前尚不清楚在这种情况下是否值得付出努力。

评论

0赞 bodacious 1/9/2014
太好了 - 感谢您的解释。我认为,就我而言,它足够准确,可以将所有浮点数四舍五入到 6 点。正如他们所说,足够接近爵士乐。
0赞 Stefan 1/9/2014
2的第十二次方根是一个无理数,它不能表示为a/b。
0赞 lurker 1/9/2014
@Stefan确实如此,这是我的观点,但更笼统地说。
0赞 Stefan 1/9/2014 #3

要回答问题的第二部分:

如何改进上述代码以获得真正准确的结果?

您可以计算 f = 2n/12 × 440 的频率:

def freq(n)
  2 ** (n/12.0) * 440
end

puts (0..12).map { |n| freq(n) }

输出:

440.0
466.1637615180899
493.8833012561241
523.2511306011972
554.3652619537442
587.3295358348151
622.2539674441618
659.2551138257398
698.4564628660078
739.9888454232688
783.9908719634985
830.6093951598903
880.0