在音高计算中处理对数时的浮点精度

Float precision when dealing with logarithms in musical pitch calculations

我正在编写一个简单的程序来确定两个音高之间的差异;一音分等于半音的 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

...但在完美的五分之一上差了大约两美分:

In [29]: cent_difference(660, 440)
Out[29]: 701.9550008653875

...并且随着我的前进变得越来越糟,在大三分之一上损失了大约 14 美分:

In [30]: cent_difference(550, 440)
Out[30]: 386.31371386483477

这都是浮点精度废话吗?为什么完美的第五个例子高估了美分,而主要的第三个例子低估了美分?这是怎么回事?

非常感谢您的帮助!

这里的问题是浮点数使用一组位数来表示任何实数。由于其中有无限多个并且 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

所以,没什么不同。我不确定你对那里的第二个结果有什么期望。如果您跳转到 Wolfram Alpha 并使用 1200 * log2(660 / 440) 对其进行任务,似乎没有干净的方法可以在没有日志的情况下编写它 - 对于无理数的任何数字表示,精度都会丢失。

What's going on here?

You're inputting frequency intervals in just intonation and expecting results in equal temperament..

如果你将 2^(4/12) 的平均大三度频率比率输入你的公式,你确实会得到 400 美分的结果(在浮点精度内,如其他答案和评论所解释的那样).

您遇到的问题不是关于Python的float类型的准确性,而是关于[=13]之间的差异=] 在音乐中。

>>> cent_difference(660, 440)
701.9550008653874

这是假设 P5 间隔表示频率比为 3/2。但在 12-ET 中,它不是:它的比率为 27/12 ≈ 1.4983070768766815。使用较高音符的正确 ET 值,您确实会得到预期的 700。

>>> cent_difference(659.2551138257398, 440)
700.0