简体   繁体   English

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

[英]Float precision when dealing with logarithms in musical pitch calculations

I'm writing a simple program to determine the difference between two musical pitches in cents;我正在编写一个简单的程序来确定两个音高之间的差异(以美分为单位); one cent is equal to 1/100th of a semitone.一分等于半音的 1/100。 Dealing in cents is preferable for comparing musical pitches because the frequency scale is logarithmic, not linear.由于频率标度是对数的,而不是线性的,因此最好使用美分来比较音高。 In theory, this is an easy calculation: the formula for determining the number of cents between two frequencies is:理论上,这是一个简单的计算:确定两个频率之间的分数的公式是:

1200 * log2(pitch_a / pitch_b)

I've written a small piece of code to automate this process:我写了一小段代码来自动化这个过程:

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

This works perfectly when I give the program octaves:当我给程序八度音程时,这非常有效:

In [28]: cent_difference(880, 440)
Out[28]: 1200.0

...but misses the mark by about two cents on a perfect fifth: ......但在完美的五分之一上错过了大约两美分:

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

...and keeps getting worse as I go, missing by about 14 cents on a major third: ......并且随着我的前进而变得越来越糟,在主要三分之一上丢失了大约 14 美分:

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

Is this all float precision nonsense?这都是浮点精度的废话吗? Why does the perfect 5th example overestimate the cents, but the major third example underestimate the cents?为什么完美的第五个例子高估了美分,而主要的第三个例子低估了美分? What's going on here?这里发生了什么?

Much obliged for any help!非常感谢任何帮助!

The issue you're having isn't about the accuracy of Python's float type, but about the discrepancy between equal temperament and just intonation in music.您遇到的问题在于 Python 的float类型的准确性,而在于音乐中的平等气质音调之间的差异。

>>> cent_difference(660, 440)
701.9550008653874

This is assuming that a P5 interval represents a frequency ratio of 3/2.这是假设 P5 间隔代表 3/2 的频率比。 But in 12-ET, it doesn't: It has a ratio of 2 7/12 ≈ 1.4983070768766815.但在 12-ET 中,它没有:它的比率为 2 7/12 ≈ 1.4983070768766815。 With the proper ET value for the higher note, you do get the expected 700.使用适当的高音 ET 值,您确实会得到预期的 700。

>>> cent_difference(659.2551138257398, 440)
700.0

The problem here is that floating point numbers use a set number of bits to represent any of the real numbers.这里的问题是浮点数使用一组位数来表示任何实数。 Since there's infinitely many of those and only 2**32 values for a 32-bit float (at best), you can see how there will effectively be infinitely many reals that will have to be approximated.由于 32 位浮点数(充其量)有无限多个这些值,并且只有 2**32 个值,因此您可以看到将如何有效地逼近无限多个实数。 And if you keep computing with those approximations, errors occur.如果您继续使用这些近似值进行计算,就会发生错误。

You don't have to use large or long numbers to run into one either.您也不必使用大数或长数来遇到一个。 My favourite:我的最爱:

>>> .1 + .1 + .1
0.30000000000000004

You can use more accurate types, which use better representations at the cost of some speed (and sometimes use operations that are slower but less likely to introduce errors).您可以使用更准确的类型,以牺牲一些速度为代价使用更好的表示(有时使用速度较慢但不太可能引入错误的操作)。

For example Decimal , but make sure you use integers to define them:例如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')

The best solution, if one exists for your problem, is to avoid floating point math altogether.如果您的问题存在,最好的解决方案是完全避免浮点数学。

By the way, here's your problem using Decimal :顺便说一句,这是您使用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))

Result:结果:

1200
701.95500086538741774000

So, not that different.所以,没有那么不同。 I'm not sure what result you expected on the second one there.我不确定您对那里的第二个结果的期望。 If you pop over to Wolfram Alpha and task it with 1200 * log2(660 / 440) , there seems to be no clean way to write this without the log still in there - precision will be lost for any representation in digits of an irrational number.如果您跳到 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. 您正在以正确的语调输入频率间隔,并期望以相同的气质获得结果。 . .

If you feed the equal-tempered major third frequency ratio of 2^(4/12) into your formula, you indeed get a result of 400 cents (within floating point accuracy, as explained by the other answers and comments).如果您将 2^(4/12) 的等温大三度频率比输入您的公式,您确实会得到 400 美分的结果(在浮点精度范围内,如其他答案和评论所述)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM