Ошибка разделения десятичных знаков в C++

Я пытаюсь извлечь 20 десятичных знаков из переменной, но должна быть ошибка с операцией деления, так как эта программа дает мне неправильный результат:

#include <iostream>
#include <cmath>
using namespace std;
int fracpart(long double input)
{
    long long I;
    I = input * 10;
    return I % 10;
}

int main()
{
    int n = 9, m = 450;
    long double S;
    S = (long double)n/m;
    for(int i=1; i<=20; i++){
        cout << fracpart(S) << " ";
        S *= 10;
    }
    return 0;
}

Что я получаю:

0 1 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9

Что я должен получить:

0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

2 ответа

Вы можете проверить базу, используемую при представлении типов с плавающей точкой, проверяя значение макропостоянной FLT_RADIX (определено в заголовке <cfloat>). Как вы, вероятно, уже знаете, двоичная система используется большинством современных компьютеров, а не десятичной.

Теперь рассмотрим рациональное число, такое как 1/3. Он не может быть представлен конечным числом цифр в базе 10, в результате вы получите некоторое приближение, например, 0,3333333 и допустимую ошибку. Обратите внимание, что это же число может быть представлено в базовой системе 3 с конечным числом цифр (0,1).

Число, которое вы пытаетесь напечатать, 9/450, имеет "красивое" базовое 10 представление, 0,02, но оно не может быть представлено в базовой 2 с абсолютной точностью, даже если деление может быть выполнено без добавления какой-либо ошибки. Не вводите в заблуждение это "2", учтите, что 0,02 = 2/100 = 1/50 = 1 / (2 * 52), где 1/5 может быть только приближена, в основании 2.

В любом случае, есть методы для достижения того, что вы хотите, например, используя манипуляторы вывода std::setprecision а также std::fixed (определено в заголовке <iomanip>) или даже написание (действительно некрасивой) пользовательской функции. Посмотрите на вывод этой программы:

#include <iostream>
#include <cmath>
#include <iomanip>
#include <vector>
#include <cstdint>

// splits a number into its integral and fractional (a vector of digits) parts
std::vector<uint8_t> to_digits (
    long double x, uint8_t precision, long double &integral
);

// Reconstructs the approximated number
long double from_digits (
     long double integral_part, std::vector<uint8_t> &fractional_part
);

int main()
{
    using std::cout;

    int n = 9, m = 450;
    long double S;
    S = static_cast<long double>(n)/m;

    cout << "\nBase 10 representation of calculated value:\n"
         << std::setprecision(70) << S << '\n';
    // This ^^^^^^^^^^^^^^^^^^^^^  will change only how the value is
    // printed, not its internal binary representation

    cout << "\nBase 10 representation of literal:\n"
         << 0.02L << '\n';
    // This ^^^^^ will print the exact same digits

    // the next greater representable value is a worse approximation
    cout << "\nNext representable value:\n"
         << std::nextafter(S, 1.0) << '\n';

    // but you can try to obtain a "better" output
    cout << "\nRounded representation printed using <iomanip> functions:\n"
         << std::setprecision(20) << std::fixed << S << '\n';

    cout << "\nRounded fractional part printed using custom function:\n";
    long double integral_part;
    auto dd = to_digits(S, 20, integral_part);
    for (auto const d : dd)
    {
        cout << static_cast<int>(d);
    }
    cout << '\n';

    // Reversing the process...
    cout << "\nApproximated value (using custom function):\n";
    auto X = from_digits(integral_part, dd);
    cout << std::setprecision(70) << std::fixed << X << '\n';
    cout << std::setprecision(20) << std::fixed << X << '\n';
}

std::vector<uint8_t> to_digits (
    long double x, uint8_t precision, long double &integral
)
{
    std::vector<uint8_t> digits;

    long double fractional = std::modf(x, &integral);

    for ( uint8_t i = 0; i < precision; ++i )
    {
        long double digit;
        fractional = std::modf(fractional * 10, &digit);
        digits.push_back(digit);
    }

    if ( digits.size()  &&  std::round(fractional) == 1.0L )
    {
        uint8_t i = digits.size();
        while ( i )
        {
            --i;
            if ( digits[i] < 9 )
            {
                ++digits[i];
                break;
            }
            digits[i] = 0;
            if ( i == 0 )
            {
                integral += 1.0L;
                break;
            }
        }
    }

    return digits;
}

long double from_digits (
     long double integral_part, std::vector<uint8_t> &fractional_part
)
{
    long double x = 1.0L;
    for ( auto d : fractional_part )
    {
        x *= 10.0L;
        integral_part += d / x;
    }

    return integral_part;
}

Я думал, что это происходит "потому что двоичное деление не совсем понятно десятичным числам", но Bob__ был прав! Проблема возникает потому, что длинная длинная переменная является "проблематичной". Итак, я просто изменил код и использовал упомянутые выше функции ceil и round. На этот раз я протестировал код, поэтому надеюсь, что он удовлетворит ваши потребности.

PS1: извлечь функцию было действительно необходимо.

PS2: не забудьте включить библиотеку math.h

PS3: И извините за задержку с ответом.

#include <iostream>
#include <cmath>
#include <math.h>
using namespace std;

int main()
{
    int n = 9, m = 450;
    long double S;
    S = (long double)n/m;
    for(int i=1; i<=20; i++){
        cout << fmod(round(fmod(S * 10,10)), 10) << " ";
        S *= 10;
    }
    return 0;
}

Вот несколько примеров: http://www.cplusplus.com/reference/cmath/trunc/

Другие вопросы по тегам