今天在看一篇公众号文章《性能之王:最快的编程语言》,发现评论区有这么一段对话:

img

img2

然后我找了下在 stackexchange 的真实提问:

https://math.stackexchange.com/questions/623449/negative-number-divided-by-positive-number-what-would-be-remainder

从回答来看,C 和 Python 的两种做法在数值计算上都是成立的。两种做法的区别在于是否允许余数为负数,或者说,符号该不该与原数值相同。

不允许余数出现负数的,是目前广泛使用的欧几里得除法。

所以“数学洁癖”会认为负值余数是错的?

举报· 2512 次点击
登录 注册 站外分享
23 条回复  
FishBear 小成 2024-11-28 09:30:02
@liprais #9 冒犯了哦. https://i.imgur.com/io2SM1h.png
NessajCN 小成 2024-11-28 09:24:31
定义问题 数学上你 7%3 == -2 也是对的,也就是个向左取还是向右取的选择
favourstreet 小成 2024-11-28 08:13:23
兄弟们,还是看一看实部或者虚部有一个是浮点数∞的时候都复数乘法该怎么算吧,我支持单点紧化
geelaw 小成 2024-11-28 02:31:06
C 语言规定 a / b 的值 q 是 a 除以 b 向零取整,而 a % b 是满足 a = qb + r (带余除法恒等式)惟一的 r 。 数论中常见的定义是 0 <= r < |b|,此时 q 的数值并不是 a 除以 b 向零取整,而是向下取整,比如 C 语言: -1 = 0*3 + (-1) 1 = 0*(-3) + 1 数论: -1 = (-1)*3 + 2 1 = (-1)*(-3) + 2 带余除法恒等式相当重要且自然,如果丧失它则扩展欧几里得算法 [给定 a, b 计算 x, y 使 ax+by=(a,b)] 会很难写对。以下三者不可兼得: 1. 带余除法恒等式 2. 对一切 a 不是 int 最小值且 b 不是 0 ,成立 -(a / b) == (-a) / b 且 -a / b == 0 - a / b ,即“向零取整” 3. a % b 永远是非负数 值得注意的是 Python 也没有完全采用数论中常见的定义,因为 Python 里 a % b 的符号是 0 或者和 b 相同(整数的情况),而不是永远非负。 C 和 Python 都不是“常见数论教材”纯粹的。数学上对余数的选择没有某种必然的对错,通常选 (-b, b) 里的任何数都不会导致常见的算法(如欧几里得算法)无法继续。 C 语言选择向零取整、保持带余除法恒等式,虽然 a % b 可能有负数,但是保证了 -a/b (-a)/b (0-a)/b -(a/b) 0-(a/b) 0-a/b 的计算结果都相同(假设 a 不是 int 最小值且 b 不是 0 )。而在 Python 里面,对于整数 a,b ,表达式 -a//b (-a)//b (0-a)//b 和 -(a//b) 0-(a//b) 0-a//b 的两组结果分别相同,但组间可以不同,不同当且仅当 a/b 是负非整数。
secondwtq 小成 2024-11-28 01:50:15
我记得这个问题我很久之前折腾过,不过具体怎样忘了(当时也没搞 Numerics ),我翻了一下记录,有这么一篇论文: dl.acm.org/doi/pdf/10.1145/128861.128862 The Euclidean Definition of the Functions div and mod 刚才搜到了这个 github.com/WebAssembly/design/issues/250 Semantics of signed integer divide and remainder · Issue #250 · WebAssembly/design · GitHub 根据这个 thread ,最早用 truncating division 的是 Fortran ,原因是早期机器上多不使用 2's complement 表示,truncating division 更好实现,C 出于和 Fortran 兼容的考虑,最后也用了 truncating division 。但是现在的 2's complement 表示上,Euclidean division 可能更好实现(见上面论文,另外两个都引用了 Guy Steele 的 Arithmetic Shifting Considered Harmful ,不过这个我还没看)。但是 truncating division 作为前 2's complement 时代的习惯保留下来了。 所以可能还真不是 C 带的头。至于是不是真的 Fortran 先干的我也不确定( Fortran 66 标准里面我没找到,77 里面倒是有,不过那时候已经有原始的 C 了),但是考古只考到 C 大概是不合格的,就算暴论也没上面那个 thread 有活。 另外上面的“好实现”指得是用 ASR 操作来模拟,硬件除法器有自己的算法,我还没看过。
vvhy 小成 2024-11-28 01:47:19
两种定义在数学上都是自洽的
cooltechbs 小成 2024-11-28 01:45:16
谈数学怎么能不提 Fortran ,Fortran 是怎么处理的(我真的不懂,真心发问)? 而其他语言“错”的根源肯定也不是 C ,而是汇编/机器码。这方面 ARM 、MIPS 又是怎么处理的?
coderluan 初学 2024-11-28 01:09:44
作者说只有 python 是对的,其他语言是错的,从数学的角度我并不反对。 但是作者说其他编程语言是因为 C 语言这么做,所以才跟着这么做的。我感觉作者有点太不看不起其他编程语言了吧,那其他语言和 C 语言不一致情况怎么解释,其他语言这会又不怕了吗?这就是明显的拉踩行为啊。
secondwtq 小成 2024-11-28 01:07:05
这些语言的行为在它们自己的体系里是自洽的——比如 C 的浮点数转整数会直接把浮点部分切掉,而 C 的除法,商也是把浮点部分切掉,然后根据此算出余数。如果用传统香烟,啊不传统余数,那同时算出的商和余数会不满足 商*除数+余数=被除数 这一基本原则,这个问题显然更严重。 注意这个行为是 C99 之后才有的,之前没有定义,不过 C99 之前标准库里定义了 div() 函数,可以同时算出商和余数,是一直遵循这个行为的。主流实现比如 x86 的 idiv 指令应该一直都是这样。 C 标准库对浮点数还定义了 fmod() 和 remainder() 两个函数,两个采取了不同的定义,remainder() 函数对应的是 IEEE 754 标准定义的 remainder 操作。fmod() 函数我没有在标准里找到对应。 Python 虽然浮点强转整数也是切,但是貌似实际用得不多,默认的 / 不能整除时直接给浮点,// 和 % 也是一致的。 至于拿计算机语言强行追求贴合数学定义我觉得大可不必,光浮点数就很头疼。等下个 IEEE 754 标准更新之后,可能会有很多符合该标准的实现,但是可能大多数人不会用。
123下一页
返回顶部