PHP Rounding Numbers

Chapter 14 15 mins

Learning outcomes:

  1. The round() function
  2. The floor() and ceil() functions

Introduction

Rounding is an extremely mainstream operation when it comes to numbers in any programming language.

Needless to say, rounding is a concern typically associated with floating-point numbers, not with integers. Who wants to round integers anyways?

In this chapter, we aim to explore all the different ways to round floating-point numbers in PHP, particularly using the round() function with its different modes and the functions floor() and ceil().

We'll also see how to use round() to round a number to given decimal places, and in some cases, to given significant figures.

The round() function

PHP provides a goto function to round floating-point numbers to a given precision.

Here's how it looks:

round($number, $precision = 0, $mode = PHP_ROUND_HALF_UP)

The first $number argument is the number to round, while the second $precision argument is the precision. This second argument is optional and defaults to 0. That is, the number is rounded to the nearest unit.

We'll explore the last $mode argument later below.

$precision can be a positive integer or a negative integer. Here's how $precision works on a given number 1780.5389.

How round() works in PHP

If $precision is 0, the number is rounded to the nearest units as shown in the figure.

If it's positive, the corresponding position to the right is determined and the number rounded to that very position.

For instance, $precision of 1 would round 1780.5389 to 1780.5 while a $precision of 2 would round 1780.5389 to 1780.54. And so on and so forth.

What would round(1780.5389, 3) return?

  • 1780.54
  • 1780.538
  • 1780.539
round(1780.5389, 3) rounds 1780.5389 to the nearest thousandth place (as highlighted) to give the number 1780.539. This goes with choice (C).

Similarly, a negative value of $precision would shift this position leftwards.

For instance, a $precision of -1 would round 1780.5389 to 1780; while a $precision of -2 would round 1780.5389 to 1800.

What would round(1780.5389, -3) return?

  • 1780.0
  • 1800.0
  • 2000.0
round(1780.5389, -3) rounds 1780.5389 to the nearest thousands (as highlighted) to give the number 2000.0. This goes with choice (C).

Simple.

Let's use the round() function on the same number 1780.5389 with different precisions:

<?php

$num = 1780.5389;

echo 'Precision of 5: ', round($num, 5), "\n";
echo 'Precision of 4: ', round($num, 4), "\n";
echo 'Precision of 3: ', round($num, 3), "\n";
echo 'Precision of 2: ', round($num, 2), "\n";
echo 'Precision of 1: ', round($num, 1), "\n";

echo 'Precision of 0: ', round($num, 0), "\n";

echo 'Precision of -1: ', round($num, -1), "\n";
echo 'Precision of -2: ', round($num, -2), "\n";
echo 'Precision of -3: ', round($num, -3), "\n";
echo 'Precision of -4: ', round($num, -4), "\n";
echo 'Precision of -5: ', round($num, -5), "\n";
Precision of 5: 1780.5389 Precision of 4: 1780.5389 Precision of 3: 1780.539 Precision of 2: 1780.54 Precision of 1: 1780.5 Precision of 0: 1781 Precision of -1: 1780 Precision of -2: 1800 Precision of -3: 2000 Precision of -4: 0 Precision of -5: 0

Notice that beyond a precision of 4 here, there is no difference between the rounded numbers. This is because rounding can only happen upto a precision that's equal to or less than the precision of the given number. In the case above, 1780.5389 is to a precision of 4 decimal places and therefore we can't go more precise than that.

Moreover, when the precision is negative and causes the number to be rounded to a position that's not in the number, 0 is returned. This is what happens in round(1780.5389, -4). The very first position in 1780.5389 is represented by a $precision value of -3 — going less than this value is not possible.

Apart from $precision, another useful argument of the round() function is the third $mode argument.

The $mode argument serves to specify the exact rounding behavior to use on the digit pointed by the $precision argument when the sequence of digits after it form a half value.
Remember that the mode only applies when the sequence of digits after the position where to round to is a perfect half.

For instance, it would be applied on the numbers 1.35 (in round(1.35, 1)), 1.5 (in round(1.5)), 2750 (in round(2750, -2)), where the sequence of digits highlighted after the desired position form a perfect half. However, it would NOT be applied on the numbers 1.352, 1.5005, 2751.

The four possible modes are given by the following four constants.

ModePurpose
PHP_ROUND_HALF_UPRound the previous digit to the next digit.
PHP_ROUND_HALF_DOWNRound the previous digit to the previous digit.
PHP_ROUND_HALF_EVENRound the previous digit to the next or previous digit, whichever is even.
PHP_ROUND_HALF_ODDRound the previous digit to the next or previous digit, whichever is odd.

$mode is an optional parameter which, if omitted, defaults to PHP_ROUND_HALF_UP.

Let's consider a couple of examples.

In the code below, we round the number 1.35 to 1 decimal place (since that's when the next digit 5 would be considered in the rounding phase) in all the aforementioned modes:

<?php

$num = 1.35;

echo 'Round up: ', round($num, 1, PHP_ROUND_HALF_UP), "\n";
echo 'Round down: ', round($num, 1, PHP_ROUND_HALF_DOWN), "\n";
echo 'Round to even: ', round($num, 1, PHP_ROUND_HALF_EVEN), "\n";
echo 'Round to odd: ', round($num, 1, PHP_ROUND_HALF_ODD), "\n";
Round up: 1.4 Round down: 1.3 Round to even: 1.4 Round to odd: 1.3

Similarly, below we round the number 2650 to the nearest hundreds (since that's when the next digit 5 would be considered in the rounding phase) in all the modes:

<?php

$num = 2650;

echo 'Round up: ', round($num, -2, PHP_ROUND_HALF_UP), "\n";
echo 'Round down: ', round($num, -2, PHP_ROUND_HALF_DOWN), "\n";
echo 'Round to even: ', round($num, -2, PHP_ROUND_HALF_EVEN), "\n";
echo 'Round to odd: ', round($num, -2, PHP_ROUND_HALF_ODD), "\n";
Round up: 2700 Round down: 2600 Round to even: 2600 Round to odd: 2700

Note that, as stated before, if the sequence of digits after the digit given by $precision is not a perfect half, the mode won't matter.

This can be seen as follows. The code is the same as before except for adding changing the number from 2650 to 2651:

<?php

$num = 2651;

echo 'Round up: ', round($num, -2, PHP_ROUND_HALF_UP), "\n";
echo 'Round down: ', round($num, -2, PHP_ROUND_HALF_DOWN), "\n";
echo 'Round to even: ', round($num, -2, PHP_ROUND_HALF_EVEN), "\n";
echo 'Round to odd: ', round($num, -2, PHP_ROUND_HALF_ODD), "\n";
Round up: 2700 Round down: 2700 Round to even: 2700 Round to odd: 2700

As we know, 51 is not a perfect half and hence when PHP tries to round the digit 6 (as given by the $precision argument -2), it is rounded upwards to 7 to finally give the number 2700 (since 51 is above the half mark).

Floor and ceil

As you start to learn to implement various algorithms and data structures in PHP, you'll start to see the applications of two very common ideas from number theory — floor and ceil.

Both of these are essentially mathematical functions that round a given number to a particular integer. Let's see what both of them exactly do.

The floor function rounds a given number to the largest integer less than or equal to the number. It's denoted as ::\lfloor x \rfloor::, where ::x:: is the number to floor.

For instance, ::\lfloor -10 \rfloor:: gives ::-10::, ::\lfloor -3.2 \rfloor:: gives ::-4::, ::\lfloor 5.02 \rfloor:: gives ::5::, ::\lfloor 15.99 \rfloor:: gives ::15::, ::\lfloor 10 \rfloor:: gives ::10::, and so on.

In contrary to this:

The ceil function rounds a given number to the smallest integer greater than or equal to the number. It's denoted as ::\lceil x \rceil::, where ::x:: denotes the number to ceil.

For instance, ::\lceil -10 \rceil:: gives ::-10::, ::\lceil -3.2 \rceil:: gives ::-3::, ::\lceil 5.02 \rceil:: gives ::6::, ::\lceil 15.99 \rceil:: gives ::16::, ::\lceil 10 \rceil:: gives ::10::, and so on.

In PHP, the functions floor() and ceil() allow us to perform these very mathematical operations on given numbers.

Consider the code below where we use the floor() function on the same values as shown above:

<?php

echo floor(-10), "\n";
echo floor(-3.2), "\n";
echo floor(5.02), "\n";
echo floor(15.99), "\n";
echo floor(10), "\n";
-10 -4 5 15 10

Now, time for the ceil() function:

<?php

echo ceil(-10), "\n";
echo ceil(-3.2), "\n";
echo ceil(5.02), "\n";
echo ceil(15.99), "\n";
echo ceil(10), "\n";
-10 -3 6 16 10

Easy, wasn't this?

floor() and ceil() in PHP return floats!

Note that contrary to what one might expect, the floor() and ceil() functions both return back a float, NOT an integer!

<?php

var_dump(floor(10.5));
var_dump(ceil(10.5));
float(10) float(11)

This is extremely important to keep in mind.

Many languages provide similar floor() and ceil() functions but they return back integers. PHP, however, takes a different approach here.

But why?

Well, as per the official documentation, this is done to enable representing values that otherwise couldn't be represented in the native int type.

This approach only benefits 32-bit machines. On 32-bit machines, the maximum integer possible in the native int type is 2147483647. However, the maximum integer possible in the float type is 9007199254740991.

64-bit machines, on the other hand, don't really benefit from this approach. This is because the maximum value of the int type on a 64-bit machine is larger than the maximum integer capable to be represented in the float format.

However, this isn't a bad approach even from the perspective of a 64-bit machine.

This is because floats larger than the maximum int value can't really be precise integers and hence, in any way, there would be no advantage of making the return type of floor() or ceil() an integer.