floating-point-gui.de icon indicating copy to clipboard operation
floating-point-gui.de copied to clipboard

For PHP, real unit test scenario - can be added to docs ;)

Open LocalHeroPro opened this issue 2 years ago • 0 comments

Unit test file

<?php

declare(strict_types=1);

namespace Tests\Unit;

use App\Traits\FloatComparison;
use PHPUnit\Framework\TestCase;

class FloatComparisonTest extends TestCase
{
    use FloatComparison;

    public function testV1(): void
    {
        $toCheck = (float) '1714.23';
        $items = [ // sum of that is 1714.23
            73.03,
            59.74,
            90.03,
            67.5,
            33.07,
            87.83,
            73.03,
            47.15,
            66.12,
            67.5,
            49.56,
            92.24,
            47.65,
            12.14,
            50.77,
            80.84,
            49.56,
            45.64,
            73.03,
            38.36,
            97.21,
            11.96,
            37.86,
            87.83,
            5.02,
            91.97,
            90.03,
            8.41,
            33.07,
            46.08,
        ];
        $amount = 0;

        foreach ($items as $item) {
            $amount += $item;
        }

        self::assertTrue(self::nearlyEqual($toCheck, $amount));
    }
}

Assertion file:

<?php

declare(strict_types=1);

namespace App\Traits;

trait FloatComparison
{
    /**
     * @link https://floating-point-gui.de/errors/comparison/
     *
     * @param float $a // 1714.23
     * @param float $b // 1714.23
     *
     * @param float $epsilon // 2.2204460492503E-16
     * @param float $floatMin // 2.2250738585072e-308
     * @param floatMax // 1.7976931348623157E+308
     *
     * @return bool
     */
    public static function nearlyEqual(float $a, float $b): bool
    {
        $absA = (float) abs($a); // 1714.23
        $absB = (float) abs($b); // 1714.23
        $diff = (float) abs($a - $b); // 2.2737367544323E-13

        if ($a == $b) { // shortcut, handles infinities
            return true;
        }

        if ($a == 0 || $b == 0 || ($absA + $absB < PHP_FLOAT_MIN)) {
            // a or b is zero or both are extremely close to it
            // relative error is less meaningful here
            return $diff < (PHP_FLOAT_EPSILON * PHP_FLOAT_MIN);
        }

        // use relative error
        return $diff / min(($absA + $absB), PHP_FLOAT_MAX) < PHP_FLOAT_MIN;
    }
}

LocalHeroPro avatar Nov 11 '22 13:11 LocalHeroPro