Recent PostsRecent Posts Popular TopicsPopular Topics
 Home Search Members Calendar Who's On

 Bankers Rounding Rate Topic Display Mode Topic Options
Author
 Message
 Posted Thursday, May 31, 2007 12:05 PM
 SSC-Addicted Group: General Forum Members Last Login: Friday, March 08, 2013 11:15 AM Points: 440, Visits: 1,785
 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 *            FROM   sysobjects            WHERE  name = N'fn_BRound')   DROP FUNCTION fn_BRound GO CREATE FUNCTION fn_BRound                (@p1 float(53),@p2 int) RETURNS money /* 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.Examples:    * 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 */ AS   BEGIN     Declare  @Temp float(53),@FixTemp float(53)          Select @Temp = @p1 * @p2          --not right!     -- 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       Begin         If @FixTemp / 2 <> Floor(@FixTemp / 2)  -- Is Temp odd           -- Reduce Magnitude by 1 to make even           Select @FixTemp = @FixTemp - Sign(@p1)       End          Return @FixTemp / @p2   END GO 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 SELECT dbo.fn_BRound(3.016,100) SELECT dbo.fn_BRound(3.013,100) SELECT dbo.fn_BRound(3.015,100) SELECT dbo.fn_BRound(3.045,100) SELECT dbo.fn_BRound(3.04501,100)  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.Dave J http://glossopian.co.uk/"I don't know what I don't know."
Post #370393
 Posted Thursday, May 31, 2007 12:09 PM
 SSC-Insane Group: General Forum Members Last Login: Yesterday @ 11:07 PM Points: 21,625, Visits: 27,468
 Without digging into myself at the moment, I'd say the problem is using float.  This is an imprecise numeric representation.  Try changing the floats to a large decimal, and see if you still have the same problem.If I get some free time I will try it out myself.
Post #370395
 Posted Thursday, May 31, 2007 12:18 PM
 SSC-Addicted Group: General Forum Members Last Login: Friday, March 08, 2013 11:15 AM Points: 440, Visits: 1,785
 Wow! - quick response! Replaced all the float declares with decimal(38,20) but I still get bad results                      --------------------- 511.9500                      --------------------- 512.9500                     --------------------- 578.9500                     --------------------- 654.9500-------------------- 655.9500                      --------------------- 3.0200                      --------------------- 3.0100                      --------------------- 3.0200                      --------------------- 3.0500                      --------------------- 3.0500Now the examples from wikipedia are broke too I tried the money data type but that didn't work either, I think it's the algorithm, which I pinched 'cos I'm too stupid to write my own CheersDave J http://glossopian.co.uk/"I don't know what I don't know."
Post #370400
 Posted Thursday, May 31, 2007 2:14 PM
 SSCrazy Eights Group: Moderators Last Login: Tuesday, April 09, 2013 12:53 PM Points: 8,357, Visits: 684
 Yeah, but decimal(30,20) works perfectly in the examples. 511.9400512.9400578.9400654.9400655.94003.02003.01003.02003.04003.0500
Post #370427
 Posted Thursday, May 31, 2007 2:22 PM
 SSCarpal Tunnel Group: General Forum Members Last Login: Today @ 4:02 AM Points: 4,553, Visits: 8,208
 Here is the mistake:... Floor(Abs(@Temp + 0.5 * Sign(@p1)))
Post #370431
 Posted Thursday, May 31, 2007 3:03 PM
 SSC-Insane Group: General Forum Members Last Login: Yesterday @ 11:07 PM Points: 21,625, Visits: 27,468
 Here is what I came up with:CREATE FUNCTION dbo.fn_BRound (    @TestValue decimal(38,12),    @pos int)RETURNS moneyas begin    return round(@TestValue, @pos, case when (nullif(round(@TestValue * power(cast(10 as decimal(38,12)), @pos),0,1),round((@TestValue * power(cast(10 as decimal(38,12)), @pos))/2,0,1) * 2) is not null) or (nullif(round((@TestValue * power(cast(10 as decimal(38,12)), @pos)),0,1),round((@TestValue * power(cast(10 as decimal(38,12)), @pos))/2,0,1) * 2) is null and (@TestValue * power(cast(10 as decimal(38,12)), @pos)) - round((@TestValue * power(cast(10 as decimal(38,12)), @pos)),0,1) > .5) then 0 else 1 end)endI also changed it to use the format of the round statement.  If you want to round to the hundreths you call it as dbo.fn_BRound(test_value, 2).
Post #370444
 Posted Thursday, May 31, 2007 4:43 PM
 SSC-Dedicated Group: General Forum Members Last Login: Yesterday @ 7:51 PM Points: 32,910, Visits: 26,800
 Heh... I've been all through this on one unbelievably long post before and then I struck gold quite by accident... Unbeknowst to most, the STR function does Banker's rounding "auto-magically"...  try it... `SELECT STR(3.016,10,2)   --rounded to hundredths is 3.02 (because the next digit (6) is 6 or more) SELECT STR(3.013,10,2)   --rounded to hundredths is 3.01 (because the next digit (3) is 4 or less) SELECT STR(3.015,10,2)   --rounded to hundredths is 3.02 (because the next digit is 5, and the hundredths digit (1) is odd) SELECT STR(3.045,10,2)   --rounded to hundredths is 3.04 (because the next digit is 5, and the hundredths digit (4) is even) SELECT STR(3.04501,10,2) --rounded to hundredths is 3.05 (because the next digit is 5, but it is followed by non-zero digits) ``{EDIT} Please disregard this... Lynn Petis found a fly in the ointment and I can't explain it. Sorry, folks.``{2nd Edit}... Serqiy figured out the error... I mistook rounding of the underlying FLOATS for Bankers' Rounding ` --Jeff Moden"RBAR is pronounced "ree-bar" and is a "Modenism" for "Row-By-Agonizing-Row".First step towards the paradigm shift of writing Set Based code: Stop thinking about what you want to do to a row... think, instead, of what you want to do to a column." For better, quicker answers on T-SQL questions, click on the following... http://www.sqlservercentral.com/articles/Best+Practices/61537/For better answers on performance questions, click on the following... http://www.sqlservercentral.com/articles/SQLServerCentral/66909/
Post #370462
 Posted Thursday, May 31, 2007 4:51 PM
 SSC-Dedicated Group: General Forum Members Last Login: Yesterday @ 7:51 PM Points: 32,910, Visits: 26,800
 p.s.Heh... looks like David and I got our rounding examples from the same source...http://en.wikipedia.org/wiki/Bankers%27_rounding#Round-to-even_method  --Jeff Moden"RBAR is pronounced "ree-bar" and is a "Modenism" for "Row-By-Agonizing-Row".First step towards the paradigm shift of writing Set Based code: Stop thinking about what you want to do to a row... think, instead, of what you want to do to a column." For better, quicker answers on T-SQL questions, click on the following... http://www.sqlservercentral.com/articles/Best+Practices/61537/For better answers on performance questions, click on the following... http://www.sqlservercentral.com/articles/SQLServerCentral/66909/
Post #370463
 Posted Thursday, May 31, 2007 5:59 PM
 SSC-Insane Group: General Forum Members Last Login: Yesterday @ 11:07 PM Points: 21,625, Visits: 27,468
 Am I doing something wrong here: select str(612.945, 10, 2) -- returns 612.95, not 612.94
Post #370479
 Posted Thursday, May 31, 2007 6:21 PM
 SSC-Dedicated Group: General Forum Members Last Login: Yesterday @ 7:51 PM Points: 32,910, Visits: 26,800
 Yeah... the definitions on the reference I made are not exactly correct... here's the long post I was talking about... the 3rd post (Andrew) on the first page on the following thread explains it much more simply as does the 3rd from the last post (Jeff Moden) on the last page...http://www.sqlservercentral.com/forums/shwmessage.aspx?forumid=8&messageid=246556`{Edit}... Sorry folks... I posted some bad info... Serqiy figured out the error... I mistook rounding of the underlying FLOATS for Bankers' Rounding ` --Jeff Moden"RBAR is pronounced "ree-bar" and is a "Modenism" for "Row-By-Agonizing-Row".First step towards the paradigm shift of writing Set Based code: Stop thinking about what you want to do to a row... think, instead, of what you want to do to a column." For better, quicker answers on T-SQL questions, click on the following... http://www.sqlservercentral.com/articles/Best+Practices/61537/For better answers on performance questions, click on the following... http://www.sqlservercentral.com/articles/SQLServerCentral/66909/
Post #370481

 Permissions