33

将数字四舍五入然后截断它(四舍五入后删除小数位)的最佳有效方法是哪种?

例如,如果小数大于 0.5(即 0.6、0.7 等),我想四舍五入然后截断(案例 1)。否则,我想截断(案例2)

for example:
232.98266601563 => after rounding and truncate = 233 (case 1)
232.49445450000 => after rounding and truncate = 232 (case 2)
232.50000000000 => after rounding and truncate = 232 (case 2)
4

12 回答 12

44

Lua 中没有内置的 math.round() 函数,但您可以执行以下操作 print(math.floor(a+0.5))

于 2013-08-19T12:10:40.023 回答
21

对整数以外的十进制数字舍入有用的技巧是通过格式化的 ASCII 文本传递值,并使用%f格式字符串指定所需的舍入。例如

mils = tonumber(string.format("%.3f", exact))

将任意值四舍五入exact为 0.001 的倍数。

math.floor()在使用or之前和之后进行缩放可以得到类似的结果math.ceil(),但是根据您对边缘情况处理的期望来获得正确的细节可能会很棘手。并不是说这不是问题string.format(),而是为了使它产生“预期”的结果,已经做了很多工作。

舍入到除 10 的幂之外的倍数仍然需要缩放,并且仍然存在所有棘手的边缘情况。一种易于表达且行为稳定的方法是编写

function round(exact, quantum)
    local quant,frac = math.modf(exact/quantum)
    return quantum * (quant + (frac > 0.5 and 1 or 0))
end

并调整上的确切条件frac(可能还有 的符号exact)以获得您想要的边缘情况。

于 2013-08-19T20:30:36.303 回答
18

要还支持负数,请使用以下命令:

function round(x)
  return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5)
end
于 2014-11-06T11:04:46.070 回答
9

如果您的 Lua 使用双精度 IEC-559(又名 IEEE-754)浮点数,就像大多数人一样,并且您的数字相对较小(该方法保证适用于 -2 51和 2 51之间的输入),则以下有效代码将使用 FPU 的当前舍入模式执行舍入,该模式通常舍入到最接近的值,与偶数相关:

local function round(num)
  return num + (2^52 + 2^51) - (2^52 + 2^51)
end

(请注意,括号中的数字是在编译时计算的;它们不影响运行时)。

例如,当 FPU 设置为最接近或偶数时,此单元测试会打印“所有测试通过”:

local function testnum(num, expected)
  if round(num) ~= expected then
    error(("Failure rounding %.17g, expected %.17g, actual %.17g")
          :format(num+0, expected+0, round(num)+0))
  end
end

local function test(num, expected)
  testnum(num, expected)
  testnum(-num, -expected)
end

test(0, 0)
test(0.2, 0)
test(0.4, 0)
-- Most rounding algorithms you find on the net, including Ola M's answer,
-- fail this one:
test(0.49999999999999994, 0)
-- Ties are rounded to the nearest even number, rather than always up:
test(0.5, 0)
test(0.5000000000000001, 1)
test(1.4999999999999998, 1)
test(1.5, 2)
test(2.5, 2)
test(3.5, 4)
test(2^51-0.5, 2^51)
test(2^51-0.75, 2^51-1)
test(2^51-1.25, 2^51-1)
test(2^51-1.5, 2^51-2)
print("All tests passed")

这是另一种(当然效率较低)算法,它执行相同的 FPU 舍入但适用于所有数字:

local function round(num)
  local ofs = 2^52
  if math.abs(num) > ofs then
    return num
  end
  return num < 0 and num - ofs + ofs or num + ofs - ofs
end
于 2019-10-16T10:56:03.127 回答
6

这是一个四舍五入到任意位数的数字(如果未定义,则为 0):

function round(x, n)
    n = math.pow(10, n or 0)
    x = x * n
    if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end
    return x / n
end
于 2016-06-13T14:51:52.157 回答
4

对于不良舍入(切断末端):

function round(number)
  return number - (number % 1)
end

好吧,如果你愿意,你可以扩展它以获得良好的舍入。

function round(number)
  if (number - (number % 0.1)) - (number - (number % 1)) < 0.5 then
    number = number - (number % 1)
  else
    number = (number - (number % 1)) + 1
  end
 return number
end

print(round(3.1))
print(round(math.pi))
print(round(42))
print(round(4.5))
print(round(4.6))

预期成绩:

3, 3, 42, 5,5

于 2016-04-22T15:53:53.220 回答
3

我喜欢RBerteig上面的回复: mils = tonumber(string.format("%.3f", exact))。将其扩展为函数调用并添加精度值。

function round(number, precision)
   local fmtStr = string.format('%%0.%sf',precision)
   number = string.format(fmtStr,number)
   return number
end
于 2019-07-31T21:23:45.107 回答
1

应该是math.ceil(a-0.5)正确处理半整数

于 2013-08-19T20:18:39.767 回答
1

这是一个灵活的函数,可以四舍五入到不同数量的位置。我用负数、大数、小数和各种边缘情况对其进行了测试,它有用且可靠:

function Round(num, dp)
    --[[
    round a number to so-many decimal of places, which can be negative, 
    e.g. -1 places rounds to 10's,  
    
    examples
        173.2562 rounded to 0 dps is 173.0
        173.2562 rounded to 2 dps is 173.26
        173.2562 rounded to -1 dps is 170.0
    ]]--
    local mult = 10^(dp or 0)
    return math.floor(num * mult + 0.5)/mult
end
于 2021-06-10T08:40:32.293 回答
0

为了四舍五入到给定数量的小数(也可以是负数),我建议以下解决方案结合已经作为答案提出的发现,尤其是Pedro Gimeno 给出的鼓舞人心的解决方案。我测试了一些我感兴趣的极端案例,但不能声称这使得这个功能 100% 可靠:

function round(number, decimals)
  local scale = 10^decimals
  local c = 2^52 + 2^51
  return ((number * scale + c ) - c) / scale
end

这些案例说明了 round-half-to-even 属性(这应该是大多数机器上的默认值):

assert(round(0.5, 0) == 0)
assert(round(-0.5, 0) == 0)
assert(round(1.5, 0) == 2)
assert(round(-1.5, 0) == -2)
assert(round(0.05, 1) == 0)
assert(round(-0.05, 1) == 0)
assert(round(0.15, 1) == 0.2)
assert(round(-0.15, 1) == -0.2)

我知道我的回答没有处理实际问题的第三种情况,但是为了符合 IEEE-754,我的方法是有意义的。所以我希望结果取决于在 FPU 中设置的当前舍入模式,FE_TONEAREST并且是默认值。这就是为什么在设置之后FE_TOWARDZERO(但是你可以在 Lua 中这样做)这个解决方案很可能会返回问题中要求的结果。

于 2021-06-14T19:18:29.323 回答
0

尝试使用math.ceil(number + 0.5)This is based on this Wikipedia page。如果我是正确的,这只是舍入正整数。你需要math.floor(number - 0.5)为底片做。

于 2021-08-27T05:16:37.757 回答
0

如果它对任何人有用,我已经散列出一个通用版本的LUA's逻辑,但这次是truncate()

**强调文本预先为不知道 lua 语法而道歉,所以这是在 AWK/lua 混合中,但希望它应该足够直观

-- due to lua-magic alrdy in 2^(52-to-53) zone,
-- has to use a more coarse-grained delta than
-- true IEEE754 double machineepsilon of 2^-52
function trunc_lua(x,s) {
    return \
      ((x*(s=(-1)^(x<-x)) \
           - 2^-1 + 2^-50 \  -- can also be written as
                          \  --    2^-50-5^0/2
           -  _LUAMAGIC  \   -- if u like symmetric 
                         \   --    code for fun
           +  _LUAMAGIC \
      )  *(s) };

它本质上与舍入的概念相同,但强制处理正值区域中的所有输入,偏移量为 -1*(0.5-delta)。我能达到的最小增量是2^-52 ~ 2.222e-16.

lua-magic 值必须在所有这些预处理步骤之后出现,否则可能会发生精度损失。最后,恢复输入的原始符号。

2个“乘法”只是低开销的符号翻转。对于最初的负值,符号翻转 4 次(2 次手动翻转和往返尾数结尾的往返),而 any x >= 0,包括 -0.0 的翻转,仅翻转两次。避免了所有三次函数调用、浮点除法和整数模数,仅对 x<0 进行 1 次条件检查。

使用说明:

  • (1) 不对输入执行无效或恶意负载检查,
  • (2) 不使用快速查零,
  • (3) 不检查可能导致该逻辑没有意义的极端输入,并且
  • (4) 不尝试漂亮地格式化值
于 2021-08-28T20:53:09.827 回答