New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Add bcfloor, bcceil and bcround to BCMath #13096
Conversation
e189f49
to
469b079
Compare
469b079
to
17db35e
Compare
ping @kocsismate There is a change in the stub, but you aren't included in the reviewers 🤔 |
Thanks for letting me know! I was wondering indeed for a long time, why I'm not included in many of the PRs changing stubs. After some investigation, I realized the problem and pushed d54e2f9 (details are in the description). Regarding your PR, it looks fine to me as far as I'm qualified to tell, but I'm not a math expert... So I cannot approve it. :/ Hopefully, Gina or Niels can help review it. :) |
bc_init_num(&result); | ||
|
||
if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) { | ||
zend_argument_value_error(1, "is not well-formed"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I miss tests for the error cases (this one and a few more below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for checking!
I added value error case to tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a case with a nul byte in the string. The current API doesn't support them as it basically ignores anything past the nul byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't look thoroughly at the tests or tested this myself, but I have some preliminary feedback.
It mostly looks correct.
@nielsdos |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't have a proper look at the test results yet
bc_init_num(&result); | ||
|
||
if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) { | ||
zend_argument_value_error(1, "is not well-formed"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a case with a nul byte in the string. The current API doesn't support them as it basically ignores anything past the nul byte.
Western Washington University | ||
Bellingham, WA 98226-9062 | ||
|
||
*************************************************************************/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This license header file looks invalid, not sure what should be used here. Be that the usual PHP License or another one.
@ramsey do you know by any chance what would be a reasonable licence to use here?
ext/bcmath/libbcmath/src/round.c
Outdated
* | ||
* 1. When rounding to an integer part, when trying to round to a digit | ||
* larger than the num. | ||
* 2. If try to round to the finest digit of num or finer than that, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "finest" here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean "smallest digit". For example, if num is 1.23456
, that digit means 6
.
What is the appropriate way to express it in English?
ext/bcmath/libbcmath/src/round.c
Outdated
/* Loop through the remaining digits. */ | ||
size_t count = num->n_len + num->n_scale - rounded_len - 1; | ||
nptr++; | ||
while ((count > 0) && (*nptr++ == 0)) count--; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CS
while ((count > 0) && (*nptr++ == 0)) count--; | |
while ((count > 0) && (*nptr++ == 0)) { | |
count--; | |
} |
Also am I missing something or why are we only checking if the digit is 0
? This does not seem to take into account the case where we need to decide if we need to go up or down on 5
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, for the following code:
bcround('0.250000', 1, PHP_ROUND_HALF_UP);
Here it is checking for 0000
after 0.25
. If num is 0.250001
, it is not an edge case, so it judge whether it is an edge case by checking whether all the remaining values are 0
.
ext/bcmath/php_bcmath.h
Outdated
@@ -19,6 +19,8 @@ | |||
|
|||
#include "libbcmath/src/bcmath.h" | |||
#include "zend_API.h" | |||
#include "php.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be included?
I tried to get rid of plenty of includes in bcmath.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to read and use round constants from php_math.h, but I need to resolve PHPAPI
.
If you have any good ideas, it would be helpful if you could let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhh right it's that issue... one "dumb" solution is to redefine the constants for BCMATH and expose them and have a PHPT test to check that the values are really equal to each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking about this for a while now, how if I separate out just the standard round-mode constants into a file like "php_math_round.h" and include that?
/* check fractional part. */ | ||
size_t count = num->n_scale; | ||
char *nptr = num->n_value + num->n_len; | ||
while ((count > 0) && (*nptr++ == 0)) count--; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this can become an additional utility function in bc_zero.c, but it's not too important.
Thank you all, I will check and correct them one by one! |
e8b6c8d
to
524dc20
Compare
I think it would probably be better to add a null check to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have checked the tests for ceil and floor and they seem correct.
Haven't done rounding modes yet.
The licence is still leaving me puzzled.
I set the test expected value to left pad and the license to php license. |
--EXPECT-- | ||
0 => 0 | ||
0.00 => 0 | ||
-0 => 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether the result should be -0
? (Similarly for cases below and for bcceil)
It would be consistent with: var_dump(floor(-0.0));
Genuine question, I don't know the answer for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BCMath does not currently support -0
.
signch = num->n_sign != PLUS && !bc_is_zero_for_scale(num, MIN(num->n_scale, scale)); |
https://3v4l.org/HteIO
It seems worth supporting, but I'd like to keep it in a separate scope from this pull request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm. Maybe wait for Gina for a final review round, as she is the code owner.
Also don't forget to add an entry to NEWS and UPGRADING.
@nielsdos |
Co-authored-by: Gina Peter Banyard <girgias@php.net>
44f37d4
to
2e2dd10
Compare
BTW, Regarding the second argument of |
Brilliant, so PHP is just wrong: https://www.calculatorsoup.com/calculators/math/rounding-methods-calculator.php?method=round+half+down&x=-1.5&round=0&action=solve Half up means to always do So PHP has just been wrong for decades. But at least it is in good company in that Java, Python, Ruby, and probably others also use the wrong terminology. I'm going to email internals explaining this situation, as I'm not sure how to "fix" this, especially in light of: https://wiki.php.net/rfc/new_rounding_modes_to_round_function |
@Girgias
Since this is an early return pattern, it is included in |
I might be blind, but I don't think I saw a case where all the different half rounding modes are tested on such a boundary condition. |
Indeed, I only tested HALF_UP, thank you. Added all modes to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You still haven't added the tests I want, I want something like the following:
$modes = [
'PHP_ROUND_HALF_UP',
'PHP_ROUND_HALF_DOWN',
'PHP_ROUND_HALF_EVEN',
'PHP_ROUND_HALF_ODD',
'PHP_ROUND_FLOOR',
'PHP_ROUND_CEILING',
'PHP_ROUND_AWAY_FROM_ZERO',
'PHP_ROUND_TOWARD_ZERO',
];
foreach ($modes as $mode) {
var_dump(round(50, -2, constant($mode)));
}
which, according to 3v4l.org (https://3v4l.org/8J3Mu/rfc#vgit.master) returns:
float(100)
float(0)
float(0)
float(100)
float(0)
float(100)
float(100)
float(0)
11b24b5
to
8226342
Compare
Added boundary value test case. Also removed test cases that should not be returned early from early return tests
8226342
to
d227a94
Compare
You have good eyes. Indeed, that test case failed. I added and modified the test cases, then modified the code. As you taught me before, I am splitting commits. Thanks! (edit) |
The comparison conditions for determining early returns were incorrect and have been corrected. Added processing when the rounding result is carried forward or becomes 0.
98ea4a0
to
a3d08e5
Compare
Fix completed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We got there in the end 🎉
Other than a very minor comment as I don't see why this works, but the tests look correct now :)
} | ||
break; | ||
|
||
case PHP_ROUND_HALF_DOWN: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't the half down case be determined without a loop? If it does require a loop how come HALF UP doesn't need one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, let's compare two cases: 5.0000
and 5.0001
.
HALF_UP: Either way, it'll be a 10.
HALF_DOWN: 5.0001 will be 10, but 5.0000 will be 0.
Therefore, in the case of HALF_UP, there is no need to loop because it always carries up regardless of the loop result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, I was refactoring BCMath in another PR and found that trailing zeros are discarded when converting strings to bc_num structs.
So in all cases I don't need to loop, just check if there is one more digit.
4388b43
to
9cf63f8
Compare
RFC implementation: https://wiki.php.net/rfc/adding_bcround_bcfloor_bcceil_to_bcmath