【问题标题】:Float precision when dealing with logarithms in musical pitch calculations在音高计算中处理对数时的浮点精度
【发布时间】:2021-12-18 06:40:38
【问题描述】:

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

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

非常感谢任何帮助!

【问题讨论】:

  • 你为什么在这里使用numpy???
  • 无论如何,是的,不要指望使用浮点数的精确值。请改用 decimal.Decimal 对象。虽然,如果你实际上需要numpy,那可能是个问题
  • 浮点数不能准确表示实数。实数的数字表示不可能每一个都无限精确,但即使是相当精确的标准浮点数也很难。您可以使用精度更高的数据类型(以及在其上定义的操作)或使用不会深入池中并保持足够精确以满足您的需求的算法。
  • @juanpa.arrivillaga 并且同样迂腐,我没有说过没有实数可以精确表示。 显然“0”是那个愚蠢陈述的反例。您的回答还忽略了这样一个事实,即实际上无法表示的实数比可以表示的实数要多得多——它们是一个不断减少的分数,因此您的评论毫无意义。
  • @juanpa.arrivillaga:关于“实际问题”:正如我们在答案中看到的,浮点舍入不是这里的实际问题。请不要仅仅因为某些问题涉及浮点运算就认为浮点舍入是问题所在。这不应该是对任何浮点问题的下意识评论。

标签: python floating-accuracy logarithm pitch


【解决方案1】:

这里的问题是浮点数使用一组位数来表示任何实数。由于 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) 对其进行任务,如果没有日志仍然在那里,似乎没有干净的方法来编写它 - 任何无理数的数字表示都会丢失精度。

【讨论】:

  • 或字符串,在这种情况下可能更容易,Decimal("0.1") + Decimal("0.1") + Decimal("0.1")
  • 确实 - 根据所需操作的复杂性,一个或另一个可能更快,但字符串肯定更具可读性,我同意这可能更重要。尤其是因为引入了Decimal,代码已经变得更难阅读了
  • 请注意,虽然 Decimal 确实允许比标准 float 更高的精度,但它仅提供分母中 10 次方的分数的精确表示。当然,它不能对logexpsin 等非代数函数给出准确的答案。
  • @dan4 - 是的,我添加了一个 OP 案例的示例以显示 Decimal 在此处的使用有限(如果有的话)。
  • 你不应该仅仅假设因为错误答案的计算使用了浮点数,这是由于浮点数舍入造成的。这个假设应该得到检验,很容易看出float 格式足够精确,1200*abs(log2(a/b)) 不会产生 700 分之二的差异。
【解决方案2】:

这是怎么回事?

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

如果您将 2^(4/12) 的等律主要第三频率比率输入到您的公式中,您确实会得到 400 美分的结果(在浮点精度范围内,正如其他答案和 cmets 所解释的那样)。

【讨论】:

    【解决方案3】:

    您遇到的问题不是关于 Python 的 float 类型的准确性,而是关于音乐中 equal temperamentjust intonation 之间的差异。

    >>> cent_difference(660, 440)
    701.9550008653874
    

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

    >>> cent_difference(659.2551138257398, 440)
    700.0
    

    【讨论】:

    • 啊,很好,很有趣 - 没意识到,谢谢 :)
    • 使用absnp.abs 的结果是相同的——我假设OP 尝试了np.abs,希望获得更高的准确性。但你的答案在这里是正确的——即使print(1200 * abs(math.log2(660 / 440))) 也非常准确。
    • 我感到非常愚蠢,因为我没有意识到这是问题所在,但这就是你在深夜编码时得到的结果!我对 Python 还很陌生,所以非常感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-10
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    相关资源
    最近更新 更多