c++ - 根据线性顺序使用最接近的值将数字裁剪到另一种类型的数值限制
问题描述
使用 C++,我想将一个数字剪辑到另一个可能小于源类型的算术类型的数值限制。约束是,结果值应该是整数集的线性顺序中最接近源值的值。
- 低于目标类型最小限制的数字应转换为该最小限制
- 大于目标类型最大限制的数字应转换为该最大限制
- 应该适用于内置整数和浮点类型(我没有查看来自 3rd 方库的多精度类型)
例子:
- 转换为无符号的负数应返回
0
65'535
转换为 int16的无符号应返回32'767
用例:图书馆的使用者只对提供的数字范围的一小部分感兴趣。为了安全地处理超出目标范围的数字,转换需要匹配上述约束(因此不能选择强制转换)。
实现(需要 C++17)
这就是我想出的:
#include <limits>
template <typename T>
constexpr T clip(T v, T lo, T hi)
{
return (v < lo) ? lo : (hi < v) ? hi : v;
}
template <typename TargetType, typename SourceType>
constexpr TargetType clipToTargetTypeNumericLimits(SourceType v)
{
// This condition is necessary to not break integer -> floating point conversion
// (the `static_cast<SourceType>(lo/hi)` below will give unwanted results),
// while at the same time still clipping floating point type -> shorter floating point type.
if constexpr (std::is_floating_point<TargetType>::value
&& !(
std::is_floating_point<SourceType>::value
&& (std::numeric_limits<SourceType>::max_exponent
>= std::numeric_limits<TargetType>::max_exponent)
)
)
{
return static_cast<TargetType>(v);
}
else
{
constexpr auto lo = std::numeric_limits<TargetType>::min();
constexpr auto hi = std::numeric_limits<TargetType>::max();
constexpr auto lo_sourceType = static_cast<SourceType>(lo);
constexpr auto hi_sourceType = static_cast<SourceType>(hi);
return static_cast<TargetType>(clip<SourceType>(v, lo_sourceType, hi_sourceType));
// ... cannot use std::clamp, since it might assert when (lo_sourceType > hi_sourceType)
// return static_cast<TargetType>(std::clamp(v, lo_sourceType, hi_sourceType));
}
}
问题
- 是否有在 boost 或其他库中已经可用的实现(我看了,但找不到)?
- 而不是
if constexpr
条件,它可能更简洁,只是禁止为整数源类型和浮点目标类型调用函数。这也可能使其与 C++11/14 兼容。如何std::enable_if
用于检查源类型和目标类型? - C++20 概念将使编译错误更具可读性。这个函数在概念上会是什么样子?(是否应该将
if constexpr
条件删除并替换为类似于之前问题的概念?) - 您是否看到可能的意外行为?
谢谢!
测试
#include <cassert>
#include <iostream>
template <typename SourceType, SourceType s,
typename TargetType, TargetType expectedResult>
void test_clipToTargetTypeNumericLimits()
{
constexpr auto result = clipToTargetTypeNumericLimits<TargetType>(s);
std::cout << s << " -> " << +result << std::endl;
static_assert(expectedResult == result);
}
template <typename SourceType, typename TargetType, typename S, typename T>
void test_clipToTargetTypeNumericLimits(S f_s, T f_expectedResult)
{
constexpr auto s = f_s();
constexpr auto expectedResult = f_expectedResult();
constexpr auto result = clipToTargetTypeNumericLimits<TargetType>(s);
std::cout << s << " -> " << +result << std::endl;
static_assert(expectedResult == result);
}
int main()
{
std::cout << "\n--- negative signed integer -> unsigned ---" << std::endl;
test_clipToTargetTypeNumericLimits<
int32_t, -42, // source value
uint32_t, 0>(); // expected result
std::cout << "\n--- integer -> shorter integer type ---" << std::endl;
test_clipToTargetTypeNumericLimits<
uint32_t, UINT32_MAX, // source value
uint16_t, UINT16_MAX>(); // expected result
test_clipToTargetTypeNumericLimits<
uint32_t, UINT32_MAX, // source value
char, INT8_MAX>(); // expected result
test_clipToTargetTypeNumericLimits<
int64_t, INT32_MIN + 42, // source value
int32_t, INT32_MIN + 42>(); // expected result
std::cout << "\n--- floating point -> integer ---" << std::endl;
test_clipToTargetTypeNumericLimits<
float,
unsigned>(
[](){ return -42.7f; }, // source value
[](){ return 0; }); // expected result
test_clipToTargetTypeNumericLimits<
double,
uint8_t>(
[](){ return 1024.5; }, // source value
[](){ return UINT8_MAX; }); // expected result
std::cout << "\n--- floating point -> shorter floating point type ---" << std::endl;
test_clipToTargetTypeNumericLimits<
double,
float>(
[](){ return std::numeric_limits<double>::min(); }, // source value
[](){ return std::numeric_limits<float>::min(); }); // expected result
test_clipToTargetTypeNumericLimits<
long double,
float>(
[](){ return std::numeric_limits<long double>::max(); }, // source value
[](){ return std::numeric_limits<float>::max(); }); // expected result
std::cout << "\n--- integer -> floating point ---" << std::endl;
test_clipToTargetTypeNumericLimits<
uint64_t,
float>(
[](){ return UINT64_MAX; }, // source value
[](){ return UINT64_MAX; }); // expected result
std::cout << "\n--- to bool ---" << std::endl;
constexpr auto b_f = clipToTargetTypeNumericLimits<bool>(0);
std::cout << 0 << " -> " << std::boolalpha << b_f << std::endl;
static_assert(0 == b_f);
constexpr auto ldbl_max = std::numeric_limits<long double>::max();
constexpr auto b_t = clipToTargetTypeNumericLimits<bool>(ldbl_max);
std::cout << ldbl_max << " -> " << std::boolalpha << b_t << std::endl;
static_assert(1 == b_t);
std::cout << "\n--- evaluation at runtime ---" << std::endl;
const auto duration_ticks = std::chrono::system_clock::now().time_since_epoch().count();
const auto i8_at_runtime = clipToTargetTypeNumericLimits<int8_t>(duration_ticks);
std::cout << duration_ticks << " -> " << +i8_at_runtime << std::endl;
assert(INT8_MAX == i8_at_runtime);
}
可能的输出:
--- negative signed integer -> unsigned ---
-42 -> 0
--- integer -> shorter integer type ---
4294967295 -> 65535
4294967295 -> 127
-2147483606 -> -2147483606
--- floating point -> integer ---
-42.7 -> 0
1024.5 -> 255
--- floating point -> shorter floating point type ---
2.22507e-308 -> 1.17549e-38
1.18973e+4932 -> 3.40282e+38
--- integer -> floating point ---
18446744073709551615 -> 1.84467e+19
--- to bool ---
0 -> false
1.18973e+4932 -> true
--- evaluation at runtime ---
1585315690266730 -> 127
解决方案
我不太明白那个复杂的 constexpr 条件 - 注释似乎与任何现有代码都不匹配。
我也没有很仔细地检查逻辑。
无论如何,我决定解决由于使用 constexpr-if 而需要 C++17 的问题,而不是试图弄清楚你要做什么。
有很多方法可以做到这一点......我只会展示三种可能性......
在每一个中,我只是让你的条件和你写的一样。
第一个使用 enable if,但如果您先创建元函数,则更容易阅读。
namespace detail {
template <typename TargetT, typename SourceT>
struct IsFloatConversion
: std::bool_constant<
std::is_floating_point<TargetT>::value
&& !(std::is_floating_point<SourceT>::value
&& (std::numeric_limits<SourceT>::max_exponent
>= std::numeric_limits<TargetT>::max_exponent))>
{
};
}
template <typename TargetType, typename SourceType>
constexpr
std::enable_if_t<detail::IsFloatConversion<TargetType, SourceType>::value,
TargetType>
clipToTargetTypeNumericLimits(SourceType v)
{
return static_cast<TargetType>(v);
}
template <typename TargetType, typename SourceType>
constexpr
std::enable_if_t<not detail::IsFloatConversion<TargetType, SourceType>::value,
TargetType>
clipToTargetTypeNumericLimits(SourceType v)
{
constexpr auto lo = std::numeric_limits<TargetType>::min();
constexpr auto hi = std::numeric_limits<TargetType>::max();
constexpr auto lo_sourceType = static_cast<SourceType>(lo);
constexpr auto hi_sourceType = static_cast<SourceType>(hi);
return static_cast<TargetType>(
clip<SourceType>(v, lo_sourceType, hi_sourceType));
}
第二种方式只是标签调度。
namespace detail {
template <typename TargetType, typename SourceType>
constexpr TargetType
clipToTargetTypeNumericLimitsImpl(SourceType v, std::true_type)
{
return static_cast<TargetType>(v);
}
template <typename TargetType, typename SourceType>
constexpr TargetType
clipToTargetTypeNumericLimitsImpl(SourceType v, std::false_type)
{
constexpr auto lo = std::numeric_limits<TargetType>::min();
constexpr auto hi = std::numeric_limits<TargetType>::max();
constexpr auto lo_sourceType = static_cast<SourceType>(lo);
constexpr auto hi_sourceType = static_cast<SourceType>(hi);
return static_cast<TargetType>(
clip<SourceType>(v, lo_sourceType, hi_sourceType));
}
}
template <typename TargetType, typename SourceType>
constexpr TargetType clipToTargetTypeNumericLimits(SourceType v)
{
constexpr bool dofloat = std::is_floating_point<TargetType>::value
&& !(
std::is_floating_point<SourceType>::value
&& (std::numeric_limits<SourceType>::max_exponent
>= std::numeric_limits<TargetType>::max_exponent)
);
return detail::clipToTargetTypeNumericLimitsImpl<TargetType>(v,
std::integral_constant<bool, dofloat>{});
}
第三个类似,但使用专业化。
namespace detail {
template <bool>
struct Clip
{
template <typename TargetType, typename SourceType>
static constexpr TargetType _ (SourceType v)
{
return static_cast<TargetType>(v);
}
};
template <>
struct Clip<false>
{
template <typename TargetType, typename SourceType>
static constexpr TargetType _ (SourceType v)
{
constexpr auto lo = std::numeric_limits<TargetType>::min();
constexpr auto hi = std::numeric_limits<TargetType>::max();
constexpr auto lo_sourceType = static_cast<SourceType>(lo);
constexpr auto hi_sourceType = static_cast<SourceType>(hi);
return static_cast<TargetType>(
clip<SourceType>(v, lo_sourceType, hi_sourceType));
}
};
}
template <typename TargetType, typename SourceType>
constexpr TargetType clipToTargetTypeNumericLimits(SourceType v)
{
constexpr bool dofloat = std::is_floating_point<TargetType>::value
&& !(
std::is_floating_point<SourceType>::value
&& (std::numeric_limits<SourceType>::max_exponent
>= std::numeric_limits<TargetType>::max_exponent)
);
return detail::Clip<dofloat>::template _<TargetType>(v);
}
还有其他方法,但这应该足以让您选择您喜欢的东西 - 或者构建您更喜欢的东西。
推荐阅读
- r - 如何从 hana 获取数据到 R.my 代码显示错误
- sql - 是否可以使用 CASE 语句从一张表中列出所有可能的用户角色?[Teradata]
- reactjs - 将(static-html)博客实现到 React Web 应用程序的最佳方法是什么?
- hyperledger-fabric - 如何在网络创建过程中创建一个不在初始通道配置中的通道?
- c++ - 如何通过扩展以下类型特征来删除 decltype(&MyClass::funct) 部分?
- jpa - 在 spring-data 中使用 @Transactional 正确提交到数据库
- python - Divio Cloud 部署错误:ENOGIT git 未安装或不在 PATH 中
- java - 我们如何修复“java.sql.SQLSyntaxErrorException:
- ios - 应用程序仅在从 Xcode 运行时才发现蓝牙设备
- python - 使用 Python 将字符串转换为字典列表