# floor / round / ceil / trunc

Definition: | atom res = floor(atom x) -- or -- atom res = round(atom x, atom inverted_precision=1) -- or -- atom res = ceil(atom x) -- or -- atom res = trunc(atom x)
Description: | floor() returns the greatest integer less than or equal to x. (Round down to an integer, towards -infinity.) round() returns the x rounded to the specified precision. ceil() returns the smallest integer greater than or equal to x. (Round up to an integer, towards +infinity.) trunc() returns the integer portion of x. (Round down to an integer, towards 0.)
pwa/p2js: | Supported.
Comments: | These functions may be applied to an atom or sq_floor(), sq_round(), sq_ceil(), sq_trunc() to all elements of a sequence. The rules for sequence operations apply. floor(a/b) is treated specially for performance reasons, as there is no point storing a floating point intermediate/parameter when the fractional part gets discarded immediately. There is a corresponding sq_floor_div() sequence operation to match that performance enhancement, which yields exactly the same results as sq_floor(sq_div()), only faster. The precision is the inverse of what you might have expected, for example: to round a number to the nearest 0.1, specify an inverted precision of 10, to round a number to the nearest 1000, specify an inverted precision of 0.001. For non-integers, ceil() is one more than floor(), and for integers ceil() returns the same result as floor(), namely the input value unaltered. The result of ceil() is rounded towards plus infinity. Note that while trunc() rounds towards zero, there is no complimentary builtin function that rounds negative fractions away from zero. The result of trunc() is always truncated towards zero, whereas floor truncates towards minus infinity.
Auxillary Function | integer pennies = bankers_rounding(atom pence, integer precison=1) In the standard round() function, exact .5s are rounded up (matching Euphoria) whereas banker’s rounding yields the nearest even number, eg:
    function precision100(atom x) return bankers_rounding(x*100,100) end function
    constant x = {-4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5}
    ?apply(x,round)             -- {-4,-3,-2,-1,0,1,2,3,4,5}
    ?apply(x,bankers_rounding)  -- {-4,-4,-2,-2,0,0,2,2,4,4}
    ?apply(x,precision100)      -- {-400,-400,-200,-200,0,0,200,200,400,400}
The reason you might want to use banker’s rounding is so that the average of the rounded numbers is more likely to match the average of the original numbers (ditto total), whereas always rounding up obviously introduces a systemic bias. Above, the standard rounding average is out by 0.1, whereas the average x and the average bankers are both exactly zero, not that it would be difficult to construct a deliberately biased x such that the opposite were true; you only get better outputs for evenly-dispersed/unbiased inputs. Obviously the use of proper knock-on rounding leads to even better results, for instance {0.6,0.6,0.6,0.6,0.6} => {0.6,0.2,0.8,0.4,1.0} (or {0.6,1.2,1.8,2.4,3.0} if you prefer) => {1,0,1,0,1}, guaranteeing that sum(results)==round(sum(inputs)), but there is as yet no builtin routine for that. Note that this is only possible when working in whole (integer) pennies/cents (and above), not pounds/dollars to 2dp, and the precision is not inverted (see technicalia). Should you want to store things in 2dp format, that’s fine as long as you round(*100) to get exact whole integer penny/cent values back out when you retrieve them, that is prior to attempting any subsequent calculations that are likely to end up requiring any further banker’s rounding, but of course you don’t need to do that if all you’re going to do is print or add and subtract them. It should make no difference whatsoever if you use round() or bankers_rounding() to retrieve the whole integer penny/cent values, since obviously there would be no .5s involved in that.
Examples: |
    ?sq_floor({0.5, -1.6, 9.99, 100}) -- {0, -2, 9, 100}
    ?round(1.4)         -- 1
    ?round(1.6)         -- 2
    ?round(8.136,100)   -- 8.14
    ?ceil(1.4)          -- 2
    ?ceil(-1.4)         -- -1
    ?trunc(1.4)         --  1
    ?floor(1.4)         --  1
    ?trunc(-1.4)        -- -1
    ?floor(-1.4)        -- -2
Implementation: | floor(): via :%opFloor in builtins\VM\pUnary.e (an autoinclude) round(), bankers_rounding(), ceil(), trunc(): see builtins\pmaths.e (an autoinclude) for details of the actual implementation.
See Also: | apply, remainder
Technicalia | The precision is specified in inverted form for a simple practical reason: atoms cannot hold the exact value of a decimal fraction such as 0.01, which is particularly significant when attempting to round values near to the accuracy limits of the physical hardware, where a small deviation in the input can easily manifest as a large discrepancy in the result. While much the same effect can be observed both when rounding to the nearest very small amount as when rounding to the nearest very large amount, any discrepancy in the latter is percentagewise insignificant, and less likely to be multiplied by a very big number, and therefore less critical. Since eg 0.045 cannot be held exactly in an IEEE 754 floating point, it would be rather difficult to coerce it to (almost) 0.04 rather than (almost) 0.05 - it would instead naturally fall depending on whether the nearest admissible representation was out by +/-epsilon. An abs(frac-0.5)<1e-13 or similar rather than frac=0.5 (see pmaths.e) might work, but could introduce another (small) bias of the very same kind that we are trying to eliminate. Best to just stick with integers. There is of course nothing to stop you taking a copy of bankers_rounding() and creating a (renamed) application specific version of it, tweaked however you like. Likewise if bankers_rounding() had an inverted precision, inaccuracies in the representation of (eg) 0.01 could totally spanner it, hence it has an non-inverted and integer precision. The integer return type of bankers_rounding() is quite deliberate.