@@ -205,6 +205,14 @@ public static function grapheme_str_split(string $string, int $length)
205
205
return $ chunks ;
206
206
}
207
207
208
+ public static function bcceil (string $ num ): string
209
+ {
210
+ if (!is_numeric ($ num )) {
211
+ throw new \ValueError ('bcceil(): Argument #1 ($num) is not well-formed ' );
212
+ }
213
+ return self ::bcround ($ num , 0 , \RoundingMode::PositiveInfinity);
214
+ }
215
+
208
216
public static function bcdivmod (string $ num1 , string $ num2 , ?int $ scale = null ): ?array
209
217
{
210
218
if (null === $ quot = \bcdiv ($ num1 , $ num2 , 0 )) {
@@ -214,4 +222,200 @@ public static function bcdivmod(string $num1, string $num2, ?int $scale = null):
214
222
215
223
return [$ quot , \bcmod ($ num1 , $ num2 , $ scale )];
216
224
}
225
+
226
+ public static function bcfloor (string $ num ): string
227
+ {
228
+ if (!is_numeric ($ num )) {
229
+ throw new \ValueError ('bcfloor(): Argument #1 ($num) is not well-formed ' );
230
+ }
231
+ return self ::bcround ($ num , 0 , \RoundingMode::NegativeInfinity);
232
+ }
233
+
234
+ /**
235
+ * @param \RoundingMode::* $mode
236
+ */
237
+ public static function bcround (string $ num , int $ precision = 0 , int $ mode = \RoundingMode::HalfAwayFromZero): string
238
+ {
239
+ if (!is_numeric ($ num )) {
240
+ throw new \ValueError ('bcround(): Argument #1 ($num) is not well-formed ' );
241
+ }
242
+
243
+ $ sign = 1 ;
244
+ if ('' !== $ num && ('- ' === $ num [0 ] || '+ ' === $ num [0 ])) {
245
+ if ('- ' === $ num [0 ]) {
246
+ $ sign = -1 ;
247
+ }
248
+
249
+ $ num = substr ($ num , 1 );
250
+ }
251
+
252
+ if (false !== strpos ($ num , '. ' )) {
253
+ [$ intPart , $ fracPart ] = array_pad (explode ('. ' , $ num , 2 ), 2 , '' );
254
+ } else {
255
+ $ intPart = $ num ;
256
+ $ fracPart = '' ;
257
+ }
258
+
259
+ if ('' === $ intPart ) {
260
+ $ intPart = '0 ' ;
261
+ }
262
+
263
+ $ intPart = self ::trimLeadingZeros ($ intPart );
264
+ $ fracPart = (string ) $ fracPart ;
265
+
266
+ if ($ precision >= 0 ) {
267
+ $ fracLength = \strlen ($ fracPart );
268
+
269
+ if ($ precision <= $ fracLength ) {
270
+ $ scaledInt = $ intPart .(string ) substr ($ fracPart , 0 , $ precision );
271
+ $ scaledFrac = (string ) substr ($ fracPart , $ precision );
272
+ } else {
273
+ $ scaledInt = $ intPart .$ fracPart .str_repeat ('0 ' , $ precision - $ fracLength );
274
+ $ scaledFrac = '' ;
275
+ }
276
+ } else {
277
+ $ shift = -$ precision ;
278
+ $ intLength = \strlen ($ intPart );
279
+
280
+ if ($ shift <= $ intLength ) {
281
+ $ splitPos = $ intLength - $ shift ;
282
+ $ scaledInt = substr ($ intPart , 0 , $ splitPos );
283
+ $ scaledInt = '' === $ scaledInt ? '0 ' : $ scaledInt ;
284
+ $ scaledFrac = substr ($ intPart , $ splitPos ).$ fracPart ;
285
+ } else {
286
+ $ scaledInt = '0 ' ;
287
+ $ scaledFrac = str_repeat ('0 ' , $ shift - $ intLength ).$ intPart .$ fracPart ;
288
+ }
289
+ }
290
+
291
+ $ roundedInt = self ::roundIntegerPart ($ scaledInt , $ scaledFrac , $ sign , $ mode );
292
+ $ isZero = '' === trim ($ roundedInt , '0 ' );
293
+ $ absResult = self ::formatRoundedDigits ($ roundedInt , $ precision );
294
+
295
+ if (-1 === $ sign && !$ isZero ) {
296
+ $ absResult = '- ' .$ absResult ;
297
+ }
298
+
299
+ return $ absResult ;
300
+ }
301
+
302
+ private static function roundIntegerPart (string $ intPart , string $ fracPart , int $ sign , int $ mode ): string
303
+ {
304
+ $ intPart = self ::trimLeadingZeros ($ intPart );
305
+
306
+ if ('' === $ fracPart || '' === trim ($ fracPart , '0 ' )) {
307
+ return $ intPart ;
308
+ }
309
+
310
+ $ firstDigit = $ fracPart [0 ];
311
+ $ tail = (string ) substr ($ fracPart , 1 );
312
+ $ tailNonZero = '' !== trim ($ tail , '0 ' );
313
+ $ isGreaterThanHalf = $ firstDigit > '5 ' || ('5 ' === $ firstDigit && $ tailNonZero );
314
+ $ isExactlyHalf = '5 ' === $ firstDigit && !$ tailNonZero ;
315
+ $ shouldIncrease = false ;
316
+
317
+ switch ($ mode ) {
318
+ case \RoundingMode::TowardsZero:
319
+ break ;
320
+
321
+ case \RoundingMode::AwayFromZero:
322
+ $ shouldIncrease = true ;
323
+ break ;
324
+
325
+ case \RoundingMode::PositiveInfinity:
326
+ $ shouldIncrease = $ sign > 0 ;
327
+ break ;
328
+
329
+ case \RoundingMode::NegativeInfinity:
330
+ $ shouldIncrease = $ sign < 0 ;
331
+ break ;
332
+
333
+ case \RoundingMode::HalfAwayFromZero:
334
+ $ shouldIncrease = $ isGreaterThanHalf || $ isExactlyHalf ;
335
+ break ;
336
+
337
+ case \RoundingMode::HalfTowardsZero:
338
+ $ shouldIncrease = $ isGreaterThanHalf ;
339
+ break ;
340
+
341
+ case \RoundingMode::HalfEven:
342
+ if ($ isGreaterThanHalf ) {
343
+ $ shouldIncrease = true ;
344
+ } elseif ($ isExactlyHalf && self ::lastDigit ($ intPart ) % 2 === 1 ) {
345
+ $ shouldIncrease = true ;
346
+ }
347
+ break ;
348
+
349
+ case \RoundingMode::HalfOdd:
350
+ if ($ isGreaterThanHalf ) {
351
+ $ shouldIncrease = true ;
352
+ } elseif ($ isExactlyHalf && self ::lastDigit ($ intPart ) % 2 === 0 ) {
353
+ $ shouldIncrease = true ;
354
+ }
355
+ break ;
356
+ }
357
+
358
+ if ($ shouldIncrease ) {
359
+ $ intPart = self ::incrementDigits ($ intPart );
360
+ }
361
+
362
+ return self ::trimLeadingZeros ($ intPart );
363
+ }
364
+
365
+ private static function formatRoundedDigits (string $ roundedInt , int $ precision ): string
366
+ {
367
+ if ($ precision > 0 ) {
368
+ if (\strlen ($ roundedInt ) <= $ precision ) {
369
+ $ roundedInt = str_pad ($ roundedInt , $ precision + 1 , '0 ' , STR_PAD_LEFT );
370
+ }
371
+
372
+ $ intDigits = substr ($ roundedInt , 0 , -$ precision );
373
+ $ fracDigits = substr ($ roundedInt , -$ precision );
374
+
375
+ $ intDigits = self ::trimLeadingZeros ('' === $ intDigits ? '0 ' : $ intDigits );
376
+ $ fracDigits = str_pad ($ fracDigits , $ precision , '0 ' , STR_PAD_LEFT );
377
+
378
+ return $ intDigits .'. ' .$ fracDigits ;
379
+ }
380
+
381
+ if (0 === $ precision ) {
382
+ return self ::trimLeadingZeros ($ roundedInt );
383
+ }
384
+
385
+ $ shift = -$ precision ;
386
+ $ digits = $ roundedInt .str_repeat ('0 ' , $ shift );
387
+
388
+ return self ::trimLeadingZeros ($ digits );
389
+ }
390
+
391
+ private static function incrementDigits (string $ digits ): string
392
+ {
393
+ $ digits = '' === $ digits ? '0 ' : $ digits ;
394
+ $ index = \strlen ($ digits ) - 1 ;
395
+ $ result = $ digits ;
396
+ $ carry = 1 ;
397
+
398
+ while ($ index >= 0 && $ carry ) {
399
+ $ value = ord ($ result [$ index ]) - 48 + $ carry ;
400
+ $ carry = $ value >= 10 ? 1 : 0 ;
401
+ $ result [$ index ] = chr (48 + ($ value % 10 ));
402
+ --$ index ;
403
+ }
404
+
405
+ return $ carry ? '1 ' .$ result : $ result ;
406
+ }
407
+
408
+ private static function trimLeadingZeros (string $ digits ): string
409
+ {
410
+ $ digits = ltrim ($ digits , '0 ' );
411
+
412
+ return '' === $ digits ? '0 ' : $ digits ;
413
+ }
414
+
415
+ private static function lastDigit (string $ digits ): int
416
+ {
417
+ $ length = \strlen ($ digits );
418
+
419
+ return $ length ? ord ($ digits [$ length - 1 ]) - 48 : 0 ;
420
+ }
217
421
}
0 commit comments