vuivui 发表于 2015-3-16 17:55:48

关于Floor、Ceiling、Round函数出错的问题

本帖最后由 vuivui 于 2015-3-16 21:38 编辑

看了"四舍五入"无法搞定?一帖,做了个答案,发现问题有点严重,就另开一帖讨论一下。
关于Round我的答案是这样的:Func _Round($num, $dig)
        $dig = 10 ^ $dig
        Local $neg = False
        If $num < 0 Then
                $num = Abs($num)
                $neg = True
        EndIf
        Local $numF = Int($num * $dig)
        If Int($num * $dig * 10) - $numF * 10 >= 5 Then $numF += 1
        $num = $numF / $dig
        If $neg Then $num *= -1
        Return $num
EndFuncInt($num * $dig * 10)里的Int原来我用的是Floor,但是用了Floor结果不对,原来Floor自身有问题,请看:
Floor(8.155*100*10) ;8154
Floor(8.155*1000) ;8154
Floor(9.155*100*10) ;9154
Floor(9.155*1000) ;9155

从下面的例子中可以想出问题出在8.155*1000这个表达式上:
$n=8.155*1000
MsgBox(1,Floor($n) ,Floor(8.155*1000)) ;==>81548154

$n=Execute(8.155*1000)
MsgBox(1,Floor($n) ,Floor(Execute(8.155*1000))) ;==>81558154

Int与Floor应该说对正数的取整效果是一样的,其实有很大的区别,au3来源于c++,c语言中没有Int函数,但可以通过声明整型变量赋值来实现取整,当然数据类型是int,Floor是函数,数据类型是Double。

8.155*1000二进制计算结果小于8155,不是十进制的8155整数,Floor对8.155*1000结果取整小数部分被舍去,变成了8154,中间少了一个二进制计算转换为十进制的纠正过程。Execute(8.155*1000)在au3的c++源码中应该是先用int赋值取整转换为字符串,然后Execute去作为代码去执行字符串,也就是说这样也行:
$n=8.155*1000 & ""
MsgBox(1,Floor($n) ,Floor(8.155*1000)) ;==>81558154
在c++中int赋值后取整是得到校正的。

按此道理可以推导出Ceiling对负数表达式也会出现同样问题的,试了一下,果然:
MsgBox(1,Ceiling(-8.155*1000) ,Ceiling(-8155)) ;==>-8154-8155

所以用Floor、Ceiling对表达式取整就用这种方式吧:
Floor(Execute(8.155*1000))

Round纠正的话试用我提供的_Round看看

netegg 发表于 2015-3-16 18:01:14

回复 1# vuivui
10 ^ $dig,先不管其他的,这一下就不知道有没有问题

afan 发表于 2015-3-16 18:47:16

蛋蛋 激情无限哈

vuivui 发表于 2015-3-16 19:03:27

回复vuivui
10 ^ $dig,先不管其他的,这一下就不知道有没有问题
netegg 发表于 2015-3-16 18:01 http://www.autoitx.com/images/common/back.gif

我想在这里是小整数应该没问题吧,复杂的浮点计算当然会有误差的。

netegg 发表于 2015-3-16 19:34:17

回复 4# vuivui
小整数没什么精度问题

邪恶海盗 发表于 2015-3-16 20:32:33

同样怀疑是二进制的问题,有人能分析一下不???

gto250 发表于 2015-3-16 21:08:13

用这个吧!
msgbox(0,"",florx(9.155*1000))



Func florx($num)
        $JS = ObjCreate("ScriptControl")
        $JS.language = "jscript"
        $JS.addcode("function florx(n){return Math.floor(n);}")
        Return $JS.Eval("florx("&$num&")")
EndFunc

vuivui 发表于 2015-3-16 21:53:02

回复 7# gto250

有时引用其他稳定的脚本对象也是好办法

zldfsz 发表于 2015-3-16 22:17:07

感觉分析得很有道理的,大家讨论得也满热情的,论坛有点以前的感觉了

afan 发表于 2015-3-16 23:07:05

LZ 的 _Round() 函数不错,本人测试无误

zldfsz 发表于 2015-3-17 08:39:43

回复 10# afan
哪里没问题了,不是和我那篇帖子里写的那个类似吗,只不过他加入了对负数情况的判断,下面是你测试的其中一个
MsgBox(4096, "测试",_Round(123456155321234567.12345678, 2))

Func _Round($num, $dig)
      $dig = 10 ^ $dig
      Local $neg = False
      If $num < 0 Then
                $num = Abs($num)
                $neg = True
      EndIf
      Local $numF = Int($num * $dig)
      If Int($num * $dig * 10) - $numF * 10 >= 5 Then $numF += 1
      $num = $numF / $dig
      If $neg Then $num *= -1
      Return $num
EndFunc

netegg 发表于 2015-3-17 09:34:44

回复 11# zldfsz
你又想犯坏了吧

zldfsz 发表于 2015-3-17 09:38:51

哈哈,正常交流

afan 发表于 2015-3-17 12:48:28

本帖最后由 afan 于 2015-3-17 12:53 编辑

回复 11# zldfsz


    的确未进行大数测试,被大师的头像戳中…
看来还真得按我们对常规四舍五入的规则直接切入了,判断其后一位是否>=5,是的话就进位,就这么简单

zldfsz 发表于 2015-3-17 13:38:33

回复 14# afan

当时觉得这头像有点逗就上传了,无意冒犯,望见谅
页: [1] 2 3 4
查看完整版本: 关于Floor、Ceiling、Round函数出错的问题