2

下面的类应该代表一个音符。我希望能够仅使用整数来存储音符的长度(例如 1/2 音符、1/4 音符、3/8 音符等)。但是,我还希望能够使用浮点数存储长度,以应对我处理不规则长度音符的罕见情况。

class note{
    string tone;
    int length_numerator;
    int length_denominator;
public:
    set_length(int numerator, int denominator){
        length_numerator=numerator;
        length_denominator=denominator;
    }
    set_length(double d){
        length_numerator=d; // unfortunately truncates everything past decimal point
        length_denominator=1;
    }
}

能够使用整数而不是双精度数来存储长度对我来说很重要的原因是,在我过去使用浮点数的经验中,有时值出乎意料地不准确。例如,一个应该是 16 的数字偶尔会被神秘地存储为 16.0000000001 或 15.99999999999(通常在经历一些操作之后),这可能会在测试相等性时引起问题(因为 16!=15.99999999999)。

是否可以将变量从 int 转换为 double (变量,而不仅仅是它的值)?如果不是,那么我还能做些什么来使用整数或双精度来存储音符的长度,具体取决于我需要的类型是什么?

4

6 回答 6

4

If your only problem is comparing floats for equality, then I'd say to use floats, but read "Comparing floating point numbers" / Bruce Dawson first. It's not long, and it explains how to compare two floating numbers correctly (by checking the absolute and relative difference).

When you have more time, you should also look at "What Every Computer Scientist Should Know About Floating Point Arithmetic" to understand why 16 occasionally gets "mysteriously" stored as 16.0000000001 or 15.99999999999.

Attempts to use integers for rational numbers (or for fixed point arithmetic) are rarely as simple as they look.

于 2011-08-11T07:01:27.953 回答
3

我看到了几种可能的解决方案:第一个只是使用双精度。确实,扩展计算可能会导致结果不准确,但在这种情况下,除数通常是 2 的幂,这将给出准确的结果(至少在我见过的所有机器上);只有在除以一些不寻常的值时才会遇到问题(在这种情况下,无论如何你都必须使用 double )。

您还可以缩放结果,例如将音符表示为 64 分音符的倍数。这将意味着大多数值将是小整数,保证精确double(同样,至少在通常的表示中)。应该是 16 的数字不会 存储为 16.000000001 或 15.99999999(但应该是 0.16 的数字可能会存储为 .1600000001 或 .1599999999)。在 出现之前long long,十进制算术类通常用作double52 位整数类型,以确保在每一步中实际值都是整数。(只有除法可能会导致问题。)

或者您可以使用某种表示有理数的类。(例如,Boost 有一个,我相信还有其他的。)这将允许任何奇怪的值(第五个音符,有人吗?)保持准确;它对于人类可读的输出也可能是有利的,例如,您可以测试分母,然后输出诸如“3 个四分音符”之类的东西。甚至像“3/4 音符”这样的东西对于音乐家来说也比“0.75 音符”更易读。

于 2011-08-11T07:57:01.787 回答
2

It is not possible to convert a variable from int to double, it is possible to convert a value from int to double. I'm not completely certain which you are asking for but maybe you are looking for a union

union DoubleOrInt
{
  double d;
  int i;
};

DoubleOrInt length_numerator;
DoubleOrInt length_denominator;

Then you can write

set_length(int numerator, int denominator){
    length_numerator.i=numerator;
    length_denominator.i=denominator;
}
set_length(double d){
    length_numerator.d=d;
    length_denominator.d=1.0;
}

The problem with this approach is that you absolutely must keep track of whether you are currently storing ints or doubles in your unions. Bad things will happen if you store an int and then try to access it as a double. Preferrably you would do this inside your class.

于 2011-08-11T07:02:05.443 回答
0

This is normal behavior for floating point variables. They are always rounded and the last digits may change valued depending on the operations you do. I suggest reading on floating points somewhere (e.g. http://floating-point-gui.de/) - especially about comparing fp values.

I normally subtract them, take the absolute value and compare this against an epsilon, e.g. if (abs(x-y)

于 2011-08-11T07:02:06.953 回答
0

如果是我,我只会使用枚举。使用该系统也可以将某些东西变成笔记非常简单。这是您可以做到的一种方法:

class Note {
public:
    enum Type {
        // In this case, 16 represents a whole note, but it could be larger
        // if demisemiquavers were used or something.
        Semiquaver = 1,
        Quaver = 2,
        Crotchet = 4,
        Minim = 8,
        Semibreve = 16
    };


    static float GetNoteLength(const Type &note) 
        { return static_cast<float>(note)/16.0f; }

    static float TieNotes(const Type &note1, const Type &note2)
        { return GetNoteLength(note1)+GetNoteLength(note2); }
};

int main()
{
    // Make a semiquaver
    Note::Type sq = Note::Semiquaver;
    // Make a quaver
    Note::Type q = Note::Quaver;
    // Dot it with the semiquaver from before
    float dottedQuaver = Note::TieNotes(sq, q);

    std::cout << "Semiquaver is equivalent to: " << Note::GetNoteLength(sq) << " beats\n";
    std::cout << "Dotted quaver is equivalent to: " << dottedQuaver << " beats\n";
    return 0;
}

您所说的那些“不规则”笔记可以使用TieNotes

于 2011-08-11T07:39:11.747 回答
0

鉴于你有一个set_length(double d),我猜你实际上需要双打。请注意,从 double 到整数分数的转换是脆弱而复杂的,并且很可能无法解决您的相等问题(0.24999999 等于 1/4 吗?)。你最好选择总是使用分数,或者总是加倍。然后,只需学习如何使用它们。我必须说,对于音乐来说,分数是有意义的,因为它甚至是描述音符的方式。

于 2011-08-11T07:13:01.387 回答