JXA vs. AppleScript - round

The only built-in JavaScript rounding function I’ve found so far is Math.round(). The macOS version of this uncompromisingly rounds .5 up, regardless of sign or nearest even. It’s as if it simpy adds 0.5 and truncates the result:

// JavaScript code.

Math.round(4.5); // --> 5
Math.round(-4.5); // --> -4

By contrast, AppleScript’s 'as integer" coercion and the StandardAdditions ‘round’ command both observe the IEEE standard whereby .5 is rounded to the nearest even integer. And even when rounding ‘as taught in school’, ‘round’ rounds both positive and negative .5s away from 0:

-- AppleScript code.

4.5 as integer --> 4
5.5 as integer --> 6
-4.5 as integer --> 4
-5.5 as integer --> -6

round 4.5 --> 4
round -4.5 --> -4
round 4.5 rounding as taught in school --> 5
round -4.5 rounding as taught in school --> -5

If AppleScript-like rounding is required in a JXA script, it’s possible to use the StandardAdditions ‘round’:

// JavaScript code.

var app = Application.currentApplication();
app.includeStandardAdditions = true;

app.round(4.5) // --> 4
app.round(4.5, {rounding: "as taught in school"}) // -> 5

But somewhat faster, and usable in “vanilla” JavaScript as well, are these home-grown functions:

// JavaScript code.

function round(n) {
	return (((n % 2) ** 2 > 0.25) ? roundAsTaughtInSchool(n) : Math.trunc(n));
};

function roundAsTaughtInSchool(n) {
	return Math.trunc(n * 2) - Math.trunc(n) ;
};

round(4.5); // --> 4
round(5.5); // --> 6
roundAsTaughtInSchool(-4.5); // --> -5

The AppleScript equivalents are:

-- AppleScript code.

on |round|(n)
	if ((n mod 2) ^ 2 > 0.25) then
		return roundAsTaughtInSchool(n)
	else
		return n div 1
	end if
end |round|

on roundAsTaughtInSchool(n)
	return n div 0.5 - n div 1
end roundAsTaughtInSchool

|round|(4.5) --> 4
|round|(5.5) --> 6
roundAsTaughtInSchool(-4.5) --> -5

Arguably Math.ceil() and Math.floor() are also rounding functions.

I’d regard those more as “nudge” functions. :wink: I think the woman who asked the estate agent about my house on Friday must have used the latter several times when working out what fraction of the asking price she’d be able to offer. :confused:

But OK. “The only built-in JavaScript function I’ve found so far which rounds a fractional number either up or down to whichever adjacent integer is the nearer and for which what to do about numbers exactly half-way between those integers is relevant is Math.round().” :stuck_out_tongue:

I think the real estate version is actually the nudge-nudge function :).

I did say “arguably”, although I think any function that turns floating-point values into round numbers has a prima facie case for inclusion in the rounding category ;).

Here are some JavaScript functions to round one number to the “nearest” multiple of another. If the other number’s omitted from the call parameters (which JXA doesn’t seem to mind), the default used is 1, for “nearest integer”.

/*	JXA script.
	Round n in various ways to an adjacent multiple of q.
	For 'nearest integer', the q parameter should be 1.
	However, JXA doesn't mind if parameters are omitted from calls. If the q parameter's omitted, the functions adopt a default q of 1.
	There's obviously no point in omitting the n parameter as well.
	JXA returns whole-number values in integer form.
	The functions below return 'NaN' when meaningful results aren't possible.
*/

// To nearest multiple, IEEE 754 style. (Midway values to nearest /even/ multiple.)
function round(n, q) {
	if (q == undefined) {q = 1;}; // Default to 1 if q not given.
	if (q == 0) {return q;}; // The only possible result if q's 0.
	// If n is more than a quarter of the way (zerofugally) between two consecutive even multiples of q, round "as taught in school". 
	if ((n % (q * 2)) ** 2 > q * q / 4) {return n + n % q - (n * 2) % q;};
	// Otherwise truncate.
	return n - n % q;
};

// To nearest multiple, "as taught in school". (Midway values to nearest multiple away from zero.)
function roundAsTaughtInSchool(n, q) {
	if (q == undefined) {q = 1;};
	if (q == 0) {return q;};
	return n + n % q - (n * 2) % q; // (Math.trunc(n * 2 / q) - Math.trunc(n / q)) * q ;
};

// To nearest multiple zerowards.
function truncate(n, q) {
	if (q == undefined) {q = 1;};
	if (q == 0) {return q;};
	return n - n % q; // Math.trunc(n / q) * q;
};

// To nearest multiple away from zero.
function extend(n, q) {
	if (q == undefined) {q = 1;};
	if ((q == 0) && (n == 0)) {return q;};
	if (n < 0) {return Math.floor(n / q) * q;};
	return Math.ceil(n / q) * q;
};

// To nearest multiple below.
function floor(n, q) {
	if (q == undefined) {q = 1;};
	if ((q == 0) && (n >= 0)) {return q;};
	return Math.floor(n / q) * q;
};

// To nearest multiple above.
function ceiling(n, q) {
	if (q == undefined) {q = 1;};
	if ((q == 0) && (n <= 0)) {return q;};
	return Math.ceil(n / q) * q;
};

// Examples:
round(123456.5) // or round(123456.5, 1) // --> 123456 (nearest integer, IEEE 754 standard)
roundAsTaughtInSchool(123456.5) // or roundAsTaughtInSchool(123456.5, 1) // --> 123457 (ditto, "as taught in school")
round(123456.5, 100) // --> 123500 (nearest hundred)
round(1.234567, 0.001) // --> 1.235 (three decimal places)
truncate(123456.5, 1000) // --> 123000 (nearest thousand towards zero)
extend(123456.5, 1000) // --> 124000 (nearest thousand away from zero)
floor(123456.6, 1000) // --> 123000 (nearest thousand below)
floor(-123456.6, 1000) // --> -124000 (ditto)

And the AS equivalents. Both parameters have to be specified in these:

(*	AppleScript script.
	Round n in various ways to an adjacent multiple of q.
	For 'nearest integer', the q parameter should be 1.
	THE q PARAMETER ISN'T OPTIONAL HERE as it's less trouble to type ", 1" than to use AS's optional-parameter methods!
	Some of the math is slightly different from that in the JXA script to ensure that the AS results are in the same number class as q.
	The last three handlers below return 'missing value' when meaningful results aren't possible.
*)

-- To nearest multiple, IEEE 754 style. (Midway values to nearest /even/ multiple.)
on |round|(n, q)
	if (q = 0) then return q -- The only possible result if q's 0.
	-- If n is more than a quarter of the way (zerofugally) between two consecutive even multiples of q, round "as taught in school".
	if ((n mod (q + q)) ^ 2 > q * q / 4) then return ((n + n) div q - n div q) * q
	-- Otherwise truncate.
	return n div q * q
end |round|

-- To nearest multiple, "as taught in school". (Midway values to nearest multiple away from zero.)
on roundAsTaughtInSchool(n, q)
	if (q = 0) then return q
	return ((n + n) div q - n div q) * q
end roundAsTaughtInSchool

-- To nearest multiple zerowards.
on truncate(n, q)
	if (q = 0) then return q
	return n div q * q
end truncate

-- To nearest multiple away from zero.
on extend(n, q)
	if (q = 0) then
		if (n = 0) then return q
		return missing value
	end if
	set nTruncated to n div q * q
	if (nTruncated = n) then return nTruncated
	if (q < 0) then set q to -q
	if (n < 0) then return nTruncated - q
	return nTruncated + q
end extend

-- To nearest multiple below.
on floor(n, q)
	if (q = 0) then
		if (n < 0) then return missing value
		return q
	end if
	set nTruncated to n div q * q
	if (nTruncated > n) then
		if (q < 0) then set q to -q
		return nTruncated - q
	end if
	return nTruncated
end floor

-- To nearest multiple above.
on ceiling(n, q)
	if (q = 0) then
		if (n > 0) then return missing value
		return q
	end if
	set nTruncated to n div q * q
	if (nTruncated < n) then
		if (q < 0) then set q to -q
		return nTruncated + q
	end if
	return nTruncated
end ceiling

-- Examples:
|round|(1.234565E+5, 1) --> 1234356 (nearest integer, IEEE 754 standard)
roundAsTaughtInSchool(-1.234565E+5, 1) --> -123457 (ditto, "as taught in school")