How to round to nearest quarter?

How might I accomplish rounding the decimal portion of a number (using plain AS) to the nearest quarter?
I wrote this, but it’s a little clunky. I’d prefer that 1.245 end up as 1.25, rather than 1.0.


global isDecimal
property numvar : 1.245 as string

set setOff to offset of "." in numvar
set isDecimal to numvar's strings (setOff + 1) thru -1
set Whole to numvar's strings 1 thru setOff
enforceQuarter(isDecimal)


to enforceQuarter(NumString)
	tell NumString
		if it ≥ 0 and it < 25 then
			set isDecimal to "0"
		else
			if it ≥ 25 and it < 50 then
				set isDecimal to "25"
			else
				if it ≥ 50 and it < 75 then
					set isDecimal to "50"
				else
					if it ≥ 75 and it ≤ 99 then
						set isDecimal to "75"
					end if
				end if
			end if
		end if
	end tell
end enforceQuarter

Whole & isDecimal

:slight_smile:

set Num to 1.245
set theint to (round Num rounding down)
set thedec to (round ((Num - theint) * 4)) / 4
set thenum to theint + thedec

Alternatively:

to roundToNearestQuarter(someNumber)
	someNumber as integer
	return result + ((round ((someNumber - result) * 4) rounding as taught in school) / 4)
end roundToNearestQuarter

roundToNearestQuarter(1.245)
--> 1.25

Edit: Also:

to roundToDecimal(someNumber, theReciprocal)
	someNumber as integer
	return result + ((round ((someNumber - result) * theReciprocal) rounding as taught in school) / theReciprocal)
end roundToDecimal

roundToDecimal(1.245, 5)
--> 1.2

:lol:
Applescript is downright funny sometimes!

If speed is important you might want to avoid round n. Because it is an OSAX command, it is much slower than a possible alternative: n as integer.

It looks like prior to AppleScript 1.9.2 (Mac OS X 10.3) coercing a non-integral real number to an integer was an invalid operation (old AppleScript Language Guide: “Only a real value that has no fractional part can be coerced to an integer.”), but these days it rounds (AppleScript 1.9.2 Release Notes: “Real numbers can now be coerced to integers even if they have a non-zero fractional part. Rounding is used to do the coercion.”). I find that as integer seems to round the same way that round n rounding to nearest rounds (including rounding 0.5 to the even side, not always away from zero).

So, I offer the following quarterRounder_3 and some result value comparisons and timing comparisons with cwtnospam’s and Bruce Phillips’s quarter-specific code:

to quarterRounder_1(Num)
	set theint to (round Num rounding down)
	set thedec to (round ((Num - theint) * 4)) / 4
	set thenum to theint + thedec
end quarterRounder_1

to quarterRounder_2(someNumber)
	someNumber as integer
	return result + ((round ((someNumber - result) * 4) rounding as taught in school) / 4)
end quarterRounder_2

to quarterRounder_3(aNum)
	return ((aNum * 4) as integer) / 4 -- In 10.3 and later, "n as integer" is like "round n rounding to nearest" (which is also the default for "round n")
end quarterRounder_3

to testThem(startNum, n, divisor)
	script s
		property testResults : {}
	end script
	repeat with testOffset from 0 to n
		set testValue to startNum + testOffset / divisor
		set {a, b, c} to {quarterRounder_1(testValue), quarterRounder_2(testValue), quarterRounder_3(testValue)}
		set allEqual to a = b and b = c
		if not allEqual then
			set end of s's testResults to {value:testValue, |1|:a, |2|:b, |3|:c}
		end if
	end repeat
	s's testResults
end testThem

to quarterRounder_0(aNum)
	aNum
end quarterRounder_0

to timeThem(startNum, n, divisor)
	set t0 to current date
	repeat with testOffset from 0 to n
		set testValue to startNum + testOffset / divisor
		quarterRounder_0(testValue)
	end repeat
	set t1 to current date
	repeat with testOffset from 0 to n
		set testValue to startNum + testOffset / divisor
		quarterRounder_1(testValue)
	end repeat
	set t2 to current date
	repeat with testOffset from 0 to n
		set testValue to startNum + testOffset / divisor
		quarterRounder_2(testValue)
	end repeat
	set t3 to current date
	repeat with testOffset from 0 to n
		set testValue to startNum + testOffset / divisor
		quarterRounder_3(testValue)
	end repeat
	set t4 to current date
	set nullTime to t1 - t0
	return {base:nullTime, |1|:t2 - t1 - nullTime, |2|:t3 - t2 - nullTime, |3|:t4 - t3 - nullTime}
end timeThem

on run
	--return testThem(-2, 4 * 8, 8) -- demonstrate some discrepancies among the algorithms
	--> {{value:-1.875, |1|:-2, |2|:-1.75, |3|:-2.0}, {value:-1.125, |1|:-1, |2|:-1.25, |3|:-1.0}, {value:-0.875, |1|:-1, |2|:-0.75, |3|:-1.0}, {value:-0.125, |1|:0, |2|:-0.25, |3|:0.0}, {value:0.125, |1|:0, |2|:0.25, |3|:0.0}, {value:0.875, |1|:1, |2|:0.75, |3|:1.0}, {value:1.125, |1|:1, |2|:1.25, |3|:1.0}, {value:1.875, |1|:2, |2|:1.75, |3|:2.0}}
	--return testThem(2.375, 400, 200) -- more individual tests over a smaller numerical domain
	--> {{value:2.875, |1|:3, |2|:2.75, |3|:3.0}, {value:3.125, |1|:3, |2|:3.25, |3|:3.0}, {value:3.875, |1|:4, |2|:3.75, |3|:4.0}, {value:4.125, |1|:4, |2|:4.25, |3|:4.0}}
	timeThem(2.375, 400, 200)
	--> {base:0, |1|:30, |2|:23, |3|:0}
	--> {base:0, |1|:34, |2|:24, |3|:0}
	--> etc.
end run

For all the values I tested, 1 and 3 effectively return the same value (1 returns an integer when possible, 3 always returns a real). 2 uses a different rounding variation, so it differs on some of the eighths. At least on my machine though the speed difference is immense (1 takes about 30 seconds, 2 a bit less than 30 seconds (only one call to round) for 400 roundings; 3 takes less than 1 second for the same 400 roundings).

Model: iBook G4 933
AppleScript: 1.10.7
Browser: Safari Version 3.2.1 (4525.27.1)
Operating System: Mac OS X (10.4)

There are also these from my set of fast rounding handlers in ScriptBuilders:

(* Round to nearest multiple of q (q/2 away from zero) *)
on quantise(n, q)
	(n * 2 div q - n div q) * q
end quantise

quantise(1.245, 0.25)

(* Round to nearest (.5 away from zero) *)
on rnd(n)
	n div 0.5 - n div 1
end rnd

rnd(1.245 * 4) / 4

Shortly after I got into AppleScript and joined the AppleScript-Users mailing list ” back in 1999, when it was Mac OS 8.6 and AS 1.3.7, and ‘round’ only had ‘rounding to nearest’, ‘rounding up’, and ‘rounding down’ parameters ” someone posted a complaint that rounding .5 to nearest sometimes rounded away from zero and sometimes towards zero. The ensuing debate polarised list members into those who considered that the convention of rounding .5 to the nearest even whole number was a standard of the Institute of Electrical and Electronic Engineers, Inc. and therefore correct, and those who’d never heard of that convention and expected .5 to round away from zero. I was one of the latter group and demanded that ‘round’ be fixed “for those who learned their maths at school”. A few months later, the next Standard Additions update gave ‘round’ its ‘rounding as taught in school’ parameter. The name probably wasn’t inspired my post, but I’ve felt bad about it ever since. :wink: It was that, and the discovery that vanilla handlers were very much faster, that inspired me to write the collection linked above.

All of these are great. Thanks, guys.