I'm calculating programatically the frequencies of given musical notes.
Quick intro:
- The musical note A4 has a frequency of 440Hz
- Notes one octave higher are double the frequency of the same note in a lower octave (so A3 is 220Hz, A2 is 110Hz)
- The difference between one note, to the next semitone is log 2 to the base of a 12th.
C3 = B3 x 2 ^ (1/12)
Writing this formula in Ruby, I came up with the following:
# 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
The results I'm getting back are not perfect though. The A notes seem to have rounded a little higher than expected:
-----
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
Note the A frequencies - instead of being 880, 1760, they are slightly higher.
I thought Ruby's Rational was supposed to give accurate calculations and avoid the rounding errors from using floats.
Can anybody explain:
- Why is this result inaccurate?
- How can I improve the above code to obtain a truly accurate result?
It's not clear to me whether in this expression:
current_freq * 2 ** Rational('1/12')
that Ruby is keeping the entire calculation in the Rational realm. Within Ruby, you get:The calculation produces a float, not a Rational. If we kept it Rational, it would look like:
Even if you do this:
Ruby goes from Rational to float. The Ruby doc on Rational doesn't describe this clearly, but the examples given show this when taking a rational to a fractional exponent that's not an integer. This makes sense, since when you take a rational number to a rational (fractional, non-integer) exponent, chances are you're going to get an irrational number.
2**(1/12)
is one of those cases.So to keep accuracy, you'd need to keep everything in the Rational realm throughout which isn't really possible once you hit an irrational number. You might, as Scott Hunter suggests, be able to narrow the field with some custom functions to control the inaccuracy. It's unclear whether that would be worth the effort in this situation.