1
RHxW 2019-10-24 15:33:29 +08:00
不懂 c++
但是这个 setprecision 看着有点可疑,改成 2 试试 关于舍入,我记得 gcc 是向零舍入,可能 VS 那个编译器是向上吧 |
2
Akiyu 2019-10-24 15:38:55 +08:00
你用了 setprecision(1)
http://www.cplusplus.com/reference/iomanip/setprecision/?kw=setprecision 至于为什么不同的环境下数值不同, 那可能和自身平台有关 |
3
kaler OP setprecision(1)是我故意加上去的,就是为了看不同编译器是怎么四舍五入的。
|
4
kaler OP 但如果把 1.25 改成 1.2500001 的话,两个编译器的结果都为 1.3
|
5
wutiantong 2019-10-24 18:18:37 +08:00
@kaler 看四舍五入不是应该用 std::round 么
|
6
yuikns 2019-10-24 18:37:08 +08:00
set precision 这个有假设是四舍五入了么?
|
7
kaler OP @yuikns 我在 setprecision 的一些文档里也没看到关于四舍五入的信息,但从输出结果来看还是存在这个过程的,所以我现在只是好奇这一步是在哪做的,也想知道这样不同编译器的输出差异有没有文档记录。
|
8
happydezhangning 2019-10-24 19:44:21 +08:00
pyton 里也遇到过类似情况,round 四舍五入规则不一样,是四舍六入,五要看前面一位的奇偶,据说这样的四舍五入才是可靠的,如果逢五都进位会导致整体偏大
|
9
by73 2019-10-24 23:24:34 +08:00 7
啊,花了一个晚上,大概总结出了一些东西。先说结论吧,这算是 crt 不同而导致的,windows sdk 中的 printf 函数( cout 应该是一致的)调用的是 windows crt 的内容,默认四舍五入; mingw 使用的是自己整的一套 mingw-w64-crt,默认直接截断。
----- tl;dr:windows crt 使用的是四舍五入,mingw crt 是直接截断。 ( v2 排版可能不太好,源代码我有给定位,可以自己开着编辑器去看) 首先是 Windows 部分,进入 Windows SDK ucrt 文件夹(我的版本是 10.0.18362.0 )从 printf() 函数开始追,能够一路追到 convert/_fptostr.cpp 这个 CRT 源码,找到 `__acrt_fp_strflt_to_string` 函数 61 行就能看到四舍五入的策略,就是只要读完 precision 后还有数字,如果这个数字大于等于 5,就往前进一。这个逻辑是完全写进 CRT 的,所以我之前尝试了半天用 `fesetround()` 都没有任何用处(或者说,对 printf 不起作用)。 ~~~ cpp // Do any rounding which may be needed. Note: if digits < 0, we don't do // any rounding because in this case, the rounding occurs in a digit which // will not be output because of the precision requested. if (digits >= 0 && *mantissa_it >= '5') { buffer_it--; while (*buffer_it == '9') { *buffer_it-- = '0'; } *buffer_it += 1; } ~~~ p.s. 顺带感叹一句,printf 实现原来原来是状态机,DFA 牛逼。而且 windows crt 对 print 这部分似乎比较罗嗦,可能是我见识的太少。打印浮点大致的流程是:设置状态 -> 读取浮点数 -> 根据浮点数转换成高精度小数字符串(这个算法有点看不太懂,我太菜了)-> 根据状态对该字符串进行四舍五入 -> 最后处理该字符串 buffer,输出到 output 设备。不知道为啥要绕这么个弯(可能是算法问题)。 同理,不过 mingw crt 的源码默认没带,要去 https://git.code.sf.net/p/mingw-w64/mingw-w64 克隆一份,我是今天克隆的,不清楚版本(不过 crt 应该不会有太大变化);可以一路追到 mingw-w64/mingw-w64-crt/stdio/mingw_pformat.c,定位到 `__pformat_float_decimal`,处理截断的就是 1796 行的 easy mode 地方。相对 windows crt 的复杂,mingw crt 在这方面倒是比较简单,直接输出 precision 个字符,输出完就行,不做任何其他处理,简而言之就是“截断”。 ~~~ cpp if(decimal_place <= 0){ /* easy mode */ __pformat_putc( '0', stream ); points: __pformat_emit_radix_point(stream); for(int32_t written = 0; written < prec; written++){ if(decimal_place < 0){ /* leading 0s */ decimal_place++; __pformat_putc( '0', stream ); /* significand */ } else if ( sig_written < max_prec ){ __pformat_putc( str_sig[sig_written], stream ); sig_written++; } else { /* trailing 0s */ __pformat_putc( '0', stream ); } } } else // 后面是 hard mode,即小数部分不定长 ~~~ p.p.s. mingw 的原理也是状态机,但是相对 windows 简化了许多,没有见到像 windows crt 那样使用的跳转表。大致 printf 流程为:读取 format 根据符号设置状态 -> 根据 IEEE 754 提出指数、小数、符号 -> 直接根据截断进行输出;相比之下没有 windows 那样频繁对 buffer 的操作。 ----- 干,搞了一晚上,一开始以为只是单纯的 compiler 的问题,但发现无论怎么调 flag 都没用,才考虑到是 crt 的问题,果然我还是 too young。不过读大厂的代码还是挺舒服的,除了代码定位只能靠 vscode 搜索,其他该有的注释都有,还很详细,不愧是 m$。相比之下 mingw 的代码要逊一点,可能是我不太习惯 c 吧 emm |
10
by73 2019-10-25 09:50:14 +08:00
@by73 补充一下 cout 吧,昨天光顾着分析 printf 了:msvc 实现的 stl 中(源码在 https://github.com/microsoft/STL/tree/master/stl/inc ),`src/cout.cpp` 可以找到 cout 的定义
~~~ cpp __PURE_APPDOMAIN_GLOBAL static filebuf fout(_cpp_stdout); __PURE_APPDOMAIN_GLOBAL extern ostream cout(&fout); ~~~ 知道 cout 就是一个 ostream,所以可以去 `inc/ostream` 找相关的 `operator<<`,然后继续追下去,可以找到真正进行输出的函数 `do_put`,位于 `inc/xlocnum` 1294 行,发现其核心为 ~~~ cpp const auto _Ngen = static_cast<size_t>(_CSTD sprintf_s( &_Buf[0], _Buf.size(), _Ffmt(_Fmt, 0, _Iosbase.flags()), static_cast<int>(_Precision), _Val)); ~~~ `sprintf_s`。到这里之后,后面的内容就交给 crt 处理了,而 sprintf_s 跟 printf 都是用的同一套 `processor_type` 模板,所以 cout 最终输出还是要依赖 crt,也因此会受到干扰。mingw 应该也是一样的,这个大家有兴趣可以自己找找,读源码还是挺有意思的。 |