cpp 浮点的 ceil 计算和其它语言不一致的问题

2022-04-10 23:16:58 +08:00
 dusu

各位大佬,最近在学 c++11 ,代码如下:

#include <iostream>
#include <math.h>

using namespace std;

int64_t random(int64_t seed){
    uint64_t result;
    result = (seed * 33 + 49297) % 23680;
    result = ceil((result * 10000000) / 23680);
    return result;
}


int main(){
    cout << random(1) << endl;
}

输出:

831925

js:

   var seed = 1;
   result = (seed * 33 + 49297) % 23680;
   result = Math.ceil((result * 10000000) / 23680);
   console.log(result);

试了几个语言都是输出:

831926

c++ 结果如果是偶数的时候正常

出问题的结果都在奇数上

请问要怎么调整才能达到输出 js 的结果?

请大佬们赐教

2060 次点击
所在节点    C++
17 条回复
hallDrawnel
2022-04-10 23:30:16 +08:00
C++中,result 是 uint64_t ,整型。所以(result * 10000000) / 23680 是整型计算,结果直接砍到了 831925 ,而不是 831925.6757 。而 js 中数字都是浮点数,(result * 10000000) / 23680 能得到精确结果 831925.6757 ,ceil 后就是 831926 了。
所以 C++中 result 要定义成浮点类型,double 什么的。
adoal
2022-04-10 23:42:30 +08:00
如上所说,C 里的整数运算算完除法之后结果还是整数,本来就不带小数部分的。其实用 ceil 函数截一下是多余的。
你在 Python 2 里运行 (1 * 33 + 49297) % 23680 * 10000000 / 23680
或者在 Python 3 里运行 (1 * 33 + 49297) % 23680 * 10000000 // 23680
(因为 Python 2 的 /是整数除法,3 里 /是保留浮点结果,//是相当于 2 和 C 里 /的整数除法)
结果也是跟 C 一样的 831925 。
目测区分整数和浮点数的语言里全程用整数运算结果都应该是 831925 。
adoal
2022-04-10 23:43:18 +08:00
何来“几个语言都是输出 831926”,“CPP 和其它语言不一致”。
treblex
2022-04-10 23:44:50 +08:00
这种问题一般都是 Js 不一致哈哈
rpman
2022-04-10 23:52:02 +08:00
基本数据类型没学好,建议从头看 🐶
pengtdyd
2022-04-11 00:00:23 +08:00
java 程序员表示:你们说的啥???
windyskr
2022-04-11 00:36:11 +08:00
C++ 的 ceil 函数最前面加个 1.0 * 或者 (double) 强制转个类型再试下?
thedrwu
2022-04-11 05:02:44 +08:00
ceil 不是用在整数上的
而且 math.h 不是 c++
xuanbg
2022-04-11 06:41:19 +08:00
js 坑人不浅呐。。。学编程不从数据结构学起的问题这笔就来了吗。
Cu635
2022-04-11 18:54:05 +08:00
@adoal
python3 这是故意不兼容的吧,python2 都已经本来跟 C 、C++保持一致“/”代表整数除法了,干嘛还非得改掉?
这有点为了改而改的意思了。
FrankHB
2022-04-15 12:14:17 +08:00
@adoal @Cu635 C 的设计才是残的。

从 C 的直系祖宗来看:
ALGOL 60 支持实数(实质上是浮点数),/是浮点除法操作符。÷(一些方言中记作 DIV )表示整除,取整数绝对值同 ENTIER 函数定义,即向下取整。
CPL 支持实数(浮点数),/是浮点除法操作符。
在 BCPL 中,浮点数的支持被移除,/表示整数除法,而舍入并非固定,依赖实现。
B 语言中,恢复了浮点数支持,但 /仍保持整除,并规定向零截断。另有#/表示单精度浮点数除法。
基本上,C 以前的语言,除非是语言限制只支持整数算术,/总是表示接近数学含义的实数除法。否则,整数和浮点数除法的操作符语法不同。

但到 C 就乱了。
因为 C 引入了显式类型,/被特设地重载了(虽然语言并不整体支持这个特性)。
不幸的是,整除对浮点数也有意义(所以浮点数除法还需要另外引入函数),而不论整除如何实现,/的语法已经造成了一些理解上的混乱。
要让 /正常地多态,最直接的设计是支持 numeric tower ,根据参数的类型提供最精确的近似,典型地出现在一些 Lisp 方言中。这样的设计更加具有可扩展性,也适应动态检查类型的潜在类型语言(不需要依赖复杂的类型检查规则)。Python3 只是部分地恢复这个传统罢了。

C 的 /的另一个显著问题是有符号数的 /在涉及负数时的行为由实现定义而减少可移植性,这也影响模运算和照搬 C 设计的 C++等语言。直到 C99/C++11 ,才明确要求整除截断。
即便如此,钦定一种明确的计算也没消除整除的固有复杂性,而且经常引起通常的用户低估和忽视这里的问题。参见:en.wikipedia.org/wiki/Modulo_operation
一种比较正常的设计是严格区分不同整除,并以不同的操作显式地明确,而非使用笼统的 /等历史上含义混乱的操作符语法,例如:
people.csail.mit.edu/riastradh/tmp/division.txt 。这里给出了 5 组有明确数学含义的整除和模运算,其中两组被 R7RS 接受,包括 C99 的截断整除。其它表述(如 R6RS 的 div0 )可对应翻译到这些明确的操作上。
对潜在类型语言,是否接受不精确数(浮点数)作为整除的操作数又具有两种变体。在此不再赘述。

@thedrwu 谁告诉你“math.h 不是 C++的”。

@xuanbg 你似乎是没从编程语言历史开始学,而被数据结构坑了。
事实上所谓的数据结构并不太关心这里的问题(虽然一些分支倒是更关心具体的数值表示),根本上也无能解决这里的混淆。
thedrwu
2022-04-15 14:10:06 +08:00
@FrankHB #11 。你写 math.h 能通过 code review ? 杠就别抬了。
Cu635
2022-04-15 18:15:41 +08:00
@FrankHB
说的很有道理,然而有个小问题:之前的“/”含义不同,是在不同的编程语言下面的区分,而且这个编程语言不同名字也显著的不同。到了 python 这里,再夸张的说 python2 和 python3“不是同一种语言”,可是维护团队是一个,网站是一个,名字也没有多大的区分度啊……
更何况,C 语言对于计算机科班来讲还是很应该学习的,它怎么定义应该可以算作普遍的了。

语法造成混乱这个,应该说是设计 C 语言的是工程师,跟数学离得还是有点远的缘故吧,毕竟,数学里面“=”、“≡”和“⇔”是三个符号,所以才非得把整数除法(用于数论)和浮点除法(用于计算)规划成一个符号。

另外,说 math.h 那个,他的意思应该是 C++里应该用#include <cmath>吧。
FrankHB
2022-04-16 00:55:03 +08:00
@thedrwu 背景常识:.h 这种 header 的地位在 ISO C++里是 deprecated ([depr.c.headers]),但是 deprecated 也同样是 [要求] 提供,懂?
一个完整的 ISO C++实现里就包含 math.h ,你要是实现标准库不提供 math.h ,管你是不是同时支持其它语言,就没资格当一个 C++的 hosted implementation 。连这点 conformance requirement 都不明白,杠就别抬了。

顺带,倒不需要我特别数落你,WG21 的人自己也有拎不清的。
比如微软的标准库实现的 Stephan T. Lavavej (因为他本人执意把这个实现叫做 STL ,为避免歧义,这里就不用类 TDN 表记法了)曾认为:

D.5 "C standard library headers" [depr.c.headers]
Why is this even deprecated?

——WG21 N4190

他不懂标准化过程中,deprecated 表示 formal discouraged ,也就是不建议用,只是为了兼容等原因保留。
这仍不表示<cname>比<cname.h>更长寿。事实上,WG21 P0063 更新 C99 兼容 header 到 C11 的同时,反倒是加上了:

The use of any of the C++ headers <ccomplex>, <cstdalign>, <cstdbool>, or <ctgmath> is deprecated.

这在 C++17 起生效。
之后,在 P0619R4 中,这个问题被重新讨论:

D.5 C standard library headers [depr.c.headers]
strong recommendation: Undeprecate the remaining [depr.c.headers] and move it directly into 20.5.5.2 [res.on.headers].
Weak recommendation: In addition to tbe above, also remove the corresponding C headers from the C++ standard, much as we have no corresponding <stdatomic.h>, <stdnoreturn.h>, or <threads.h>, headers.
Toronto Review: No recommendation, take no action without a more detailed paper.

结论是 C++20 仍然保持现状不变。
在 P2139 中,这再次被拉出来鞭尸:

D.9 C headers [depr.c.headers]

Strong recommendation(A): Remove the vacuous headers, then undeprecate the remaining [depr.c.headers] and move directly into 16.5.5.2 [res.on.headers].
strong recommendation (B): Undeprecate the remaining [depr.c.headers] and move directly into 16.5.5.2 [res.on.headers].
Weak recommendation: Remove entirely from C++23.

可见即便是到了现在,移除仍然不是主流意见。

而另一方面,是不是 C++先不说,什么时候容忍用.h 过 review ,是不同的问题,应该分类讨论。
例如很多.h 在 POSIX 中提供扩展(像_r 这样的 reentrant routines ),如果你用<cname>,这是不保证提供的,因此是错的,漏了<cname.h>应该不能通过 review 才对。
而就<math.h>,真正的问题远远更琐碎。用<cmath>代替<math.h>不能纠正误用 abs 代替 std::abs 或 std::fabs 这样的经常更危险的错误(效果倒是跟这里 OP 的问题类似),尽管 Clang++的[-Wabsolute-value]可能会指出这种问题。

我不指望随便一个 C++用户比如你是这方面的专家,但笼统以错误的认知装作适格者来 review ,先不说态度,这看起来就是在凑工时捣乱。
FrankHB
2022-04-16 00:58:48 +08:00
@Cu635 Python 的兼容历来拉胯。Python3.7 这种小版本升级都能加 syntax 破坏向后兼容性,还亏它是个动态语言,连个靠谱的语法扩展都做不到。Python2 到 Python3 不兼容得更加离谱,你当是两种不同的语言也没什么大问题。但这里有个不同的原因不是像 2/3 普遍都有的缺少可扩展性机制的设计,而是 Python2 很多具体特性就设计得尤其烂,不说没有前瞻性,从来就是过气的。要是一开始就不要搞那么拉胯的设计(什么 print 这种阿猫阿狗也好意思当什么“语句”,BASIC 吗……),直接设计成 Python3 这种,不兼容也不会那么低级。

对软件工程实现人员来讲,知道编程语言的缺陷是最起码的整体认识,否则选型就容易出问题,可以说不知道不适合干什么约等于基本不会。C 的历史地位主要体现在第一个跨体系结构实现操作系统的主要部分,说白了也就是只擅长写这种层次特定软件的 DSL ,在有更正常的选项时大规模铺开开发应用本来就是灾难。另一方面,除了知道缺点,科班更应该知道不同语言之间的演化路线,不能学了一种别的就抓瞎。

至于符号,其实抄数学的多了去了,但各种不一致,这种“工程语言”更加见怪不怪了。BCPL 里就有≡,大约是字符集的问题,≢后来改成了 NEQV (但是≡为什么不改呢,我暂且蒙在鼓里);更离谱的是还有=、≠、<、>、≤和>=,然而没有<=和≥,也许是<=和⇐容易混罢。
但是数学符号也可能滥用,特别是有些用法得看作者(比如→和⇒是否同义),也未必好哪去。这类滥用在个别编程语言中也有体现,像声名狼藉的 APL 。然而虽然语法也不咋地,数学语言的语义可没那么拉胯。
Cu635
2022-04-16 16:41:46 +08:00
@FrankHB
“Python2 到 Python3 不兼容得更加离谱,你当是两种不同的语言……”
不是“我当是两种不同的语言”,是有这种说法,我引用了一下。

要是直接设计成 python3 这种,也就没有“干嘛还非得改掉”这种态度了。

C 语言不仅仅是第一个,而且还能够从底到上穿起来计算机科班的学习,当然,也是因为这个,写大型应用软件确实是灾难……

不同语言的话,我更希望了解的是不同的概念,不同语言如何从不同视角反映计算机理论,语言开发时候的取舍和开发思路,而不是这种无意义似是而非的符号问题。

见怪不怪不能说明正确性,要不然,流感就一定是好东西了。不过我上面举的例子,是想说“不同含义就用不同的符号”这个观点,想说 C 语言把整数除法(用于数论)和浮点除法(用于计算)合并成一个符号又已经形成历史惯性的无奈。
FrankHB
2022-04-16 18:47:24 +08:00
@Cu635 关于 Python ,我基本同意你引用的这个说法。特别地,不要试图没事去兼容 Python2 和 Python3 ,要迁移历史遗留项目另说。实际体验被恶心过的应该容易理解。其它语言多多少少也有这样的问题,但很少那么乱。比如 C ,就算要混用不同版本的方言,基本上搞定#if 就没太大问题了。

至于学习,语言有共性,可以相互对照。一般应避免以某个完整的语言为模板通过打补丁来学新的(设计上就没有明确版本继承关系的;至于 Python 这种,看着办)语言,而自己应对比和其它语言的差异之后合并重复的知识点。这样便于甩掉偶然习惯的个别语言缺陷的带来的不良影响,也能减少不同语言串味的风险。

见怪不怪说明的是这些现有语言的具体设计都不足以作为设计的合理性的依据,评价设计时应参照更普遍的更直接反映现实需求的理由。习惯不是瘾,不要做历史的奴隶。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/846153

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX