Skip to content

Commit 6439b8b

Browse files
mesourdg
authored andcommitted
Added Nette\Utils\Floats class for comparing floats (#221)
1 parent b7f1262 commit 6439b8b

9 files changed

+363
-0
lines changed

src/Utils/Floats.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Utils;
11+
12+
use Nette;
13+
14+
15+
/**
16+
* Floating-point numbers comparison.
17+
*/
18+
class Floats
19+
{
20+
use Nette\StaticClass;
21+
22+
private const EPSILON = 1e-10;
23+
24+
25+
public static function isZero(float $value): bool
26+
{
27+
return abs($value) < self::EPSILON;
28+
}
29+
30+
31+
public static function isInteger(float $value): bool
32+
{
33+
return abs(round($value) - $value) < self::EPSILON;
34+
}
35+
36+
37+
/**
38+
* Compare two floats. If $a < $b it returns -1, if they are equal it returns 0 and if $a > $b it returns 1
39+
* @throws \LogicException if one of parameters is NAN
40+
*/
41+
public static function compare(float $a, float $b): int
42+
{
43+
if (is_nan($a) || is_nan($b)) {
44+
throw new \LogicException('Trying to compare NAN');
45+
46+
} elseif (!is_finite($a) && !is_finite($b) && $a === $b) {
47+
return 0;
48+
}
49+
50+
$diff = abs($a - $b);
51+
if (($diff < self::EPSILON || ($diff / max(abs($a), abs($b)) < self::EPSILON))) {
52+
return 0;
53+
}
54+
55+
return $a < $b ? -1 : 1;
56+
}
57+
58+
59+
/**
60+
* Returns true if $a = $b
61+
* @throws \LogicException if one of parameters is NAN
62+
*/
63+
public static function areEqual(float $a, float $b): bool
64+
{
65+
return self::compare($a, $b) === 0;
66+
}
67+
68+
69+
/**
70+
* Returns true if $a < $b
71+
* @throws \LogicException if one of parameters is NAN
72+
*/
73+
public static function isLessThan(float $a, float $b): bool
74+
{
75+
return self::compare($a, $b) < 0;
76+
}
77+
78+
79+
/**
80+
* Returns true if $a <= $b
81+
* @throws \LogicException if one of parameters is NAN
82+
*/
83+
public static function isLessThanOrEqualTo(float $a, float $b): bool
84+
{
85+
return self::compare($a, $b) <= 0;
86+
}
87+
88+
89+
/**
90+
* Returns true if $a > $b
91+
* @throws \LogicException if one of parameters is NAN
92+
*/
93+
public static function isGreaterThan(float $a, float $b): bool
94+
{
95+
return self::compare($a, $b) > 0;
96+
}
97+
98+
99+
/**
100+
* Returns true if $a >= $b
101+
* @throws \LogicException if one of parameters is NAN
102+
*/
103+
public static function isGreaterThanOrEqualTo(float $a, float $b): bool
104+
{
105+
return self::compare($a, $b) >= 0;
106+
}
107+
}

tests/Utils/Floats.areEqual().phpt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::areEqual()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::true(Floats::areEqual(9, 9));
17+
Assert::true(Floats::areEqual(9, 9.0));
18+
Assert::true(Floats::areEqual(3.0, 3));
19+
Assert::true(Floats::areEqual(0.0, 0));
20+
Assert::true(Floats::areEqual(0.0, 0.0));
21+
Assert::true(Floats::areEqual(0.1 + 0.2, 0.3));
22+
Assert::true(Floats::areEqual(0.1 - 0.5, -0.4));
23+
Assert::false(Floats::areEqual(0.0, 5));
24+
Assert::false(Floats::areEqual(-5, 5));
25+
Assert::false(Floats::areEqual(0.001, 0.01));
26+
27+
$float1 = 1 / 3;
28+
$float2 = 1 - 2 / 3;
29+
Assert::true(Floats::areEqual($float1, $float2));
30+
Assert::true(Floats::areEqual($float1 * 1e9, $float2 * 1e9));
31+
Assert::true(Floats::areEqual($float1 - $float2, 0.0));
32+
Assert::true(Floats::areEqual($float1 - $float2 + 123, $float2 - $float1 + 123));
33+
Assert::true(Floats::areEqual($float1 - $float2, $float2 - $float1));
34+
35+
Assert::true(Floats::areEqual(INF, INF));
36+
Assert::false(Floats::areEqual(INF, -INF));
37+
Assert::false(Floats::areEqual(-INF, INF));
38+
39+
Assert::exception(function () {
40+
Floats::areEqual(NAN, NAN);
41+
}, \LogicException::class);

tests/Utils/Floats.compare().phpt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::compare()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::same(0, Floats::compare(0, 0));
17+
Assert::same(0, Floats::compare(0.0, 0));
18+
Assert::same(0, Floats::compare(0, 0x0));
19+
Assert::same(1, Floats::compare(0, -25.7));
20+
Assert::same(-1, Floats::compare(-2, 30.7));
21+
Assert::same(1, Floats::compare(0.0, -5));
22+
Assert::same(1, Floats::compare(20, 10));
23+
Assert::same(-1, Floats::compare(20, 30));
24+
Assert::same(1, Floats::compare(-20, -30));
25+
Assert::same(-1, Floats::compare(-50, -30));
26+
27+
Assert::exception(function () {
28+
Floats::compare(NAN, -30);
29+
}, \LogicException::class);
30+
31+
Assert::exception(function () {
32+
Floats::compare(6, NAN);
33+
}, \LogicException::class);
34+
35+
Assert::exception(function () {
36+
Floats::compare(NAN, NAN);
37+
}, \LogicException::class);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::isGreaterThan()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::false(Floats::isGreaterThan(-9, 9));
17+
Assert::false(Floats::isGreaterThan(-9.7, 0.0));
18+
Assert::false(Floats::isGreaterThan(10, 150));
19+
Assert::false(Floats::isGreaterThan(0, 0.0));
20+
Assert::false(Floats::isGreaterThan(10, 10));
21+
Assert::false(Floats::isGreaterThan(-50, -50));
22+
Assert::true(Floats::isGreaterThan(170, 150));
23+
Assert::true(Floats::isGreaterThan(170.879, -20));
24+
Assert::true(Floats::isGreaterThan(11.879, 0.0));
25+
26+
Assert::true(Floats::isGreaterThan(INF, -INF));
27+
Assert::false(Floats::isGreaterThan(INF, INF));
28+
Assert::false(Floats::isGreaterThan(-INF, INF));
29+
30+
Assert::exception(function () {
31+
Floats::isGreaterThan(NAN, NAN);
32+
}, \LogicException::class);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::isGreaterOrEqualThan()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::false(Floats::isGreaterThanOrEqualTo(-9, 9));
17+
Assert::false(Floats::isGreaterThanOrEqualTo(-9.7, 0.0));
18+
Assert::false(Floats::isGreaterThanOrEqualTo(10, 150));
19+
Assert::true(Floats::isGreaterThanOrEqualTo(0, 0.0));
20+
Assert::true(Floats::isGreaterThanOrEqualTo(10, 10));
21+
Assert::true(Floats::isGreaterThanOrEqualTo(-50, -50));
22+
Assert::true(Floats::isGreaterThanOrEqualTo(170, 150));
23+
Assert::true(Floats::isGreaterThanOrEqualTo(170.879, -20));
24+
Assert::true(Floats::isGreaterThanOrEqualTo(11.879, 0.0));
25+
26+
Assert::true(Floats::isGreaterThanOrEqualTo(INF, INF));
27+
Assert::true(Floats::isGreaterThanOrEqualTo(INF, -INF));
28+
Assert::false(Floats::isGreaterThanOrEqualTo(-INF, INF));
29+
30+
Assert::exception(function () {
31+
Floats::isGreaterThanOrEqualTo(NAN, NAN);
32+
}, \LogicException::class);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::isInteger()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::true(Floats::isInteger(0));
17+
Assert::true(Floats::isInteger(0.0));
18+
Assert::true(Floats::isInteger(5.0));
19+
Assert::true(Floats::isInteger(-5.0));
20+
Assert::true(Floats::isInteger(-5));
21+
Assert::true(Floats::isInteger((1 - (0.1 + 0.2)) * 10));
22+
Assert::false(Floats::isInteger(-5.1));
23+
Assert::false(Floats::isInteger(0.000001));
24+
Assert::false(Floats::isInteger(NAN));
25+
Assert::false(Floats::isInteger(INF));
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::isLowerThan()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::true(Floats::isLessThan(-9, 9));
17+
Assert::true(Floats::isLessThan(-9.7, 0.0));
18+
Assert::true(Floats::isLessThan(10, 150));
19+
Assert::false(Floats::isLessThan(0, 0.0));
20+
Assert::false(Floats::isLessThan(10, 10));
21+
Assert::false(Floats::isLessThan(-50, -50));
22+
Assert::false(Floats::isLessThan(170, 150));
23+
Assert::false(Floats::isLessThan(170.879, -20));
24+
Assert::false(Floats::isLessThan(11.879, 0.0));
25+
26+
Assert::true(Floats::isLessThan(-INF, INF));
27+
Assert::false(Floats::isLessThan(INF, INF));
28+
Assert::false(Floats::isLessThan(INF, -INF));
29+
30+
Assert::exception(function () {
31+
Floats::isLessThan(NAN, NAN);
32+
}, \LogicException::class);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::isLowerOrEqualThan()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::true(Floats::isLessThanOrEqualTo(-9, 9));
17+
Assert::true(Floats::isLessThanOrEqualTo(-9.7, 0.0));
18+
Assert::true(Floats::isLessThanOrEqualTo(10, 150));
19+
Assert::true(Floats::isLessThanOrEqualTo(0, 0.0));
20+
Assert::true(Floats::isLessThanOrEqualTo(10, 10));
21+
Assert::true(Floats::isLessThanOrEqualTo(-50, -50));
22+
Assert::false(Floats::isLessThanOrEqualTo(170, 150));
23+
Assert::false(Floats::isLessThanOrEqualTo(170.879, -20));
24+
Assert::false(Floats::isLessThanOrEqualTo(11.879, 0.0));
25+
26+
Assert::true(Floats::isLessThanOrEqualTo(-INF, INF));
27+
Assert::true(Floats::isLessThanOrEqualTo(INF, INF));
28+
Assert::false(Floats::isLessThanOrEqualTo(INF, -INF));
29+
30+
Assert::exception(function () {
31+
Floats::isLessThanOrEqualTo(NAN, NAN);
32+
}, \LogicException::class);

tests/Utils/Floats.isZero().phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Floats::isZero()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Floats;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
Assert::true(Floats::isZero(0));
17+
Assert::true(Floats::isZero(0.0));
18+
Assert::true(Floats::isZero(0x0));
19+
Assert::false(Floats::isZero(-12.5));
20+
Assert::false(Floats::isZero(0.2));
21+
Assert::false(Floats::isZero(20));
22+
Assert::false(Floats::isZero(-2));
23+
Assert::false(Floats::isZero(0x5));
24+
Assert::false(Floats::isZero(INF));
25+
Assert::false(Floats::isZero(NAN));

0 commit comments

Comments
 (0)