Safe Comparisons of Integrals with C++20

Safe Comparisons of Integrals with C++20

This is a cross-post from www.ModernesCpp.com.

When you compare signed and unsigned integers, you may not get the result you expect. Thanks to the six std::cmp_* functions, there is a cure in C++20.

Maybe, you remember the rule "ES.100 Don't mix signed and unsigned arithmetic" from the C++ Core Guidelines. I wrote a few words about it in my previous post to "Arithmetic Rules". Today, I want to dig deeper into this issue and compare signed and unsigned integers.

Let's start with an unsafe comparison.

Unsafe Comparison of Integrals

 Of course, there is a reason for the program name unsafeComparison.cpp.

// unsafeComparison.cpp

#include <iostream>

int main() {

    std::cout << std::endl;

    std::cout << std::boolalpha;

    int x = -3;                  // (1)
    unsigned int y = 7;          // (2)

    std::cout << "-3 < 7:  " << (x < y) << std::endl;
    std::cout << "-3 <= 7: " << (x <= y) << std::endl;
    std::cout << "-3 > 7:  " << (x > y) << std::endl;
    std::cout << "-3 => 7: " << (x >= y) << std::endl;

    std::cout << std::endl;
   
}

When I execute the program, the output may not meet your expectations.

No alt text provided for this image

When you read the output of the program, you recognize -3 should be bigger than 7. You presumably know the reason. I compared a signed x (line (1)) with an unsigned y (line (2)). What is happening under the hood? The following program provides the answer.

// unsafeComparison2.cpp

int main() {
    int x = -3;
    unsigned int y = 7;

    bool val = x < y;              // (1)
    static_assert(static_cast<unsigned int>(-3) == 4'294'967'293);
}

In the example, I'm focusing on the less-than operator. C++ Insights gives me the following output:

No alt text provided for this image

Here is what's happening:

  1. The compiler transforms the expression x < y (line 1) into static_cast<unsigned int>(x) < y. In particular, the signed x is converted to an unsigned int.
  2. Due to the conversion, -3 becomes 4'294'967'293. 
  3. 4'294'967'293 is equal to (-3) modulo (2 to the power of 32).
  4. 32 is the number of bits of an unsigned int on C++ Insights.

Thanks to C++20, we have a safe comparison of integrals.

Safe Comparison of Integrals

C++20 supports the six comparison functions for integrals:

No alt text provided for this image

Thanks to the six comparison functions, I can easily transform the previous program unsafeComparison.cpp into the program safeComparison.cpp. The new comparison functions require the header <utility>.

// safeComparison.cpp

#include <iostream>
#include <utility>

int main() {

    std::cout << std::endl;

    std::cout << std::boolalpha;

    int x = -3;
    unsigned int y = 7;

    std::cout << "3 == 7:  " << std::cmp_equal(x, y) << std::endl;
    std::cout << "3 != 7:  " << std::cmp_not_equal(x, y) << std::endl;
    std::cout << "-3 < 7:  " << std::cmp_less(x, y) << std::endl;
    std::cout << "-3 <= 7: " << std::cmp_less_equal(x, y) << std::endl;
    std::cout << "-3 > 7:  " << std::cmp_greater(x, y) << std::endl;
    std::cout << "-3 => 7: " << std::cmp_greater_equal(x, y) << std::endl;
    
    std::cout << std::endl;
   
}

I also used in this program the equal and not equal operator.

Thanks to GCC 10, here is the expected result:

No alt text provided for this image

Invoking a comparison function a non-integral value would causes a compile-time error. 

// safeComparison2.cpp

#include <iostream>
#include <utility>

int main() {

    double x = -3.5;             // (1)
    unsigned int y = 7;          // (2)

    std::cout << "-3.5 < 7:  " << std::cmp_less(x, y) << std::endl;

}

Trying to compare a double (line (1)) and an unsigned int (line (2)) gives with the GCC 10 compiler a lengthy error message. Here is the crucial line of the error message:

The internal type-traits __is_standard_integer failed. I was curious about what that means and looked it up in the GCC type-traits implementation on GitHub. Here are the relevant lines from the header type-traits:

// Check if a type is one of the signed or unsigned integer types.
  template<typename _Tp>
    using __is_standard_integer
      = __or_<__is_signed_integer<_Tp>, __is_unsigned_integer<_Tp>>;

// Check if a type is one of the signed integer types.
  template<typename _Tp>
    using __is_signed_integer = __is_one_of<__remove_cv_t<_Tp>,
	  signed char, signed short, signed int, signed long,
	  signed long long

// Check if a type is one of the unsigned integer types.
  template<typename _Tp>
    using __is_unsigned_integer = __is_one_of<__remove_cv_t<_Tp>,
	  unsigned char, unsigned short, unsigned int, unsigned long,
	  unsigned long long

__remove_cv_t is the internal function of GCC to remove const or volatile from a type.

Maybe, you are now curious what happens when you compare a double and an unsigned int the classical way.

Here is the modified program safeComparison2.cpp.

// classicalComparison.cpp

int main() {

    double x = -3.5;             
    unsigned int y = 7;          

    auto res = x < y;     // true
    
}

It works. The crucial unsigned int is floating-point promoted to double. C++ Insights shows the truth:

No alt text provided for this image

After so many comparisons, I want to end this post with the new mathematical constants we have with C++20.

Mathematical Constants

 First, the constants require the header <numbers> and the namespace std::numbers. The following tables give you the first overview.

No alt text provided for this image
No alt text provided for this image

The program mathematicConstants.cpp applies the mathematical constants.

// mathematicConstants.cpp

#include <iomanip>
#include <iostream>
#include <numbers>

int main() {
    
    std::cout << std::endl;
    
    std::cout<< std::setprecision(10);
    
    std::cout << "std::numbers::e: " <<  std::numbers::e << std::endl; 
    std::cout << "std::numbers::log2e: " <<  std::numbers::log2e << std::endl; 
    std::cout << "std::numbers::log10e: " <<  std::numbers::log10e << std::endl; 
    std::cout << "std::numbers::pi: " <<  std::numbers::pi << std::endl; 
    std::cout << "std::numbers::inv_pi: " <<  std::numbers::inv_pi << std::endl;
    std::cout << "std::numbers::inv_sqrtpi: " <<  std::numbers::inv_sqrtpi << std::endl; 
    std::cout << "std::numbers::ln2: " <<  std::numbers::ln2 << std::endl; 
    std::cout << "std::numbers::sqrt2: " <<  std::numbers::sqrt2 << std::endl; 
    std::cout << "std::numbers::sqrt3: " <<  std::numbers::sqrt3 << std::endl; 
    std::cout << "std::numbers::inv_sqrt3: " <<  std::numbers::inv_sqrt3 << std::endl;
    std::cout << "std::numbers::egamma: " <<  std::numbers::egamma << std::endl;
    std::cout << "std::numbers::phi: " <<  std::numbers::phi << std::endl;
    
    std::cout << std::endl;
    
}

Here is the output of the program with the MSVC compiler 19.27.

No alt text provided for this image

The mathematical constants are available for float, double, and long double. Per-default double is used but you can also specify float (std::numbers::pi_v<float>) or long double (std::numbers::pi_v<long double>).

What's next?

C++20 offers more useful utilities. For example, you can ask your compiler which C++ feature it supports, can easily create function objects with std::bind_front, or perform different actions in a function whether the function runs a compile-time or at runtime.


Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dr?ge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang G?rtner, Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Avi Kohn, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, Sudhakar Balagurusamy, lennonli, and Pramod Tikare Muralidhara.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, and Dendi Suhubdy

 

Seminars

I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.

Bookable (Online)

Deutsch

English

Standard Seminars 

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++


 

要查看或添加评论,请登录

Rainer Grimm的更多文章

  • std::execution: Asynchronous Algorithms

    std::execution: Asynchronous Algorithms

    supports many asynchronous algorithms for various workflows. Presenting proposal P2300R10 is not easy.

  • My ALS Journey (17/n): Christmas Special

    My ALS Journey (17/n): Christmas Special

    Today, I have a special Christmas gift. My ALS Journey so far Make the Difference Let’s do something great together:…

  • std::execution

    std::execution

    std::execution, previously known as executors or Senders/Receivers, provides “a Standard C++ framework for managing…

    1 条评论
  • C++26 Core Language: Small Improvements

    C++26 Core Language: Small Improvements

    There are more small improvements in the C++26 language, which you should know. static_assert extension First, here’s…

  • My ALS Journey (16/n): Good Bye Training / Hello Mentoring

    My ALS Journey (16/n): Good Bye Training / Hello Mentoring

    In 2025, I will no longer offer C++ classes. Instead, I will only offer C++ mentoring in the future.

    1 条评论
  • Placeholders and Extended Character Set

    Placeholders and Extended Character Set

    Placeholders are a nice way to highlight variables that are no longer needed. Additionally, the character set of C++26…

    4 条评论
  • Contracts in C++26

    Contracts in C++26

    Contracts allow you to specify preconditions, postconditions, and invariants for functions. Contracts should already be…

    5 条评论
  • Mentoring as a Key to Success

    Mentoring as a Key to Success

    I know that we are going through very challenging times. Saving is the top priority.

  • Reflection in C++26: Determine the Layout

    Reflection in C++26: Determine the Layout

    Thanks to reflection, you can determine the layout of types. My examples are based on the reflection proposal P2996R5.

    5 条评论
  • My ALS Journey (15/n): A typical Day

    My ALS Journey (15/n): A typical Day

    You may wonder how my day looks. Let me compare a day before ALS with a current day.

    3 条评论

社区洞察

其他会员也浏览了