在音高计算中处理对数时的浮点精度
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
我正在编写一个简单的程序来确定两个音高之间的差异;一音分等于半音的 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