Супер странная C ++ черная дыра в int и float - PullRequest
0 голосов
/ 22 апреля 2011

По сути, я пытаюсь ввести значение в консоль и вывести десятичную точку как целое число, и это то, что должно произойти.

Я разработал способ сделать это, используя float, инт и простые математики.Я все еще новичок в C ++, но эта ошибка не имеет смысла.

Если вы введете 0,01, 0,02, 0,03, 0,04, 0,06 или 0,08, вы получите неправильный вывод.Я просто хочу, чтобы это было так просто, как 0,06 * 100 = 6.

Я почти уверен, что это простая ошибка, но почему это так, когда я в любом случае ввожу целое число с плавающей точкой?

#include <iostream>
using namespace std;

int main()
{
    float input = 0;

    while (input <= 0 || input > 999.99)
    {
        cout << "Please enter a number with decimal: ";
        cin >> input;
    }

    int whole_num = input;
    float to_decimal = input - whole_num;
    int decimal = to_decimal * 100;

    cout << decimal << endl;

    return 0;
}

РЕДАКТИРОВАТЬ : я нашел решение для моей проблемы


Возникла проблема с точностью с плавающей точкой.Пока добавление 0.5f к int может решить проблему.Я знаю, что он делает это правильно для ввода 2 десятичных знаков, не уверен для других типов.

Благодаря Фредерику Слайкерману!

#include <iostream>
using namespace std;

int main ()
{
float asfloat = 0.03;
int asint = asfloat * 100;
int asint_fix = 0.5f + asfloat * 100;
cout << "0.03 * 100 = " << asint << endl;
cout << "0.03 * 100 (with the +0.5f fix) = " << asint_fix << endl;
return 0;
}

Возвращает:

0.03 * 100 = 2
0.03 * 100 (with the +0.5f fix) = 3

Ответы [ 7 ]

2 голосов
/ 22 апреля 2011

Это потому, что числа с плавающей запятой не могут точно представлять десятичные числа.

Формат чисел с плавающей запятой, используемый вашим компьютером, является двоичным. Это означает, что он может точно представлять 1/2, 1/4, 1/8, 1/16, ... и их комбинации. Таким образом, вы можете сказать 0,5, или 0,25, или даже 0,75 (0,5 + 0,25), и они будут точными с плавающей запятой. Но 0.01 не может быть создан с комбинациями этих фракций; следовательно, его значение является приблизительным. Аналогичная история с другими номерами, которые вы тестировали.

Это неотъемлемое ограничение при использовании двоичной плавающей запятой. Это не "супер странно"; это с плавающей точкой 101 . : -)

1 голос
/ 22 апреля 2011

Я просто хочу обратить ваше внимание, что вы теряете точность при сохранении результата float в int в строке int decimal = to_decimal * 100; Если вы объявите его как float decimal = to_decimal * 100, тогда он должен работать для вас.

1 голос
/ 22 апреля 2011

Я бы добавил к ответу Криса, что это классический источник ошибок в научных вычислениях. Поскольку действительные значения не могут быть точно представлены (точность в компьютере конечна), вы накапливаете ошибки округления по ходу ваших вычислений. Это очень серьезная проблема, если вы долго вычисляете траектории, например, для спутников.

Таким образом, существуют инструменты статического анализа (такие как Astrée ), которые помогают вам определить, когда такие проблемы могут вызвать проблемы в вашем коде, или гарантировать, что вы в безопасности.

В общем, это не "очень странно", но, безусловно, "очень неудачно".

В вашем конкретном случае, может быть, использование double вместо float может помочь, это увеличит точность двоичного представления вашего числа.

0 голосов
/ 22 апреля 2011

Прочитайте Руководство с плавающей точкой .Это то, что должен знать каждый программист при выполнении математических операций с плавающей точкой.

0 голосов
/ 22 апреля 2011

Собственная проблема с точностью с плавающей точкой усиливается округлением усечения по умолчанию в C и C ++, которое округляется, например От 0.999999 до 0. Вы можете сделать вещи более надежными, округляя вместо усечения:

int decimal = 0.5f + to_decimal * 100;

Вы также можете использовать небольшое значение, например 1e-6, вместо 0,5, чтобы получить более надежное усечение. Все зависит от вашей конкретной ситуации.

0 голосов
/ 22 апреля 2011

Вам нужно решить, сколько значащих битов вас интересует (похоже, 2), после того, как вы это сделаете, это лучшее, что вы можете сделать .:

const int significantBits = 2;
int decimal = (int)(pow(10, significantBits) * div(input, 1.0));
0 голосов
/ 22 апреля 2011

Как уже упоминалось, проблема в том, что числа с плавающей запятой в C ++ соответствуют стандарту IEEE 754 , который, к сожалению, не может точно представлять общие числа, такие как 0,01, 0,03 и т. Д. (Вы можете проверить с простым циклом типа for (float f=0.0; f<=1.0; f+=0.1) printf("%.010f\n",f); и посмотрите, как быстро накапливается ошибка.)

Однако вы часто можете обойти такие проблемы, используя целые числа и деление / умножение для ввода / вывода. Также может помочь GNU GMP Произвольная прецизионная арифметическая библиотека .

...