Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<cmath>: std::nextafter with specific type does not work correctly for single precision floats since msvc v19.36 VS17.6 #4982

Closed
spookyGh0st opened this issue Sep 25, 2024 · 1 comment
Labels
invalid This issue is incorrect or by design

Comments

@spookyGh0st
Copy link

Test case

Compare also the difference to versions before v19.36 VS17.6 in https://godbolt.org/z/zzqxKoaqK

C:\Temp>type repro.cpp
#include <iostream>
#include <cmath>

int main(){
    using scalar_t = float;
    scalar_t nextBefOne = std::nextafter<scalar_t>(scalar_t(1), scalar_t(0)); // does not work
    // scalar_t nextBefOne = std::nextafter(scalar_t(1), scalar_t(0)); // works as expected
    if (nextBefOne < scalar_t(1))
        std::cout << "Sucess" <<std::endl;
    else
        std::cout << "Failure" <<std::endl;
}
C:\Temp>cl /EHcs /W4 /std::c++latest .\repro.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.41.34120 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

cl : Command line warning D9002 : ignoring unknown option '/std::c++latest'
repro.cpp
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.41.34120\include\cmath(822): warning C4244: 'return': conversion from 'double' to 'float', possible loss of data
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.41.34120\include\cmath(822): note: the template instantiation context (the oldest one first) is
.\repro.cpp(6): note: see reference to function template instantiation 'float nextafter<main::scalar_t,main::scalar_t,0>(_Ty1,_Ty2) noexcept' being compiled
        with
        [
            _Ty1=main::scalar_t,
            _Ty2=main::scalar_t
        ]
Microsoft (R) Incremental Linker Version 14.41.34120.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:repro.exe
repro.obj
C:\Temp>.\repro.exe
Failure

Expected behavior

std::nextafter should return the next representable value for both single precision floats and double precision floats independent whether the type was specified. However since msvc v19.36 VS17.6 it has stopped working for single precision floats. For doubles it works as expected.

@StephanTLavavej StephanTLavavej added the invalid This issue is incorrect or by design label Sep 25, 2024
@StephanTLavavej
Copy link
Member

Thanks for reporting this issue. We're resolving it as By Design.

The proximate cause was merging #3253 in VS 2022 17.6, which altered how we provide <cmath>'s (notoriously complicated) "sufficient additional overloads".

According to the Standard, <cmath> doesn't necessarily provide function templates - it simply ensures (through some combination of overloads and/or templates) that calls to the math functions with various arithmetic arguments produce the specified results. Giving explicit template arguments to a <cmath> function is non-Standard, because they aren't guaranteed to be templates in the first place. This is WG21-N4988 [cmath.syn]/2-3:

For each function with at least one parameter of type floating-point-type, the implementation provides an overload for each cv-unqualified floating-point type (6.8.2) where all uses of floating-point-type in the function signature are replaced with that floating-point type.

For each function with at least one parameter of type floating-point-type other than abs, the implementation also provides additional overloads sufficient to ensure that, if every argument corresponding to a floating-point-type parameter has arithmetic type, then every such argument is effectively cast to the floating-point type with the greatest floating-point conversion rank and greatest floating-point conversion subrank among the types of all such arguments, where arguments of integer type are considered to have the same floating-point conversion rank as double. If no such floating-point type with the greatest rank and subrank exists, then overload resolution does not result in a usable candidate (12.2.1) from the overloads provided by the implementation.

The root cause of what's physically happening is that we now provide a plain (float, float) overload that absorbs those arguments. We then provide a template to handle all other combos of double, long double, and integers, which performs its work as double. The problem with passing an explicit template argument of float is that it disrupts the overload resolution that we expect (from Standard-conforming user code), bypassing the plain (float, float) overload.

The solution, as you've found, is to simply call std::nextafter without explicit template arguments. If you want to modify the inputs, convert the arguments before the call.

(I gave a talk about this principle, Don't Help The Compiler. The idea is to avoid passing explicit template arguments to functions that weren't intentionally designed for it - e.g. make_shared<T> is of course correct, but swap, make_pair, and in this case nextafter should always be called via template argument deduction.)

@StephanTLavavej StephanTLavavej closed this as not planned Won't fix, can't repro, duplicate, stale Sep 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid This issue is incorrect or by design
Projects
None yet
Development

No branches or pull requests

2 participants