我正在写一个简单的程序来确定两个音高之间的差异(以美分为单位(;1美分等于半音的1/100。在比较音高时,以美分为单位更可取,因为频率刻度是对数的,而不是线性的。理论上,这是一个简单的计算:确定两个频率之间的美分数的公式是:
1200 * log2(pitch_a / pitch_b)
我写了一小段代码来自动化这个过程:
import numpy as np
import math
def cent_difference(pitch_a, pitch_b)
cents = 1200 * np.abs(math.log2(pitch_a / pitch_b))
return cents
当我给程序八度音阶时,这非常有效:
In [28]: cent_difference(880, 440)
Out[28]: 1200.0
但在完美的第五局中以大约2美分的差距错过了目标:
In [29]: cent_difference(660, 440)
Out[29]: 701.9550008653875
随着我的进步,情况越来越糟,在主要的三分之一上少了大约14美分
In [30]: cent_difference(550, 440)
Out[30]: 386.31371386483477
这都是浮点精度的无稽之谈吗?为什么完美的第五个例子高估了美分,而主要的第三个例子低估了美分?这是怎么回事?
非常感谢您的帮助!
您遇到的问题不是关于Python的float
类型的准确性,而是关于音乐中平等气质和语调之间的差异。
>>> cent_difference(660, 440)
701.9550008653874
这是假设P5间隔表示3/2的频率比。但在12-ET中,它没有:它的比率为27/12≈1.4983070768766815。有了较高音符的适当ET值,你就可以得到预期的700。
>>> cent_difference(659.2551138257398, 440)
700.0
这里发生了什么?
你只是在语调中输入频率间隔,并期望以相同的气质获得结果。。
如果你在公式中加入2^(4/12(的等调和主音第三频率比,你确实得到了400美分的结果(如其他答案和评论所解释的,在浮点精度范围内(。
这里的问题是浮点数使用一组位数来表示任何实数。由于其中有无限多个,而32位浮点值(最多(只有2**32个值,您可以看到如何有效地有无限多的实数需要近似。如果你一直用这些近似值计算,就会出现错误。
你也不必使用大的或长的数字来计算一个。我的最爱:
>>> .1 + .1 + .1
0.30000000000000004
您可以使用更准确的类型,这些类型以一定的速度为代价使用更好的表示(有时使用较慢但不太可能引入错误的操作(。
例如Decimal
,但请确保使用整数来定义它们:
>>> .1 + .1 + .1
0.30000000000000004
>>> from decimal import Decimal
>>> Decimal(.1) + Decimal(.1) + Decimal(.1)
Decimal('0.3000000000000000166533453694')
>>> Decimal (1)/Decimal(10) + Decimal(1)/Decimal(10) + Decimal(1)/Decimal(10)
Decimal('0.3')
如果你的问题有一个最好的解决方案,那就是完全避免浮点运算。
顺便说一句,这是你使用Decimal
:的问题
from decimal import Decimal, Context
def cent_difference(pitch_a, pitch_b, ctx):
ratio = ctx.divide(pitch_a, pitch_b)
cents = Decimal(1200) * ctx.copy_abs(ratio.ln(ctx) / Decimal(2).ln(ctx))
return cents
ctx = Context(prec=20)
print(cent_difference(Decimal(880), Decimal(440), ctx))
print(cent_difference(Decimal(660), Decimal(440), ctx))
结果:
1200
701.95500086538741774000
所以,没什么不同。我不确定你对第二场比赛的预期结果是什么。如果你跳到WolframAlpha并用1200 * log2(660 / 440)
进行任务,如果日志不在那里,似乎就没有干净的方法来写这篇文章——无理数的任何数字表示都会丢失精度。