Lua: getallen afronden en vervolgens afkappen

Wat is de beste efficiënte manier om een getal naar boven af te ronden en vervolgens af te kappen (verwijder decimalen na het afronden)?

Als decimaal bijvoorbeeld hoger is dan 0,5 (dat wil zeggen 0,6, 0,7, enzovoort), wil ik naar boven afronden en vervolgens afkappen (geval 1). Anders wil ik afkappen (geval 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)

Antwoord 1, autoriteit 100%

Er is geen ingebouwde math.round() functie in Lua, maar je kunt het volgende doen:
print(math.floor(a+0.5)).


Antwoord 2, autoriteit 47%

Een truc die handig is voor het afronden op andere decimale cijfers dan hele gehele getallen, is om de waarde door opgemaakte ASCII-tekst te leiden en de tekenreeks %fte gebruiken om de gewenste afronding te specificeren. Bijvoorbeeld

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

rondt de willekeurige waarde in exactaf op een veelvoud van 0,001.

Een soortgelijk resultaat kan worden verkregen met schalen voor en na het gebruik van math.floor()of math.ceil(), maar de details goed krijgen volgens uw verwachtingen rond de behandeling van randgevallen kunnen lastig zijn. Niet dat dit geen probleem is met string.format(), maar er is veel werk gestoken in het produceren van “verwachte” resultaten.

Afronding naar een veelvoud van iets anders dan een macht van tien vereist nog steeds schaal en heeft nog steeds alle lastige randgevallen. Een benadering die eenvoudig uit te drukken is en stabiel gedrag vertoont, is door te schrijven

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

en tweak de exacte toestand op frac(en mogelijk het teken van exact) om de randvakken te krijgen die u wilde.


Antwoord 3, Autoriteit 45%

Om ondersteunen ook negatieve getallen, gebruik dan deze:

function round(x)
  return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5)
end

Antwoord 4, Autoriteit 21%

Als uw Lua maakt gebruik van dubbele precisie IEC-559 (ook bekend als IEEE-754) drijft, zoals de meeste te doen, en je nummers zijn relatief klein (de methode werkt gegarandeerd voor inputs tussen -2 51 en 2 51 ), de volgende efficiënte code verricht afronding met behulp van de huidige afrondingsmodus van uw FPU, die meestal rond is naar het dichtstbijzijnde, banden tot zelfs:

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

(merk op dat de cijfers tussen haakjes worden berekend op de compilatietijd; ze hebben geen invloed op runtime).

Bijvoorbeeld, wanneer de FPU is ingesteld op rond naar de dichtstbijzijnde of zelfs, wordt deze eenheidstest afgedrukt “Alle tests doorgegeven”:

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")

Hier is een ander (minder efficiënt, natuurlijk) algoritme dat dezelfde FPU-afronding uitvoert, maar werkt voor alle nummers:

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

Antwoord 5, Autoriteit 13%

Hier is een om naar een willekeurig aantal cijfers (0, indien niet gedefinieerd):

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

Antwoord 6, Autoriteit 11%

Voor slechte afronding (het einde afsnijden):

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

Nou, als je wilt, kun je dit uitbreiden voor een goede afronding.

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))

Verwachte resultaten:

3, 3, 42, 5, 5


Antwoord 7, autoriteit 8%

Ik vind het bovenstaande antwoord van RBerteig leuk: mils = tonumber(string.format("%.3f", exact)).
Uitgebreid tot een functieaanroep en een precisiewaarde toegevoegd.

function round(number, precision)
   local fmtStr = string.format('%%0.%sf',precision)
   number = string.format(fmtStr,number)
   return number
end

Antwoord 8, autoriteit 3%

Moet math.ceil(a-0.5)zijn om halve gehele getallen correct te verwerken


Antwoord 9, autoriteit 3%

Hier is een flexibele functie om op verschillende plaatsen af te ronden. Ik heb het getest met negatieve getallen, grote getallen, kleine getallen en allerlei randgevallen, en het is nuttig en betrouwbaar:

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

Antwoord 10

Voor het afronden op een bepaald aantal decimalen (dat ook negatief kan zijn), stel ik de volgende oplossing voor die is gecombineerd met de bevindingen die al als antwoorden zijn gepresenteerd, met name de inspirerende gegeven door Pedro Gimeno. Ik heb een paar hoekcases getest waarin ik geïnteresseerd ben, maar kan niet beweren dat dit deze functie 100% betrouwbaar maakt:

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

Deze gevallen illustreren de eigenschap round-halfway-to-even (die op de meeste machines standaard zou moeten zijn):

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)

Ik ben me ervan bewust dat mijn antwoord het derde geval van de eigenlijke vraag niet behandelt, maar in het voordeel van IEEE-754 compliant te zijn, is mijn aanpak logisch. Dus ik zou verwachten dat de resultaten afhankelijk zijn van de huidige afrondingsmodus die is ingesteld in de FPU met FE_TONEARESTis de standaard. En daarom lijkt het zeer waarschijnlijk dat na het instellen van FE_TOWARDZERO(maar je kunt dat in Lua doen) deze oplossing precies de resultaten oplevert waar in de vraag om werd gevraagd.

Other episodes