余数运算符在Java中用于Doubles

我在OSU任教已近两年了,这总是令我惊讶,我从学生那里学到了多少。 例如,过去, 我让学生写一些我不理解的奇怪代码 。 在这一点上,即使经过300多个博客文章, 几个YouTube视频 ,甚至从100多种语言中收集代码段 ,您都认为我已经看完了。 好吧,最近,我看到一个学生在双打中使用余数运算符( % ),从那以后我就再也没有真正的相同了。

余数与模运算符

在开始讲故事之前,我想先介绍一下余数运算符和模数运算符。 在Java中, 没有模运算符 。 相反, %是余数运算符。 对于正数,它们在功能上是等效的。 但是,一旦开始使用负数,我们将看到令人惊讶的差异。

我已经在有关RSA加密的文章中谈到了这种差异。 就是说,我找到了另一个很好的来源 ,可以比较Java,Python,PHP和C等各种语言中的“模”运算符。

总而言之,余数运算符的工作原理与我们期望的正数一样。 例如,如果我们采用3 % 5 ,我们将得到3,因为5根本就不等于3。 如果我们开始用负数计算,结果将是相似的。 例如,如果我们使用3 % -5 ,我们仍然会得到3 % -5 ,因为这就是剩下的全部。

同时,如果我们翻转脚本并使股息为负(毕竟,余数是除法的副产品),我们将开始看到负余数。 例如, -3 % 5返回-3。 同样, -3 % -5返回-3。

请注意,在所有这些示例中,如何在符号上有所不同的情况下获得相同的结果。 换句话说,对于余数运算符,我们不太关心符号。 我们只想知道一个数字变成另一个数字的次数。 然后,我们偷看股息以确定征兆。

在另一方面,取模运算符还有很多细微差别。 对于启动器,右侧的操作数确定可能的返回值范围。 如果该值为正,则结果将为正。 这与我们剩余的运算符有点不同。

同时,左操作数确定我们在可能值范围内循环的方向。 自然,当两个值具有相同的符号时,这与余数运算符完美匹配。 不幸的是,它们在任何其他情况下都是完全不同的:

表达 Java(其余) Python(MOD)
3 % 5 3 3
3 % -5 3 -2
-3 % 5 -3 2
-3 % -5 -3 -3

如果您有兴趣学习有关模块化算术的更多信息,另一位学生启发我写了一篇关于使用模块化算术的Rock Paper Scissors游戏的文章

双打余数运算符

当我们考虑余数运算符时,通常会假设它仅与整数一起使用-至少直到最近,这才是我的理解。 事实证明,余数运算符实际上对浮点数起作用,这是有道理的。

灵感

本月初,我正在与一个学生一起在实验室里工作,该实验室要求他们编写一个硬币兑换程序。 具体来说,该程序应从用户那里接受许多美分,并以美国货币(例如美元,半美元,四分之一,角钱,镍币和便士)输出面额。

如果您正在考虑如何解决此问题,我会给您一个提示:您可以采用贪婪的方法。 换句话说,首先选择最大的硬币,然后计算将其中多少分成您当前的美分。 如果做对了,您甚至不需要控制流程。 但是,您可以使用数组和循环来稍微整理一下代码。 由于我懒得用Java编写解决方案,因此它在Python中可能看起来像这样:

3 % 5

无论如何,我有一个学生将美分解释为美元和美分。 换句话说,他们让用户输入1.5美元(而不是150美分)等美元金额。 公平地说,这不是什么大问题。 我们要做的就是将美元金额乘以100,然后将剩余的美分相加得到一个整数。

但是,那不是那个学生所做的。 取而代之的是,他们将每个面额都视为双倍(即实数)。 然后,他们继续使用剩下的运算符而没有任何后果。 简单地说,我傻眼了。 毕竟,那怎么可能呢? 您只计算长除法的余数,对吗? 否则,您将只剩下一个小数,什么也没剩下-还是我想。

使用双打

如果要使用美元和美分重写上面的程序,则可能会出现以下内容:

3 % 5

如果运行此命令,我们将获得与以前完全相同的结果:一美元和一半美元。 那怎么可能?

事实证明,使用小数来计算余数是完全有效的。 我们需要做的就是计算将除数完全除数的次数。 例如, .77 % .25将“理想地”产生.02,因为这与我们不经过而就达到.77的程度非常接近。

注意事项

在发现可以取小数点后的余数之后,我立即想知道为什么我不早知道它。 当然,快速的Google搜索会向您显示可能出现的各种错误行为。

例如,在前面的示例中,我声称.02将是.77和.25的余数,并且有点。 请参阅,在大多数编程语言中,默认浮点值具有一定的精度,该精度由基础二进制体系结构决定。 换句话说,有些十进制数字不能用二进制表示。 这些数字之一恰好是上述表达式的结果:

3 % 5

在处理实数时,我们始终会遇到此类问题。 毕竟,有令人惊讶的十进制数无法用二进制表示。 结果,我们最终遇到了四舍五入错误可能导致更改算法失败的情况。 为了证明这一点,我重写了上面的解决方案以计算前200美分的变化:

3 % 5

为了您的理智,我不会转储结果,但是在该算法失败的地方,我将分享一些金额:

  • $ 0.06(计算镍时失败: .06 % .05
  • $ 0.08(计算便士时失败: .03 % .01
  • $ 0.09(计算镍价时失败: .09 % .05
  • $ 0.11(计算点数时失败: .11 % .1
  • $ 0.12(计算点数时失败: .12 % .1
  • $ 0.13(与$ 0.08相同)
  • $ 0.15(计算点数时失败: .15 % .1
  • $ 0.16(与$ 0.06相同)

我们已经开始看到这些计算中令人震惊的部分成为四舍五入错误的牺牲品。 仅在最初的16美分中,我们就无法在50%的时间内产生准确的变化(忽略0)。 那不是很好!

另外,许多错误开始重演。 换句话说,我怀疑随着更多的可能性,这个问题会变得更糟,因为在此过程中有更多的机会舍入错误。 当然,我继续并再次修改了程序以实际测量错误率:

3 % 5

现在,我应该作为序言,此代码段使用==比较实数,这通常被认为是不好的做法。 结果,有可能我们认为一些“正确”的解决方案是错误的。 就是说,我认为目前这是一个足够好的估计。

当我运行它时,我发现所有更改计算中的53.850699999999996%不正确。 具有讽刺意味的是,即使是我的错误计算也存在一个舍入问题。

您是否应该在双打中使用余数运算符?

在这一点上,我们不得不怀疑在Java中对double使用其余运算符是否有意义。 毕竟,如果舍入错误是一个问题,谁能相信结果呢?

就我个人而言,我的直觉会说不惜一切代价避免执行此操作。 就是说,我做了一些挖掘,并且有几种方法可以解决此问题。 例如,我们可以尝试使用一个将浮点值表示为整数字符串的类(例如Python中的Decimal类或Java中的BigDecimal类)在另一个基础上执行算术。

当然,这些类型的类都有其自身的性能问题,并且无法摆脱以10为底的舍入错误。毕竟,以10为底的值不能代表三分之一。 也就是说,使用余数运算符将获得更多成功。

然而,归根结底,我个人还没有遇到这种情况,并且我怀疑您也会这样做。 当然,如果您在这里,很可能是因为遇到了这个确切的问题。 不幸的是,我没有为您提供很多解决方案。

无论如何,谢谢您的光临。 如果您发现本文有趣,请考虑分享。

翻译自: https://www.javacodegeeks.com/2020/02/the-remainder-operator-works-on-doubles-in-java.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值