Hey guys!

In this article, I will share with you some tips and explanations about number rounding in SQL Server using ROUND, FLOOR, CEILING and also using a custom function to meet the definitions of the ABNT NBR 5891 standard.

## Introduction

Present in virtually all information systems, rounding functions are widely used to handle fractional and decimal numbers, especially currency ($). Because of this importance, it is very important that the rounding rules are very well understood so that there are no inconsistencies when rounding is applied to numbers, especially in large amounts of amounts.

To meet these needs, SQL Server provides us with 3 rounding functions:

- FLOOR: Returns an integer, always rounded down, ie returns the integer part of the entered decimal number.
- CEILING: Returns an integer value, always rounding up, that is, the integer + 1 part of the entered decimal number (if the decimal value is> 0)
- ROUND: Returns a decimal value, rounding to the number of decimal places specified in the function. By default, if the decimal place is <= 4, it will be rounded down. If the decimal place is> = 5, it will be rounded up.
This behavior can be changed by the 3 function parameter, which, when entered the value 0, will perform the truncation of the data instead of rounding, ie 10.999 with 2 decimal places would be 10.99.

However, when we have a system that needs to follow the ABNT NBR 5891 standard, we have a problem because the above functions do not meet the standard criterion, which is defined as follows:

#### Rounding Rules

The rounding rules, following the ABNT NBR 5891 Standard, apply to decimal digits in the position following the number of decimal digits you wish to transform, ie if we have a number of 4, 5, 6, n decimal digits and we want to round to 2, these rounding rules will apply:

If the following decimal digits are **smaller than 50, 500, 5000**…, The former does not change.

If the following decimal digits are **larger than 50, 500, 5000**…, The previous one increments by one.

If the following decimal digits are **equal to 50, 500, 5000**…, The above is verified; if it is even, the former does not change; if it is odd, the previous one increases by one.

**Examples**

Rounding to 2 decimal digits we should take into account the third and fourth decimal. Thus, according to the previous rules:

The number 12,6**529** would be rounded to 12,6**5** (here is 12.65, since 29 is less than 50, so it doesn't change)

The number 12,8**6512** would be rounded to 12,8**7** (here is 12.87, since 512 is greater than 500, so one unit increments)

The number 12,7**44623** would be rounded to 12,7**4** (here is 12.74, since 4623 is less than 5000, so it doesn't change)

The number 12,8**752** would be rounded to 12,8**8** (here is 12.88, since 52 is greater than 50, so one unit increments)

The number 12,8**150** would be rounded to 12,8**2** (here is 12.82, since the following digits is equal to 50 and the previous one is odd, in this case 1, so one unit is incremented)

The number 12,8**050** would be rounded to 12,8**0** (here is 12.80, since the following digits equals 50 and the previous one is even, in this case 0, so the previous one doesn't change)

The 13,4 Number**666**…, If we were to round the whole part, it will always be rounded to 13, because 4666… will always be less than 5000… (If we do number-by-number rounding, we would have: 13,4666… → 13,47 → 13,5 → 14, but that would be to say that 13,4666… is closer to 14 than it is to 13, which is not true. **Therefore, we should not round the number previously rounded !!!**)

*Reference: https://en.wikipedia.org/wiki/Rounding*

## Number rounding with FLOOR, ROUND and CEILING

As noted in the introduction to this article, SQL Server provides us with 3 rounding functions:

- FLOOR: Returns an integer, always rounded down, ie returns the integer part of the entered decimal number.
- CEILING: Returns an integer value, always rounding up, that is, the integer + 1 part of the entered decimal number (if the decimal value is> 0)
- ROUND: Returns a decimal value, rounding to the number of decimal places specified in the function. By default, if the decimal place is <= 4, it will be rounded down. If the decimal place is> = 5, it will be rounded up.
This behavior can be changed by the 3 function parameter, which, when entered the value 0, will perform the truncation of the data instead of rounding, ie 10.999 with 2 decimal places would be 10.99.

I will demonstrate some examples to make it easier to understand the difference of these functions:

1 2 3 4 | SELECT CAST(CEILING(10.4925) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.4925) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.4925, 2) AS [Round] |

1 2 3 4 | SELECT CAST(CEILING(10.0001) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.0001) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.0001, 2) AS [Round] |

1 2 3 4 | SELECT CAST(CEILING(10.5000) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.5000) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.5000, 2) AS [Round] |

In this example, we will use the 3th parameter of the ROUND () function to force rounding up (0 = standard rounding, ie 0 to 4 rounds down and 5 to 9 rounds up) and truncation (1 = Truncating, ie does not round, simply cuts the decimal places above the limit specified in the function).

1 2 3 4 5 6 | SELECT CAST(CEILING(10.9999) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.9999) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.9999, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(10.9999, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(10.9999, 2, 0)) AS Round_Up |

1 2 3 4 5 6 | SELECT CAST(CEILING(10.235) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.235) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.235, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(10.235, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(10.235, 2, 0)) AS Round_Up |

1 2 3 4 5 6 | SELECT CAST(CEILING(10.225) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.225) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.225, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(10.225, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(10.225, 2, 0)) AS Round_Up |

As you can see, the ROUND () function meets 2 of the 3 rules defined in the ABNT standard, but in the example of the value 10.225, according to the standard, rounding should have been done for 10,22 and not 10,23.

## Number rounding following ABNT NBR 5891

As we saw in the previous examples, when we fell into the rule **“If the following decimal digits are equal to 50, 500, 5000…, the above is true; if it is even, the former does not change; if it is odd, the previous one increases by one. ”**, SQL Server standard rounding functions do not meet this need.

For this, I will provide the function below, which can fully comply with the rounding definitions of the standard ABNT NBR 5891:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | CREATE FUNCTION dbo.fncArredondamento_ABNT ( @Valor NUMERIC(38, 16) ) RETURNS NUMERIC(38, 16) AS BEGIN DECLARE @Parte_Inteira INT = ROUND(@Valor, 0, 1), @Parte_Decimal NUMERIC(38, 16) = @Valor - ROUND(@Valor, 0, 1), @Nova_Parte_Decimal NUMERIC(38, 16), @SegundoDecimal INT, @DoisPrimeirosDecimais AS NUMERIC(18, 2), @RestanteDosDecimais AS NUMERIC(38, 16) SELECT @SegundoDecimal = SUBSTRING(CAST(@Parte_Decimal AS VARCHAR(40)), 4, 1), @DoisPrimeirosDecimais = '0.' + SUBSTRING(CAST(@Parte_Decimal AS VARCHAR(40)), 3, 2), @RestanteDosDecimais = '0.' + SUBSTRING(CAST(@Parte_Decimal AS VARCHAR(40)), 5, 16) SELECT @Nova_Parte_Decimal = (CASE WHEN @RestanteDosDecimais > 0.5 THEN @DoisPrimeirosDecimais + 0.01 WHEN @RestanteDosDecimais < 0.5 THEN @DoisPrimeirosDecimais ELSE @DoisPrimeirosDecimais + IIF(@SegundoDecimal % 2 = 0, 0.00, 0.01) END) RETURN (@Parte_Inteira + @Nova_Parte_Decimal) END |

#### Examples of use

The number 12,6529 would be rounded to 12,65 (here is 12.65, since 29 is less than 50, so it doesn't change)

1 2 3 4 5 6 7 | SELECT CAST(CEILING(12.6529) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.6529) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.6529, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.6529, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.6529, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.6529) AS [Round_ABNT] |

The number 12,86512 would be rounded to 12,87 (here is 12.87, since 512 is greater than 500, so one unit increments)

1 2 3 4 5 6 7 | SELECT CAST(CEILING(12.86512) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.86512) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.86512, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.86512, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.86512, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.86512) AS [Round_ABNT] |

The number 12,744623 would be rounded to 12,74 (here is 12.74, since 4623 is less than 5000, so it doesn't change)

1 2 3 4 5 6 7 | SELECT CAST(CEILING(12.744623) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.744623) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.744623, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.744623, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.744623, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.744623) AS [Round_ABNT] |

The number 12,8752 would be rounded to 12,88 (here is 12.88, since 52 is greater than 50, so one unit increments)

1 2 3 4 5 6 7 | SELECT CAST(CEILING(12.8752) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.8752) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.8752, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.8752, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.8752, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.8752) AS [Round_ABNT] |

The number 12,8150 would be rounded to 12,82 (here is 12.82, since the following digits equals 50 and the previous one is odd, in this case 1, then one unit is incremented)

1 2 3 4 5 6 7 | SELECT CAST(CEILING(12.8150) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.8150) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.8150, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.8150, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.8150, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.8150) AS [Round_ABNT] |

The number 12,8050 would be rounded to 12,80 (here is 12.80, since the following digits equals 50 and the previous one is even, in this case 0, so the previous one does not change)

1 2 3 4 5 6 7 | SELECT CAST(CEILING(12.8050) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.8050) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.8050, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.8050, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.8050, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.8050) AS [Round_ABNT] |

Well, that's it, guys!

I hope you enjoyed this post and see you next time!

very good!

is this review included in the 2014 review of abnt?

very good!!! congratulations!!!

Thank you