CString.Format produces different values with the same precision
Asked Answered
T

2

6

Sorry for probably stupid question, as I am C# developer it is really difficult for me to find some ideas.

We are in progress of migration from VC++ 2013 to VC++ 2019. We have 2 similar VMs with VC++2013 and VC++2019 installed. Projects are using VC++2019 already. When running on 2 VMs results for simple evaluation are different:

CString Value;
int precision = 8;
double number = 50.634765625;
String^ RetValue;

Value.Format(_T("%.*f"), precision, number);
RetValue = gcnew String(Value.GetString());

On one VM (Win 7 64bit) we have RetValue "50.63476563" on another VM (win10 64bit) we have - "50.63476562" all tools I expect are the same (difficult to say 100% as there a lot installed, but most important c++ are).

When converting and running projects under VS2013 results on both VM are the same - "50.63476563".

Are there any option settings that control formatting? Or why in case of one VM truncation was selected in favor of rounding?

UPDATE So the problem is really related to VS2013/VS2019 difference. I found why we have different results on 2 VMs using the same VS2019. So:

  1. VS2013 rounding of 50.634765625 == VS 2019 rounding for Release
  2. VS2013 rounding of 50.634765625 != VS 2019 rounding for Debug

in both cases rounding direction is to nearest. So MSFT interprets to nearest differently in Release/Debug. C++ compiler option that makes difference /MD vs /MDd.

Could not find any other way except of switching to /MD to force rounding go old way for whole project in debug.

Traylor answered 4/9, 2020 at 15:18 Comment(11)
String^ -- Change or update your tags. This is not C++.Samons
@Samons it's not standard C++, but it's Microsoft's .NET extension to it. Irrelevant to the question anyway.Clerestory
Yes, but did the OP know this? Hopefully they know the tools they're using.Samons
It looks like 2013 is using "round to nearest" while 2019 is using "round to even". I don't know how you would control that.Clerestory
I was tempted to think this was another case of Is floating point math broken?, but it isn't - 50.634765625 is one of the few floating point numbers that can be represented exactly without error.Clerestory
You can set the rounding mode using _control87 and friends. Are you compiling for 32-bit or 64-bit architectures? This is important, as 32-bit targets compile to FPU code, whereas 64-bit targets always use the SSE unit for floating point calculations. @pau The fact that the OP is converting their MFC CString to a C++/CLI String^ isn't relevant to the issue.Generalissimo
Check what std::fegetround returns on each system en.cppreference.com/w/cpp/numeric/fenv/feroundServiceable
@Generalissimo 32bitTraylor
@VladFeinstein both have FE_TONEARESTTraylor
Yauhen.F Curious, which is more important for you in this case? Correct results or consistent results?Vinaya
@Reinstate Monica I would say both :) Generally our old version that uses VS2013 produce the same (correct rounding results) on all PCs/VMs. We want to have the same. Of course probably inconsistency is more annoying as we don't control it and results are unpredictable on each environment.Traylor
B
6

We found a difference between 2013 and 2017 in our testing. We were seeing differences in rendering the number .249500 to three digits. 2013 = 0.250 whereas 2017 = 0.249. Consider this example:

#include <stdio.h>

// little endian bytes, to avoid parsing differences between both compilers
unsigned char bytesOfDouble[] = {0x56, 0x0E, 0x2D, 0xB2, 0x9D, 0xEF, 0xCF, 0x3F}; 
double& dVal = reinterpret_cast<double&>(bytesOfDouble);

void PrintDouble(const double& d)
{
    printf("printf (%%.3f): %.3f\n", d);
    printf("printf (%%.3g): %.3g\n", d);

    printf("printf (%%f)  : %f\n", d);
    printf("printf (%%g)  : %g\n", d);
}

int main(int argc, char** argv)
{
    PrintDouble(dVal);
    return 0;
}

If you compile it with VS2013 and VS2017, you get different results. If you try g++ or clang, you get the same results as VS2017.

How to get the same results between the two? IDK... I think MS definitely changed their underlying C runtime.

Beadsman answered 4/9, 2020 at 21:4 Comment(5)
VS 2013 was before vs. 2017 after the switch to UCRT, so Floating-point migration issues could be relevant (cc @Yauhen.F).Denotative
So the problem is really related to VS2013/VS2019 difference. I found why we have different results on 2 VMs using the same VS2019. Looks like MSFT changed rounding default, but only for Debug. So: 1) VS2013 rounding of 50.634765625 == VS 2019 rounding for Release 2) VS2013 rounding of 50.634765625 != VS 2019 rounding for Debug trying to find way to unifyTraylor
@Denotative I think I remember trying to step into the runtime and it was totally different...I think you are right about switching to the UCRT sometime after 2013 (for 2015 or 2017).Beadsman
.2495 isn't a good test value, because unlike the value used in the question it isn't exact in base 2. It's actually 0.2494999999999999995559107...Clerestory
Well, in one of our long standing statistical tests, that was the result of a computation. Those exact bytes in the program above. They rendered differently on a legend in a graph in VS2017, 0.249 vs 0.250. So, the regression test indicated to us a change. After looking at the computation, we could see that the bytes of the double were exactly the same in our two versions and then concluded that the runtime was giving different results. When you make a calculation, you cannot always guarantee that the result will render exactly in base 2. Actually, isn't it more common that it won't?Beadsman
T
0

So in my case it looks like a bug introduced in Windows 10 SDK (10.0.19041.1) by Microsoft. For now will use some workaround.

https://developercommunity.visualstudio.com/content/problem/1085732/different-printf-double-rounding-behaviour-between.html

Traylor answered 9/9, 2020 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.