Microsoft Visual Studio 2022 and Floating-point to Integer Conversions
Quick Summary:
- /QIfist is going away
- MSVC is compatible with Standard C++ for all floating-point to integer conversions
- For floating-point to signed integer conversions nothing changed
- VS2022 is compatible with VS2017 and earlier by default for all floating-point to integer conversions
- For floating-point to unsigned integer conversions in VS2019:
- When targeting x64, it is usually compatible with VS2017
- When targeting x86, it is usually compatible with AVX-512
- Version 16.7 and later can use /fpcvt:BC for VS2017 compatibility and /fpcvt:IA for AVX-512 compatibility
- Intrinsic functions give you more options and better control
See also the MSDN documentation on /fpcvt here.
Introduction
As some of you may be aware, Microsoft Visual Studio 2019 changed some floating-point to integer conversions for Intel Architecture (IA). When targeting 32-bit IA (x86), most conversions match the Intel AVX-512 conversion instructions. For signed integers this is the same as Visual Studio 2017. With unsigned integers the results of invalid conversions can be different, as I will explain later. With 64-bit IA (x64), the AVX-512 instructions are used when /arch:AVX512 is selected, but otherwise there was no change from VS2017. Unfortunately, we did not announce this change and we didn’t provide an option for backward compatibility until version 16.7.
By default, Visual Studio 2022 reverts back to the way Visual Studio 2017 handled these conversions. It also keeps the option of using conversions compatible with AVX-512. This post tells you more about this, including other options you might want to use.
Background
Standard C++ specifies how valid conversions work, but invalid conversions can do anything at all. Valid conversions start by truncating the floating-point value, which discards any fractional part and leaves only the integer value. This is also known as “rounding toward zero”. The conversion is valid if the truncated value can be represented in the result type, and the result must be that value. MSVC has been compatible with this for well over a decade, and only invalid conversions to unsigned types have changed.
Most floating-point operations indicate an invalid operation by returning a special “Not-a-Number” (NaN) value, but conversion to integer doesn’t allow that option. Any result value can come from a valid conversion, and there is no single “correct” result for an invalid conversion. Of course, completely random results for such conversions aren’t useful, so two different approaches are commonly used. Intel Architecture (IA) uses the result value farthest from zero as a substitute for NaN, so that any invalid conversion returns this sentinel value. (The specific value returned depends on the result type.) The sentinel value is easy to test for, and it will often cause distinctive behavior during testing and debugging.
The other common approach is called saturation, where any floating-point value too high to fit in the destination type gives the highest possible destination value, and any value too low to fit gives the lowest possible value. If the source value is NaN, zero will be returned for the result. Even if the result is wrong, it is as close as possible to the correct result and may be less likely to cause failures. ARM uses saturation for its conversion instructions.
Conversions with Visual Studio 2017
Intel Architecture has had instructions to convert from floating-point to signed integer types since before the first IBM PC, but instructions to convert to unsigned integer types were first introduced in AVX-512. Up through Visual Studio 2017, conversions to unsigned integer types are based on conversion to type long long. Converting to unsigned first converts to long long and then truncates to 32 bits. When converting to unsigned long long, valid source values that are too high for long long are handled as a special case. All other values are simply converted to long long and recast. This gets around the lack of unsigned conversion instructions, but the values returned for invalid conversions aren’t especially useful.
Conversions with Visual Studio 2019
For VS2019, we intended to make all FP to integer conversions compatible with the corresponding Intel Architecture conversion instructions. That allows using VCVTTSD2USI and VCVTTSS2USI when /arch:AVX512 is selected with no change in behavior. Unfortunately, this change was a bit more difficult than we expected and some cases were not completed. It is only enabled on x64 when /arch:AVX512 is selected. On x86 it was not enabled when /arch:IA32 or /arch:SSE is selected. In addition, on x86 the behavior of a conversion of a floating-point value returned from a function call could be either way. Although this was still compatible with Standard C++, it was obviously undesirable, and we introduced the /fpcvt option to allow developers to select which behavior they want.
The /fpcvt Compilation Option
Starting with Visual Studio 2019 version 16.7, the /fpcvt compilation option controls the results of floating-point to unsigned integer conversions. There are two selections: /fpcvt:BC which specifies the backward compatible VS2017 behavior, and /fpcvt:IA which specifies the new AVX-512 instruction behavior. This option works with either x86 or x64 targets, and it applies whether or not /arch:AVX512 is specified. With VS2022 the default has changed to be the same as /fpcvt:BC, but the /fpcvt:IA selection is still available for both x86 and x64.
Intrinsic Functions for Conversion
There are three problems that the /fpcvt option does not address:
- It applies to all compiled conversions, even where that isn’t the best option.
- It applies to headers and source code modules that may have been written to expect other behavior.
- Neither /fpcvt option generates saturating conversions. Saturation provides compatibility with languages such as Rust and WebAssembly, as well as code compiled to target ARM.
Visual Studio 2022 provides intrinsic functions to address these issues. These sentinel and saturating conversion functions are fully defined on IA, so the behavior doesn’t change due to compilation settings or context.
In addition to these functions, there are fast conversion functions which execute as quickly as possible for valid conversions. Unlike the saturating and sentinel conversions, these are not fully defined, and may generate different values or exceptions for invalid conversions depending on target platform, compilation settings and context. They are useful for handling values that have already been range-checked or values that are generated in a manner that can never cause an invalid conversion.
Fast conversion functions | Saturating conversion functions | Sentinel conversion functions |
int
_cvt_ftoi_fast (float a); |
int
_cvt_ftoi_sat (float a); |
int
_cvt_ftoi_sent (float a); |
long long
_cvt_ftoll_fast (float a); |
long long
_cvt_ftoll_sat (float a); |
long long
_cvt_ftoll_sent (float a); |
unsigned
_cvt_ftoui_fast (float a); |
unsigned
_cvt_ftoui_sat (float a); |
unsigned
_cvt_ftoui_sent (float a); |
unsigned long long
_cvt_ftoull_fast (float a); |
unsigned long long
_cvt_ftoull_sat (float a); |
unsigned long long
_cvt_ftoull_sent (float a); |
int
_cvt_dtoi_fast (double a); |
int
_cvt_dtoi_sat (double a); |
int
_cvt_dtoi_sent (double a); |
long long
_cvt_dtoll_fast (double a); |
long long
_cvt_dtoll_sat (double a); |
long long
_cvt_dtoll_sent (double a); |
unsigned
_cvt_dtoui_fast (double a); |
unsigned
_cvt_dtoui_sat (double a); |
unsigned
_cvt_dtoui_sent (double a); |
unsigned long long
_cvt_dtoull_fast (double a); |
unsigned long long
_cvt_dtoull_sat (double a); |
unsigned long long
_cvt_dtoull_sent (double a); |
Termination of /QIfist Support
Visual Studio 2022 version 17.0 still supports the deprecated /QIfist option on x86, but we will remove it in an update. This option allowed floating-point to int conversions to round according to the current rounding mode (usually round-to-nearest with ties to even) instead of always truncating as specified in Standard C++. This option supports legacy code written on x86 before truncation was adopted as standard. It has never been supported on other target platforms and has been marked deprecated for many releases. Use intrinsic functions to round floating-point values before converting to integer, since this is clearer, faster, and more portable.
Closing Notes
We’d love for you to download Visual Studio 2022 and see how these options can be used in your applications. Your feedback is key to deliver the best experience. If you have questions, please feel free to ask us below. You can also send us your comments through email. If you encounter problems with the experience or have suggestions for improvement, please Report A Problem or reach out via Developer Community. We are also found on Twitter @VisualC.
I assume it is really the underlying msbuild and C++ compiler that was changed? I.e. the impact is also with our build pipelines outside Visual Studio. I would say that Visual Studio should be positioned as a front-end on top of cli tooling.