Информация об изменениях

Сообщение Re[3]: использование round от 11.02.2018 22:12

Изменено 11.02.2018 22:18 Constructor

Re[3]: использование round
Здравствуйте, B0FEE664, Вы писали:

R>>Здесь фишка в том, что у std::round тип возвращамого значения тот же, что и у параметра. Поэтому явный каст к нужному типу вполне приемлем, я считаю:

R>>
R>>int value = int(std::round(3.14f));
R>>


BFE>А гарантия того, что в value будет 3, а не 2 есть?


Только для относительно небольших чисел. (1ull << std::numeric_limits<Real>::digits) + 1 — первое целое число, которое не имеет точного представления в вещественном типе Real. К этому и подобным ему числам значения вещественного типа не смогут округлиться.

Для float число значащих цифр мантиссы равно 23 + 1 = 24, поэтому пограничное "хорошее" число (которым завершается непрерывная последовательность целых чисел, представимых типом float) будет равно 16.777.216.
Для double число значащих цифр в мантиссе — 52 + 1 = 53, пограничное число — 9.007.199.254.740.992.

Проверочный код:

#include <iostream>
#include <iomanip>
#include <type_traits>
#include <limits>
#include <string>
#include <locale>


class number_punctuation_with_digit_separators final : public std::numpunct<char>
{
protected:
    virtual char do_thousands_sep() const override
    {
        return '.';
    }
    
    virtual std::string do_grouping() const override
    {
        return "\03";
    }
};


template <typename Real, typename Integer>
void print_numbers_near_real_boundary_value(
    const std::make_signed_t<Integer> limit, const std::string& real_name, const std::string& integer_name)
{
    constexpr auto real_digits = std::numeric_limits<Real>::digits;
    constexpr auto integer_digits = std::numeric_limits<Integer>::digits;
    
    static_assert(real_digits + 1 <= integer_digits, "The 'Integer' type is too short to hold the 'Real' boundary value.");
    
    const auto print_type_line = [](const auto& type_category, const auto& type_name, const auto& digits)
    {
        std::cout
            << std::left << std::setw(10) << type_category
            << std::setw(12) << type_name
            << std::right << std::setw(9) << digits << std::endl;
    };
    
    print_type_line("Type", "Type name", "Digits");
    print_type_line("Real", real_name, real_digits);
    print_type_line("Integer", integer_name, integer_digits);
    std::cout << std::endl;
    
    const auto print_number_line = [](const auto& shift, const auto& initial_integer, const auto& real,
        const auto& transformed_integer, const auto& are_equal)
    {
        std::cout
            << std::setw(5) << shift
            << std::setw(24) << initial_integer
            << std::setw(24) << real
            << std::setw(24) << transformed_integer
            << std::setw(9) << are_equal << std::endl;
    };
    
    constexpr auto boundary_value = static_cast<Integer>(1) << real_digits;
        
    print_number_line("Shift", "Initial integer", "Real", "Transformed integer", "Equal?");
    for (auto shift = - limit; shift <= limit; ++shift)
    {
        const auto initial_integer = boundary_value + shift;
        const auto real = static_cast<Real>(initial_integer);
        const auto transformed_integer = static_cast<Integer>(real);
        const auto are_equal = (initial_integer == transformed_integer) ? "yes" : "no";
        
        print_number_line(shift, initial_integer, real, transformed_integer, are_equal);
    }
    std::cout << std::endl << std::endl;
}

#define PRINT_NUMBERS_NEAR_REAL_BOUNDARY_VALUE(Real, Integer, limit)     print_numbers_near_real_boundary_value<Real, Integer>(limit, #Real, #Integer)

int main()
{
    std::cout.imbue(std::locale(std::locale::classic(), new number_punctuation_with_digit_separators));
    std::cout << std::fixed << std::setprecision(0);
    
    constexpr auto limit = 10;
    
    PRINT_NUMBERS_NEAR_REAL_BOUNDARY_VALUE(float, std::int32_t, limit);
    PRINT_NUMBERS_NEAR_REAL_BOUNDARY_VALUE(double, std::int64_t, limit);
}


Вывод:

Type      Type name      Digits
Real      float              24
Integer   std::int32_t       31

Shift         Initial integer                    Real     Transformed integer   Equal?
  -10              16.777.206              16.777.206              16.777.206      yes
   -9              16.777.207              16.777.207              16.777.207      yes
   -8              16.777.208              16.777.208              16.777.208      yes
   -7              16.777.209              16.777.209              16.777.209      yes
   -6              16.777.210              16.777.210              16.777.210      yes
   -5              16.777.211              16.777.211              16.777.211      yes
   -4              16.777.212              16.777.212              16.777.212      yes
   -3              16.777.213              16.777.213              16.777.213      yes
   -2              16.777.214              16.777.214              16.777.214      yes
   -1              16.777.215              16.777.215              16.777.215      yes
    0              16.777.216              16.777.216              16.777.216      yes
    1              16.777.217              16.777.216              16.777.216       no
    2              16.777.218              16.777.218              16.777.218      yes
    3              16.777.219              16.777.220              16.777.220       no
    4              16.777.220              16.777.220              16.777.220      yes
    5              16.777.221              16.777.220              16.777.220       no
    6              16.777.222              16.777.222              16.777.222      yes
    7              16.777.223              16.777.224              16.777.224       no
    8              16.777.224              16.777.224              16.777.224      yes
    9              16.777.225              16.777.224              16.777.224       no
   10              16.777.226              16.777.226              16.777.226      yes

Type      Type name      Digits
Real      double             53
Integer   std::int64_t       63

Shift         Initial integer                    Real     Transformed integer   Equal?
  -10   9.007.199.254.740.982   9.007.199.254.740.982   9.007.199.254.740.982      yes
   -9   9.007.199.254.740.983   9.007.199.254.740.983   9.007.199.254.740.983      yes
   -8   9.007.199.254.740.984   9.007.199.254.740.984   9.007.199.254.740.984      yes
   -7   9.007.199.254.740.985   9.007.199.254.740.985   9.007.199.254.740.985      yes
   -6   9.007.199.254.740.986   9.007.199.254.740.986   9.007.199.254.740.986      yes
   -5   9.007.199.254.740.987   9.007.199.254.740.987   9.007.199.254.740.987      yes
   -4   9.007.199.254.740.988   9.007.199.254.740.988   9.007.199.254.740.988      yes
   -3   9.007.199.254.740.989   9.007.199.254.740.989   9.007.199.254.740.989      yes
   -2   9.007.199.254.740.990   9.007.199.254.740.990   9.007.199.254.740.990      yes
   -1   9.007.199.254.740.991   9.007.199.254.740.991   9.007.199.254.740.991      yes
    0   9.007.199.254.740.992   9.007.199.254.740.992   9.007.199.254.740.992      yes
    1   9.007.199.254.740.993   9.007.199.254.740.992   9.007.199.254.740.992       no
    2   9.007.199.254.740.994   9.007.199.254.740.994   9.007.199.254.740.994      yes
    3   9.007.199.254.740.995   9.007.199.254.740.996   9.007.199.254.740.996       no
    4   9.007.199.254.740.996   9.007.199.254.740.996   9.007.199.254.740.996      yes
    5   9.007.199.254.740.997   9.007.199.254.740.996   9.007.199.254.740.996       no
    6   9.007.199.254.740.998   9.007.199.254.740.998   9.007.199.254.740.998      yes
    7   9.007.199.254.740.999   9.007.199.254.741.000   9.007.199.254.741.000       no
    8   9.007.199.254.741.000   9.007.199.254.741.000   9.007.199.254.741.000      yes
    9   9.007.199.254.741.001   9.007.199.254.741.000   9.007.199.254.741.000       no
   10   9.007.199.254.741.002   9.007.199.254.741.002   9.007.199.254.741.002      yes
Re[3]: использование round
Здравствуйте, B0FEE664, Вы писали:

R>>Здесь фишка в том, что у std::round тип возвращамого значения тот же, что и у параметра. Поэтому явный каст к нужному типу вполне приемлем, я считаю:

R>>
R>>int value = int(std::round(3.14f));
R>>


BFE>А гарантия того, что в value будет 3, а не 2 есть?


Только для относительно небольших чисел. (1ull << std::numeric_limits<Real>::digits) + 1 — первое целое число, которое не имеет точного представления в вещественном типе Real. К этому и подобным ему числам значения вещественного типа не смогут округлиться.

Для float число значащих цифр мантиссы равно 23 + 1 = 24, поэтому пограничное "хорошее" число (которым завершается непрерывная последовательность целых чисел, представимых типом float) будет равно 16.777.216.
Для double число значащих цифр в мантиссе — 52 + 1 = 53, пограничное число — 9.007.199.254.740.992.

Проверочный код:

#include <iostream>
#include <iomanip>
#include <type_traits>
#include <limits>
#include <string>
#include <locale>


class number_punctuation_with_digit_separators final : public std::numpunct<char>
{
protected:
    virtual char do_thousands_sep() const override
    {
        return '.';
    }
    
    virtual std::string do_grouping() const override
    {
        return "\03";
    }
};


template <typename Real, typename Integer>
void print_numbers_near_real_boundary_value(
    const std::make_signed_t<Integer> limit, const std::string& real_name, const std::string& integer_name)
{
    constexpr auto real_digits = std::numeric_limits<Real>::digits;
    constexpr auto integer_digits = std::numeric_limits<Integer>::digits;
    
    static_assert(real_digits + 1 <= integer_digits, "The 'Integer' type is too short to hold the 'Real' boundary value.");
    
    const auto print_type_line = [](const auto& type_category, const auto& type_name, const auto& digits)
    {
        std::cout
            << std::left << std::setw(10) << type_category
            << std::setw(12) << type_name
            << std::right << std::setw(9) << digits << std::endl;
    };
    
    print_type_line("Type", "Type name", "Digits");
    print_type_line("Real", real_name, real_digits);
    print_type_line("Integer", integer_name, integer_digits);
    std::cout << std::endl;
    
    const auto print_number_line = [](const auto& shift, const auto& initial_integer, const auto& real,
        const auto& transformed_integer, const auto& are_equal)
    {
        std::cout
            << std::setw(5) << shift
            << std::setw(24) << initial_integer
            << std::setw(24) << real
            << std::setw(24) << transformed_integer
            << std::setw(9) << are_equal << std::endl;
    };
    
    constexpr auto boundary_value = static_cast<Integer>(1) << real_digits;
        
    print_number_line("Shift", "Initial integer", "Real", "Transformed integer", "Equal?");
    for (auto shift = - limit; shift <= limit; ++shift)
    {
        const auto initial_integer = boundary_value + shift;
        const auto real = static_cast<Real>(initial_integer);
        const auto transformed_integer = static_cast<Integer>(real);
        const auto are_equal = (initial_integer == transformed_integer) ? "yes" : "no";
        
        print_number_line(shift, initial_integer, real, transformed_integer, are_equal);
    }
    std::cout << std::endl << std::endl;
}

#define PRINT_NUMBERS_NEAR_REAL_BOUNDARY_VALUE(Real, Integer, limit) \
    print_numbers_near_real_boundary_value<Real, Integer>(limit, #Real, #Integer)

int main()
{
    std::cout.imbue(std::locale(std::locale::classic(), new number_punctuation_with_digit_separators));
    std::cout << std::fixed << std::setprecision(0);
    
    constexpr auto limit = 10;
    
    PRINT_NUMBERS_NEAR_REAL_BOUNDARY_VALUE(float, std::int32_t, limit);
    PRINT_NUMBERS_NEAR_REAL_BOUNDARY_VALUE(double, std::int64_t, limit);
}


Вывод:

Type      Type name      Digits
Real      float              24
Integer   std::int32_t       31

Shift         Initial integer                    Real     Transformed integer   Equal?
  -10              16.777.206              16.777.206              16.777.206      yes
   -9              16.777.207              16.777.207              16.777.207      yes
   -8              16.777.208              16.777.208              16.777.208      yes
   -7              16.777.209              16.777.209              16.777.209      yes
   -6              16.777.210              16.777.210              16.777.210      yes
   -5              16.777.211              16.777.211              16.777.211      yes
   -4              16.777.212              16.777.212              16.777.212      yes
   -3              16.777.213              16.777.213              16.777.213      yes
   -2              16.777.214              16.777.214              16.777.214      yes
   -1              16.777.215              16.777.215              16.777.215      yes
    0              16.777.216              16.777.216              16.777.216      yes
    1              16.777.217              16.777.216              16.777.216       no
    2              16.777.218              16.777.218              16.777.218      yes
    3              16.777.219              16.777.220              16.777.220       no
    4              16.777.220              16.777.220              16.777.220      yes
    5              16.777.221              16.777.220              16.777.220       no
    6              16.777.222              16.777.222              16.777.222      yes
    7              16.777.223              16.777.224              16.777.224       no
    8              16.777.224              16.777.224              16.777.224      yes
    9              16.777.225              16.777.224              16.777.224       no
   10              16.777.226              16.777.226              16.777.226      yes

Type      Type name      Digits
Real      double             53
Integer   std::int64_t       63

Shift         Initial integer                    Real     Transformed integer   Equal?
  -10   9.007.199.254.740.982   9.007.199.254.740.982   9.007.199.254.740.982      yes
   -9   9.007.199.254.740.983   9.007.199.254.740.983   9.007.199.254.740.983      yes
   -8   9.007.199.254.740.984   9.007.199.254.740.984   9.007.199.254.740.984      yes
   -7   9.007.199.254.740.985   9.007.199.254.740.985   9.007.199.254.740.985      yes
   -6   9.007.199.254.740.986   9.007.199.254.740.986   9.007.199.254.740.986      yes
   -5   9.007.199.254.740.987   9.007.199.254.740.987   9.007.199.254.740.987      yes
   -4   9.007.199.254.740.988   9.007.199.254.740.988   9.007.199.254.740.988      yes
   -3   9.007.199.254.740.989   9.007.199.254.740.989   9.007.199.254.740.989      yes
   -2   9.007.199.254.740.990   9.007.199.254.740.990   9.007.199.254.740.990      yes
   -1   9.007.199.254.740.991   9.007.199.254.740.991   9.007.199.254.740.991      yes
    0   9.007.199.254.740.992   9.007.199.254.740.992   9.007.199.254.740.992      yes
    1   9.007.199.254.740.993   9.007.199.254.740.992   9.007.199.254.740.992       no
    2   9.007.199.254.740.994   9.007.199.254.740.994   9.007.199.254.740.994      yes
    3   9.007.199.254.740.995   9.007.199.254.740.996   9.007.199.254.740.996       no
    4   9.007.199.254.740.996   9.007.199.254.740.996   9.007.199.254.740.996      yes
    5   9.007.199.254.740.997   9.007.199.254.740.996   9.007.199.254.740.996       no
    6   9.007.199.254.740.998   9.007.199.254.740.998   9.007.199.254.740.998      yes
    7   9.007.199.254.740.999   9.007.199.254.741.000   9.007.199.254.741.000       no
    8   9.007.199.254.741.000   9.007.199.254.741.000   9.007.199.254.741.000      yes
    9   9.007.199.254.741.001   9.007.199.254.741.000   9.007.199.254.741.000       no
   10   9.007.199.254.741.002   9.007.199.254.741.002   9.007.199.254.741.002      yes


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