Сообщение Re[3]: использование round от 11.02.2018 22:12
Изменено 11.02.2018 22:18 Constructor
Re[3]: использование round
Здравствуйте, B0FEE664, Вы писали:
R>>Здесь фишка в том, что у std::round тип возвращамого значения тот же, что и у параметра. Поэтому явный каст к нужному типу вполне приемлем, я считаю:
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.
Проверочный код:
Вывод:
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>>
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.
Проверочный код:
Вывод:
Поэтому, если хочется абсолютно надежного округления, лучше написать обертку над std::round, которая бы проводила необходимую проверку возможности корректного округления (эту обертку можно сделать шаблонной, параметризовав целочисленным типом, к которому должно быть приведен результат выполнения std::round).
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).