[REBOL] Rounding function and related mod function
From: greggirwin::mindspring::com at: 7-Nov-2001 11:01
Hi Gang,
Using Ladilsav's rounding functions as a starting point, along with the the
comments posted here, behold...the kitchen sink! Let me know if it produces
incorrect results (and please send me the test case data) or just what you
think. What missing? What's superfluous? What's confusing?
mod: func [
{Compute a remainder.}
value1 [number! money! time!] {The dividend}
value2 [number! money! time!] {The divisor}
/euclid {Compute a non-negative remainder such that: a = qb + r and
r < b}
/local r
] [
either euclid [
either negative? r: value1 // value2 [r + abs value2] [r]
][
value1 // value2
]
]
round: func [
value [number! money! time!] {The value to round}
/up {Round away from 0}
/floor {Round down, towards the next more negative digit}
/ceiling {Round up, towards the next more positive digit}
/truncate {Round down. Remaining digits are unchanged. (a.k.a.
down)}
/places {The number of decimal places to keep}
pl [integer!]
/to-interval {Round to the nearest multiple of interval}
interval [number! money! time!]
/local
factor
][
;-- places and to-interval are redundant. E.g.:
; places 2 = to-interval .01
; to-interval is more flexible so I may dump places.
;-- This sets factor in one line, under 80 chars, but is it clearer?
;factor: either places [10 ** (- pl)][either to-interval
[interval][1]]
factor: either places [
10 ** (negate pl)
][
either to-interval [interval][1]
]
;-- We may set truncate, floor, or ceiling in this 'if block.
if not any [up floor ceiling truncate] [
;-- Default rounding is even. Should we take the specified
; decimal places into account when rounding? We do at the
; moment.
either (abs value // factor) <> (.5 * factor) [
value: (.5 * factor) + value
return value - mod/euclid value factor
][
;-- If we get here, it means we're rounding off exactly
; .5 (at the final decimal position that is).
either even? value [
truncate: true
][
either negative? value [floor: true][ceiling: true]
]
]
]
if up [either negative? value [floor: true][ceiling: true]]
if truncate [return value - (value // factor)]
if floor [return value - mod/euclid value factor]
if ceiling [return value + mod/euclid (negate value) factor]
]
Here are a few test cases:
;-- Even rounding
round -2.5
round -1.5
round -0.5
round 0.5
round 1.5
round 2.5
;-- Round to the nearest quarter
round/to-interval 10.87 .25
round/to-interval 10.88 .25
round/to-interval -10.87 .25
round/to-interval -10.88 .25
;-- Round to the nearest hundred
round/truncate/to-interval 5555.5 100
round/up/to-interval 5555.5 100
Here are some possibilities for alternate help text for refinements:
/euclid {Find R such that A = QB + R and 0<=R<ABS (B)}
/floor {Round downwards}
/ceiling {Round upwards}
/truncate {Round towards zero}
I'm open to any suggestions, particularly those that will make it easier to
use and/or easier to understand the behavior of the various options.
Thanks!
--Gregg