Seeing all the activity Grant Fritchey's article "The T-SQL Quiz" has generated, which is well worth a read btw, I thought I'd pose this.
Warning: You'll need to set aside an extended lunch break to work through all the code generated in the responses to the article , but boy will you learn a lot.
I was asked if it was possible to do bankers rounding in SQL. In my usual way, I went off and did a little research (see comments below) before coming up with the following function. The thing is, it has a bug! Hope fully the comments in the code make it clear what's going wrong and where, but if anyone can enlighten me, please do so. My math is too poor! In my defense, if you give the test cases to Excel using the algorithm supplied, it goes wrong too!
-- Create scalar function (FN)
IF EXISTS (SELECT *
WHERE name = N'fn_BRound')
DROP FUNCTION fn_BRound
CREATE FUNCTION fn_BRound
(@p1 float(53),@p2 int)
RETURNS money < ?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Round-to-even method (from http://en.wikipedia.org/wiki/Rounding)
This method is also known as unbiased rounding or as statistician's rounding or as bankers' rounding.
It is identical to the common method of rounding except when the digit(s) following the rounding digit
start with a five and have no non-zero digits after it. The new algorithm is:
* Decide which is the last digit to keep.
* Increase it by 1 if the next digit is 6 or more, or a 5 followed by one or more non-zero digits.
* Leave it the same if the next digit is 4 or less
* Otherwise, all that follows the last digit is a 5 and possibly trailing zeroes;
then change the last digit to the nearest even digit. That is, increase the rounded
digit if it is currently odd; leave it if it is already even.
With all rounding schemes there are two possible outcomes: increasing the rounding digit by one or
leaving it alone. With traditional rounding, if the number has a value less than the half-way mark
between the possible outcomes, it is rounded down; if the number has a value exactly half-way or
greater than half-way between the possible outcomes, it is rounded up. The round-to-even method is the
same except that numbers exactly half-way between the possible outcomes are sometimes rounded up—sometimes down.
Although it is customary to round the number 4.5 up to 5, in fact 4.5 is no nearer to 5 than it is to 4
(it is 0.5 away from either). When dealing with large sets of scientific or statistical data, where trends
are important, traditional rounding on average biases the data upwards slightly. Over a large set of
data, or when many subsequent rounding operations are performed as in digital signal processing, the
round-to-even rule tends to reduce the total rounding error, with (on average) an equal portion of numbers
rounding up as rounding down. This generally reduces the upwards skewing of the result.
Round-to-even is used rather than round-to-odd as the latter rule would prevent rounding to a result of zero.
* 3.016 rounded to hundredths is 3.02 (because the next digit (6) is 6 or more)
* 3.013 rounded to hundredths is 3.01 (because the next digit (3) is 4 or less)
* 3.015 rounded to hundredths is 3.02 (because the next digit is 5, and the hundredths digit (1) is odd)
* 3.045 rounded to hundredths is 3.04 (because the next digit is 5, and the hundredths digit (4) is even)
* 3.04501 rounded to hundredths is 3.05 (because the next digit is 5, but it is followed by non-zero digits)
-- Example to execute function
SELECT dbo.fn_BRound (282.26545, 100) -- 100 to nearest penny, 20 to nearest 5 pence, 10 to nearest 10 pence,
-- 5 to nearest 20 pence, 2 to nearest 50 pence, 1 to nearest pound/dollar/euro
Code (poorly?) converted from VB example @ http://support.microsoft.com/kb/196652
Declare @Temp float(53),@FixTemp float(53)
Select @Temp = @p1 * @p2
-- According to Excel help Fix (used in the algorithm pinched from MS) in VBA is equivalent to
-- Sgn(number) * Int(Abs(number))
-- So why does this not work?
-- Select @Temp = @Temp + 0.5 * Sign(@p1)
-- Select @FixTemp = Sign(@Temp) * Floor(Abs(@Temp))
--This is the closest I can get, but it is still wrong for certain values
Select @FixTemp = Sign(@Temp + 0.5 * Sign(@p1)) * Floor(Abs(@Temp + 0.5 * Sign(@p1)))
-- Handle rounding of .5 in a special manner
If @Temp - Floor(@Temp) = 0.5
If @FixTemp / 2 <> Floor(@FixTemp / 2) -- Is Temp odd
-- Reduce Magnitude by 1 to make even
Select @FixTemp = @FixTemp - Sign(@p1)
Return @FixTemp / @p2
SET NOCOUNT ON
-- Example to execute function
SELECT dbo.fn_BRound(511.945,100) -- Right - 511.94
SELECT dbo.fn_BRound(512.945,100) -- Wrong - 512.95
SELECT dbo.fn_BRound(578.945,100) -- Any number between the one above and below, ending .945
SELECT dbo.fn_BRound(654.945,100) -- Wrong - 654.95
SELECT dbo.fn_BRound(655.945,100) -- Right - 655.94
--examples as mentioned in comment above
This is not urgent, as the requirement has gone away, but it does intrigue me as to what the £$%* is going on. Hint: Make the function return a float and you will see what's rounding up, though I can't figure out why.