Функция cmath std::pow дает неправильное значение при назначении переменной?

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

void build_prob_distro(const std::vector<Foo>& num_sets, std::map<int, int>& prob_distro){
    int key;
    Foo cur_foo;

    for(unsigned int foo_num = 0; foo_num<num_sets.size(); foo_num++){
        cur_foo = num_sets.at(foo_num);
        key = 0;
        int val;
        for(int cur_foo_num=0; cur_foo_num<cur_foo.get_foo_length(); cur_foo_num++){
            std::cout << cur_foo.get_num_at(cur_foo_num)*std::pow(10, cur_foo.get_foo_length()-cur_foo_num-1) << std::endl;
            val = cur_foo.get_num_at(cur_foo_num)*std::pow(10, cur_foo.get_foo_length()-cur_foo_num-1);
            std::cout << val << std::endl;
            key = key + cur_foo.get_num_at(cur_foo_num)*std::pow(10, cur_foo.get_foo_length()-cur_foo_num-1);
        }

        prob_distro[key] += 1;
    }
}

Проблема, с которой я сталкиваюсь, заключается в том, что когда я использую метод std::pow() для вычисления значения ключа для моей карты, все, что больше 100, отключается на -1 (т.е. 100 становится 99, 103 становится 102 и т. д.). Когда я распечатываю расчет с помощью std::cout, результат правильный, но как только я присваиваю значение переменной int, я получаю ошибку -1. Я просматривал код снова и снова и не вижу в нем ничего плохого. Любые предложения о том, что может вызвать эту проблему и почему?

Я не считаю, что класс foo слишком важен для этого примера/проблемы, но я опубликую его на всякий случай, если он действительно является причиной какой-то проблемы.

//Foo.h
#ifndef FOO_H
#define FOO_H

#include <string>
#include <vector>

class Foo
{
    public:
        Foo();
        Foo(const std::vector<int>& nums);
        int get_num_at(int pos) const;
        int get_foo_length() const;
        std::string to_string() const;
    private:
        std::vector<int> nums;

};

#endif // Foo_H


//Foo.cpp
#include "Foo.h"

#include <string>

Foo::Foo(const std::vector<int>& nums){
    for(int i=0; i<nums.size(); i++){
        this->nums.push_back(nums.at(i));
    }
}

Foo::Foo(){}


/*       SETTERS & GETTERS           */

int Foo::get_num_at(int pos) const{
    if(nums.size() != 0){
        return nums[pos];
    }

    return -1;
}

int Foo::get_foo_length() const{
    return nums.size();
}

/*       END SETTERS & GETTERS         */


std::string Foo::to_string() const{}

РЕДАКТИРОВАТЬ: я знаю, что некоторые сразу укажут на использование чего-то более простого, чем класс Foo в векторе, но у меня есть другие функции, которые мне нужно включить в каждый набор, так что это был лучший способ, который я мог придумать, чтобы сохранить мой связанный код вместе и разрешить ему представлять любое целочисленное значение длины, которое мне было бы интересно (т. е. foo может представлять 1 так же легко, как и 10000).


person MrJman006    schedule 24.12.2013    source источник
comment
Вы, вероятно, получаете ошибки округления. pow() может дать неточные результаты, а преобразование double в int усекает значение, поэтому преобразование 99.99999 в int дает 99. Я серьезно сомневаюсь, что вам нужно использовать арифметику с плавающей запятой для всего, что вы делаете.   -  person Keith Thompson    schedule 24.12.2013
comment
У меня есть подозрение, что это связано с преобразованием int/double внутри pow, но что меня действительно беспокоит, так это то, что std::cout ‹‹ cur_foo.get_num_at(cur_foo_num)*std::pow(10, cur_foo.get_foo_length( )-cur_foo_num-1) ‹‹ std::endl; показывает правильное значение.   -  person MrJman006    schedule 24.12.2013
comment
@ MrJman006 По умолчанию вывод округляется. Добавьте << std::setprecision(20) << val прямо перед выводом значения и посмотрите, правильно ли оно :)   -  person Daniel Frey    schedule 24.12.2013
comment
@KeithThompson Привет, Кит, я просто комментировал, когда вы опубликовали, и я согласен, что это, вероятно, проблемы с округлением, но почему оператор std::cout отображается правильно?   -  person MrJman006    schedule 24.12.2013
comment
@DanielFrey Привет, Даниэль, я только что попробовал, и никаких изменений. в моей строке std::cout с выражением.   -  person MrJman006    schedule 24.12.2013
comment
@MrJman006 MrJman006 Возможно, оно все еще округлено, попробуйте вывести значение по сравнению с ожидаемым значением (==100) и посмотрите, возвращает ли оно значение true или false.   -  person Daniel Frey    schedule 24.12.2013
comment
@DanielFrey Когда я вычисляю (выражение == 100), я получаю ложное значение, чего вы и ожидали.   -  person MrJman006    schedule 24.12.2013
comment
@DanielFrey Не могли бы вы указать мне направление ресурсов, которые я мог бы просмотреть, чтобы лучше разобраться с компьютерной математикой с плавающей запятой? Мне нужны точные вычисления, но я не очень хорошо знаком с этими странными причудами округления и представления.   -  person MrJman006    schedule 24.12.2013
comment
@MrJman006 Ваше путешествие начинается здесь.   -  person Daniel Frey    schedule 24.12.2013
comment
[OT]: cur_foo должна быть (константной) ссылкой, чтобы избежать копирования Foo.   -  person Jarod42    schedule 24.12.2013
comment
@DanielFrey Спасибо. Я уверен, что это будет долгий путь :D   -  person MrJman006    schedule 25.12.2013


Ответы (2)


Вы, вероятно, получаете ошибки округления,

поэтому вы можете попробовать std::lround как:

key += cur_foo.get_num_at(cur_foo_num) * std::lround(std::pow(10, cur_foo.get_foo_length() - cur_foo_num - 1));

или напишите свою собственную функцию pow_int, чтобы не использовать float:

constexpr int pow_int(int x, unsigned int n)
{
    // x ** (2n + 1) == ((x * x) ** n) * x
    // x ** 2n == (x * x) ** n
    // x ** 0 == 1
    return (((n >> 1) == 0) ? 1 : pow_int(x * x, n >> 1)) * (((n & 1) == 0) ? 1 : x);
}

или (линейная версия)

int pow_int(int x, unsigned int n)
{
    int res = 1;

    for (unsigned int i = 0; i != n; ++i) {
        res *= x;
    }
    return res;
}
person Jarod42    schedule 24.12.2013
comment
Это на самом деле то, что я в конечном итоге сделал. Я написал свою собственную функцию int power. Моя была очень похожа на последнюю реализацию. - person MrJman006; 25.12.2013

Пытаться

key = key + cur_foo.get_num_at(cur_foo_num)*std::pow(10, cur_foo.get_foo_length()-cur_foo_num-1) + 0.5;
person Violet Giraffe    schedule 24.12.2013
comment
Вы можете использовать std::lround(d) (С++ 11) вместо трюка int(d + 0.5). - person Jarod42; 24.12.2013