Skip to content

Conversation

artumi-richard
Copy link
Contributor

To find the median() the middle element is required in a list. Before this change that was achieved by counting the elements and dividing by 2 using the / operator.

Finding the middle of a list of values using the float div operator (/) is wrong, and can be shown to go wrong at some higher values where the int/2 is not representable as a float to single digit precision.

On a 64bit system it starts going wrong around 2**53, which means the impact of this bug is on collections with more than 2**53 elements. And the impact is that the wrong median value will be out by one.

This is solved with intdiv(). https://www.php.net/manual/en/function.intdiv.php . Here's a proof of the issue:

<?php

function test($i)
{
    $v = $i + 2 ** 53;
    $a1 = (int) ($v / 2);
    $a2 = intdiv($v, 2);

    printf("i = %d, //2**53 + %d\n", $v, $i);
    printf("(int)(i/2) = %d\n", $a1);
    printf("intdiv(i,2) = %d\n", $a2);
    echo ($a1 === $a2 ? "✅ same\n\n" : "❌ DIFFERENT!\n\n");
}

for ($i = 0; $i < 12; $i++) {
    test($i);
}

Which produces the output:

i = 9007199254740992, //2**53 + 0
(int)(i/2) = 4503599627370496
intdiv(i,2) = 4503599627370496
✅ same

i = 9007199254740993, //2**53 + 1
(int)(i/2) = 4503599627370496
intdiv(i,2) = 4503599627370496
✅ same

i = 9007199254740994, //2**53 + 2
(int)(i/2) = 4503599627370497
intdiv(i,2) = 4503599627370497
✅ same

i = 9007199254740995, //2**53 + 3
(int)(i/2) = 4503599627370498
intdiv(i,2) = 4503599627370497
❌ DIFFERENT!

i = 9007199254740996, //2**53 + 4
(int)(i/2) = 4503599627370498
intdiv(i,2) = 4503599627370498
✅ same

i = 9007199254740997, //2**53 + 5
(int)(i/2) = 4503599627370498
intdiv(i,2) = 4503599627370498
✅ same

i = 9007199254740998, //2**53 + 6
(int)(i/2) = 4503599627370499
intdiv(i,2) = 4503599627370499
✅ same

i = 9007199254740999, //2**53 + 7
(int)(i/2) = 4503599627370500
intdiv(i,2) = 4503599627370499
❌ DIFFERENT!

i = 9007199254741000, //2**53 + 8
(int)(i/2) = 4503599627370500
intdiv(i,2) = 4503599627370500
✅ same

i = 9007199254741001, //2**53 + 9
(int)(i/2) = 4503599627370500
intdiv(i,2) = 4503599627370500
✅ same

i = 9007199254741002, //2**53 + 10
(int)(i/2) = 4503599627370501
intdiv(i,2) = 4503599627370501
✅ same

i = 9007199254741003, //2**53 + 11
(int)(i/2) = 4503599627370502
intdiv(i,2) = 4503599627370501
❌ DIFFERENT!

This has been my favourite out by one error for some time!

Finding the middle of a list of values using the float div operator (/) is wrong, and goes wrong at some higher values where the int/2 is not representable as a float to single digit precision. 

That's why intdiv() exists.
@taylorotwell taylorotwell merged commit 4e42ded into laravel:12.x Sep 23, 2025
65 checks passed
tegos pushed a commit to tegos/laravel-framework that referenced this pull request Sep 28, 2025
Finding the middle of a list of values using the float div operator (/) is wrong, and goes wrong at some higher values where the int/2 is not representable as a float to single digit precision. 

That's why intdiv() exists.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants