[英]Divide integers with floor, ceil and outwards rounding modes in C++
在 C++ 中, /
除法運算默認使用截斷(向零)舍入。 我們可以將除法的結果調整為零以實現其他舍入模式。 請注意,當除法沒有余數時,所有舍入模式都是等效的,因為不需要舍入。
考慮到這一點,我們可以實現不同的舍入模式。 但在我們開始之前,我們需要一個返回類型的幫助模板,這樣我們就不會到處使用auto
返回類型:
#include <type_traits>
/**
* Similar to std::common_type_t<A, B>, but if A or B are signed, the result will also be signed.
*
* This differs from the regular type promotion rules, where signed types are promoted to unsigned types.
*/
template <typename A, typename B>
using common_signed_t =
std::conditional_t<std::is_unsigned_v<A> && std::is_unsigned_v<B>,
std::common_type_t<A, B>,
std::common_type_t<std::make_signed_t<A>, std::make_signed_t<B>>>;
Ceil舍入與負商的截斷舍入相同,但對於正商和非零余數,我們從零舍入。 這意味着我們增加非零余數的商。
多虧了if-constexpr
,我們可以只使用一個 function 來實現一切:
template <typename Dividend, typename Divisor>
constexpr common_signed_t<Dividend, Divisor> div_ceil(Dividend x, Divisor y)
{
if constexpr (std::is_unsigned_v<Dividend> && std::is_unsigned_v<Divisor>) {
// quotient is always positive
return x / y + (x % y != 0); // uint / uint
}
else if constexpr (std::is_signed_v<Dividend> && std::is_unsigned_v<Divisor>) {
auto sy = static_cast<std::make_signed_t<Divisor>>(y);
bool quotientPositive = x >= 0;
return x / sy + (x % sy != 0 && quotientPositive); // int / uint
}
else if constexpr (std::is_unsigned_v<Dividend> && std::is_signed_v<Divisor>) {
auto sx = static_cast<std::make_signed_t<Dividend>>(x);
bool quotientPositive = y >= 0;
return sx / y + (sx % y != 0 && quotientPositive); // uint / int
}
else {
bool quotientPositive = (y >= 0) == (x >= 0);
return x / y + (x % y != 0 && quotientPositive); // int / int
}
}
乍一看,簽名類型的實現似乎很昂貴,因為它們同時使用 integer 除法和模除法。 但是,在現代架構中,除法通常會設置一個標志,指示是否存在余數,因此在這種情況下x % y != 0
是完全免費的。
您可能還想知道為什么我們不先計算商然后檢查商是否為正。 這是行不通的,因為我們在這個划分過程中已經失去了精度,所以我們不能在之后進行這個測試。 例如:
-1 / 2 = -0.5
// C++ already rounds towards zero
-0.5 -> 0
// Now we think that the quotient is positive, even though it is negative.
// So we mistakenly round up again:
0 -> 1
對於正商,下限舍入與截斷相同,但對於負商,我們從零舍入。 這意味着我們減少非零余數的商。
template <typename Dividend, typename Divisor>
constexpr common_signed_t<Dividend, Divisor> div_floor(Dividend x, Divisor y)
{
if constexpr (std::is_unsigned_v<Dividend> && std::is_unsigned_v<Divisor>) {
// quotient is never negative
return x / y; // uint / uint
}
else if constexpr (std::is_signed_v<Dividend> && std::is_unsigned_v<Divisor>) {
auto sy = static_cast<std::make_signed_t<Divisor>>(y);
bool quotientNegative = x < 0;
return x / sy - (x % sy != 0 && quotientNegative); // int / uint
}
else if constexpr (std::is_unsigned_v<Dividend> && std::is_signed_v<Divisor>) {
auto sx = static_cast<std::make_signed_t<Dividend>>(x);
bool quotientNegative = y < 0;
return sx / y - (sx % y != 0 && quotientNegative); // uint / int
}
else {
bool quotientNegative = (y < 0) != (x < 0);
return x / y - (x % y != 0 && quotientNegative); // int / int
}
}
實現幾乎與div_ceil
相同。
遠離零與truncate完全相反。 基本上,我們需要根據商的符號來增加或減少,但前提是有余數。 這可以表示為將商的sgn
添加到結果中:
template <typename Int>
constexpr signed char sgn(Int n)
{
return (n > Int{0}) - (n < Int{0});
};
使用這個助手 function,我們可以完全實現向上舍入:
template <typename Dividend, typename Divisor>
constexpr common_signed_t<Dividend, Divisor> div_up(Dividend x, Divisor y)
{
if constexpr (std::is_unsigned_v<Dividend> && std::is_unsigned_v<Divisor>) {
// sgn is always 1
return x / y + (x % y != 0); // uint / uint
}
else if constexpr (std::is_signed_v<Dividend> && std::is_unsigned_v<Divisor>) {
auto sy = static_cast<std::make_signed_t<Divisor>>(y);
signed char quotientSgn = sgn(x);
return x / sy + (x % sy != 0) * quotientSgn; // int / uint
}
else if constexpr (std::is_unsigned_v<Dividend> && std::is_signed_v<Divisor>) {
auto sx = static_cast<std::make_signed_t<Dividend>>(x);
signed char quotientSgn = sgn(y);
return sx / y + (sx % y != 0) * quotientSgn; // uint / int
}
else {
signed char quotientSgn = sgn(x) * sgn(y);
return x / y + (x % y != 0) * quotientSgn; // int / int
}
}
不幸的是,這些函數不適用於所有可能的輸入,這是我們無法解決的問題。 例如,將uint32_t{3 billion} / int32_t{1}
相除得到int32_t(3 billion)
,它無法使用 32 位有符號 integer 表示。 在這種情況下,我們得到一個下溢。
使用更大的返回類型將是除了 64 位整數之外的所有選項,因為沒有更大的替代方案可用。 因此,用戶有責任確保當他們將一個無符號數傳遞給這個 function 時,它等同於它的有符號表示。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.