1<?php
2
3namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
4
5use PhpOffice\PhpSpreadsheet\Calculation\Exception;
6use PhpOffice\PhpSpreadsheet\Calculation\Functions;
7
8class Floor
9{
10    private static function floorCheck1Arg(): void
11    {
12        $compatibility = Functions::getCompatibilityMode();
13        if ($compatibility === Functions::COMPATIBILITY_EXCEL) {
14            throw new Exception('Excel requires 2 arguments for FLOOR');
15        }
16    }
17
18    /**
19     * FLOOR.
20     *
21     * Rounds number down, toward zero, to the nearest multiple of significance.
22     *
23     * Excel Function:
24     *        FLOOR(number[,significance])
25     *
26     * @param mixed $number Expect float. Number to round
27     * @param mixed $significance Expect float. Significance
28     *
29     * @return float|string Rounded Number, or a string containing an error
30     */
31    public static function floor($number, $significance = null)
32    {
33        if ($significance === null) {
34            self::floorCheck1Arg();
35        }
36
37        try {
38            $number = Helpers::validateNumericNullBool($number);
39            $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1);
40        } catch (Exception $e) {
41            return $e->getMessage();
42        }
43
44        return self::argumentsOk((float) $number, (float) $significance);
45    }
46
47    /**
48     * FLOOR.MATH.
49     *
50     * Round a number down to the nearest integer or to the nearest multiple of significance.
51     *
52     * Excel Function:
53     *        FLOOR.MATH(number[,significance[,mode]])
54     *
55     * @param mixed $number Number to round
56     * @param mixed $significance Significance
57     * @param mixed $mode direction to round negative numbers
58     *
59     * @return float|string Rounded Number, or a string containing an error
60     */
61    public static function math($number, $significance = null, $mode = 0)
62    {
63        try {
64            $number = Helpers::validateNumericNullBool($number);
65            $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1);
66            $mode = Helpers::validateNumericNullSubstitution($mode, null);
67        } catch (Exception $e) {
68            return $e->getMessage();
69        }
70
71        return self::argsOk((float) $number, (float) $significance, (int) $mode);
72    }
73
74    /**
75     * FLOOR.PRECISE.
76     *
77     * Rounds number down, toward zero, to the nearest multiple of significance.
78     *
79     * Excel Function:
80     *        FLOOR.PRECISE(number[,significance])
81     *
82     * @param float $number Number to round
83     * @param float $significance Significance
84     *
85     * @return float|string Rounded Number, or a string containing an error
86     */
87    public static function precise($number, $significance = 1)
88    {
89        try {
90            $number = Helpers::validateNumericNullBool($number);
91            $significance = Helpers::validateNumericNullSubstitution($significance, null);
92        } catch (Exception $e) {
93            return $e->getMessage();
94        }
95
96        return self::argumentsOkPrecise((float) $number, (float) $significance);
97    }
98
99    /**
100     * Avoid Scrutinizer problems concerning complexity.
101     *
102     * @return float|string
103     */
104    private static function argumentsOkPrecise(float $number, float $significance)
105    {
106        if ($significance == 0.0) {
107            return Functions::DIV0();
108        }
109        if ($number == 0.0) {
110            return 0.0;
111        }
112
113        return floor($number / abs($significance)) * abs($significance);
114    }
115
116    /**
117     * Avoid Scrutinizer complexity problems.
118     *
119     * @return float|string Rounded Number, or a string containing an error
120     */
121    private static function argsOk(float $number, float $significance, int $mode)
122    {
123        if (!$significance) {
124            return Functions::DIV0();
125        }
126        if (!$number) {
127            return 0.0;
128        }
129        if (self::floorMathTest($number, $significance, $mode)) {
130            return ceil($number / $significance) * $significance;
131        }
132
133        return floor($number / $significance) * $significance;
134    }
135
136    /**
137     * Let FLOORMATH complexity pass Scrutinizer.
138     */
139    private static function floorMathTest(float $number, float $significance, int $mode): bool
140    {
141        return Helpers::returnSign($significance) == -1 || (Helpers::returnSign($number) == -1 && !empty($mode));
142    }
143
144    /**
145     * Avoid Scrutinizer problems concerning complexity.
146     *
147     * @return float|string
148     */
149    private static function argumentsOk(float $number, float $significance)
150    {
151        if ($significance == 0.0) {
152            return Functions::DIV0();
153        }
154        if ($number == 0.0) {
155            return 0.0;
156        }
157        if (Helpers::returnSign($significance) == 1) {
158            return floor($number / $significance) * $significance;
159        }
160        if (Helpers::returnSign($number) == -1 && Helpers::returnSign($significance) == -1) {
161            return floor($number / $significance) * $significance;
162        }
163
164        return Functions::NAN();
165    }
166}
167