Skip to content

Commit 31b9521

Browse files
committed
number_format with int no precision loss
1 parent 907b554 commit 31b9521

File tree

5 files changed

+524
-4
lines changed

5 files changed

+524
-4
lines changed

ext/standard/math.c

Lines changed: 156 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,16 +1131,159 @@ PHPAPI zend_string *_php_math_number_format_ex(double d, int dec, const char *de
11311131
return res;
11321132
}
11331133

1134+
PHPAPI zend_string *_php_math_number_format_long(zend_long num, int dec, const char *dec_point,
1135+
size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len)
1136+
{
1137+
int neg = num < 0;
1138+
zend_ulong num_abs = neg ? ((zend_ulong)-(num + 1)) + 1 : (zend_ulong)num;
1139+
zend_string *res;
1140+
zend_string *res2;
1141+
zend_string *tmpbuf;
1142+
char *s, *t, *t2; /* source, target */
1143+
size_t integral;
1144+
size_t reslen = 0;
1145+
int count = 0;
1146+
size_t topad;
1147+
char cur_char;
1148+
int roundup = 0;
1149+
1150+
tmpbuf = strpprintf(0, "%lu", num_abs);
1151+
1152+
/* rounding more places than length of num
1153+
* or same places of length of num with rounding down
1154+
* will result in zero */
1155+
if (dec < 0) {
1156+
if (-dec > ZSTR_LEN(tmpbuf) || (-dec == ZSTR_LEN(tmpbuf) && ZSTR_VAL(tmpbuf)[0] < '5')) {
1157+
reslen = 1;
1158+
res = zend_string_alloc(reslen, 0);
1159+
t = ZSTR_VAL(res);
1160+
*t = '0';
1161+
1162+
ZSTR_LEN(res) = reslen;
1163+
zend_string_release_ex(tmpbuf, 0);
1164+
return res;
1165+
}
1166+
}
1167+
1168+
integral = ZSTR_LEN(tmpbuf);
1169+
1170+
/* allow for thousand separators */
1171+
if (thousand_sep) {
1172+
integral = zend_safe_addmult((integral-1)/3, thousand_sep_len, integral, "number formatting");
1173+
}
1174+
1175+
reslen = integral + neg;
1176+
1177+
if (dec > 0) {
1178+
reslen += dec;
1179+
1180+
if (dec_point) {
1181+
reslen = zend_safe_addmult(reslen, 1, dec_point_len, "number formatting");
1182+
}
1183+
}
1184+
1185+
res = zend_string_alloc(reslen, 0);
1186+
1187+
s = ZSTR_VAL(tmpbuf) + ZSTR_LEN(tmpbuf) - 1;
1188+
t = ZSTR_VAL(res) + reslen;
1189+
*t-- = '\0';
1190+
1191+
/* copy the decimal places. */
1192+
if (dec > 0) {
1193+
topad = (size_t)dec;
1194+
1195+
/* pad with '0's */
1196+
while (topad--) {
1197+
*t-- = '0';
1198+
}
1199+
1200+
/* add decimal point */
1201+
if (dec_point) {
1202+
t -= dec_point_len;
1203+
memcpy(t + 1, dec_point, dec_point_len);
1204+
}
1205+
}
1206+
1207+
/* copy the numbers before the decimal point,
1208+
* adding thousand separator every three digits,
1209+
* round negative decimal places if needed */
1210+
topad = 0;
1211+
cur_char = *s;
1212+
while (s >= ZSTR_VAL(tmpbuf)) {
1213+
cur_char = *s--;
1214+
if (roundup && cur_char != '-') {
1215+
if (cur_char < '9') {
1216+
cur_char++;
1217+
roundup = 0;
1218+
} else {
1219+
cur_char = '0';
1220+
}
1221+
}
1222+
1223+
if (dec < 0 && -dec > topad) {
1224+
if (-dec == topad+1 && cur_char >= '5') {
1225+
roundup = 1;
1226+
}
1227+
1228+
topad++;
1229+
*t-- = '0';
1230+
} else {
1231+
*t-- = cur_char;
1232+
}
1233+
1234+
if (thousand_sep && (++count%3)==0 && s >= ZSTR_VAL(tmpbuf)) {
1235+
t -= thousand_sep_len;
1236+
memcpy(t + 1, thousand_sep, thousand_sep_len);
1237+
}
1238+
}
1239+
1240+
if (neg) {
1241+
*t = '-';
1242+
}
1243+
1244+
/* Allocate more bytes in case we have to round up to more places than initially thought
1245+
* E.g. 999 with negative decimals between -1 and -3 needs to end up as 1.000 */
1246+
if (roundup) {
1247+
reslen += 1 + (thousand_sep && (count%3)==0 ? thousand_sep_len : 0);
1248+
res2 = zend_string_alloc(reslen, 0);
1249+
1250+
t2 = ZSTR_VAL(res2);
1251+
if (neg) {
1252+
*t2++ = '-';
1253+
*t2++ = '1';
1254+
if (thousand_sep && (count%3) == 0) {
1255+
memcpy(t2, thousand_sep, thousand_sep_len);
1256+
t2 += thousand_sep_len;
1257+
}
1258+
memcpy(t2, ZSTR_VAL(res)+1, reslen - 2);
1259+
} else {
1260+
*t2++ = '1';
1261+
if (thousand_sep && (count%3) == 0) {
1262+
memcpy(t2, thousand_sep, thousand_sep_len);
1263+
t2 += thousand_sep_len;
1264+
}
1265+
memcpy(t2, ZSTR_VAL(res), reslen - 1);
1266+
}
1267+
1268+
zend_string_release_ex(res, 0);
1269+
res = res2;
1270+
}
1271+
1272+
ZSTR_LEN(res) = reslen;
1273+
zend_string_release_ex(tmpbuf, 0);
1274+
return res;
1275+
}
1276+
11341277
/* {{{ Formats a number with grouped thousands */
11351278
PHP_FUNCTION(number_format)
11361279
{
1137-
double num;
1280+
zval* num;
11381281
zend_long dec = 0;
11391282
char *thousand_sep = NULL, *dec_point = NULL;
11401283
size_t thousand_sep_len = 0, dec_point_len = 0;
11411284

11421285
ZEND_PARSE_PARAMETERS_START(1, 4)
1143-
Z_PARAM_DOUBLE(num)
1286+
Z_PARAM_NUMBER(num)
11441287
Z_PARAM_OPTIONAL
11451288
Z_PARAM_LONG(dec)
11461289
Z_PARAM_STRING_OR_NULL(dec_point, dec_point_len)
@@ -1156,7 +1299,17 @@ PHP_FUNCTION(number_format)
11561299
thousand_sep_len = 1;
11571300
}
11581301

1159-
RETURN_STR(_php_math_number_format_ex(num, (int)dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1302+
switch (Z_TYPE_P(num)) {
1303+
case IS_LONG:
1304+
RETURN_STR(_php_math_number_format_long(Z_LVAL_P(num), (int)dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1305+
break;
1306+
1307+
case IS_DOUBLE:
1308+
RETURN_STR(_php_math_number_format_ex(Z_DVAL_P(num), (int)dec, dec_point, dec_point_len, thousand_sep, thousand_sep_len));
1309+
break;
1310+
1311+
EMPTY_SWITCH_DEFAULT_CASE()
1312+
}
11601313
}
11611314
/* }}} */
11621315

ext/standard/php_math.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
PHPAPI double _php_math_round(double value, int places, int mode);
2222
PHPAPI zend_string *_php_math_number_format(double d, int dec, char dec_point, char thousand_sep);
2323
PHPAPI zend_string *_php_math_number_format_ex(double d, int dec, const char *dec_point, size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len);
24+
PHPAPI zend_string *_php_math_number_format_long(zend_long num, int dec, const char *dec_point, size_t dec_point_len, const char *thousand_sep, size_t thousand_sep_len);
2425
PHPAPI zend_string * _php_math_longtobase(zend_long arg, int base);
2526
PHPAPI zend_long _php_math_basetolong(zval *arg, int base);
2627
PHPAPI void _php_math_basetozval(zend_string *str, int base, zval *ret);

ext/standard/tests/math/number_format_basic.phpt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ $values = array(1234.5678,
66
-1234.5678,
77
1234.6578e4,
88
-1234.56789e4,
9+
999999,
10+
-999999,
11+
999999.0,
12+
-999999.0,
913
0x1234CDEF,
1014
02777777777,
1115
"123456789",
@@ -37,13 +41,23 @@ for ($i = 0; $i < count($values); $i++) {
3741
$res = number_format($values[$i], 2, ',' , ' ');
3842
var_dump($res);
3943
}
44+
45+
echo "\n number_format tests.....multichar format\n";
46+
for ($i = 0; $i < count($values); $i++) {
47+
$res = number_format($values[$i], 2, ' DECIMALS ' , ' THOUSAND ');
48+
var_dump($res);
49+
}
4050
?>
4151
--EXPECT--
4252
number_format tests.....default
4353
string(5) "1,235"
4454
string(6) "-1,235"
4555
string(10) "12,346,578"
4656
string(11) "-12,345,679"
57+
string(7) "999,999"
58+
string(8) "-999,999"
59+
string(7) "999,999"
60+
string(8) "-999,999"
4761
string(11) "305,450,479"
4862
string(11) "402,653,183"
4963
string(11) "123,456,789"
@@ -57,6 +71,10 @@ string(8) "1,234.57"
5771
string(9) "-1,234.57"
5872
string(13) "12,346,578.00"
5973
string(14) "-12,345,678.90"
74+
string(10) "999,999.00"
75+
string(11) "-999,999.00"
76+
string(10) "999,999.00"
77+
string(11) "-999,999.00"
6078
string(14) "305,450,479.00"
6179
string(14) "402,653,183.00"
6280
string(14) "123,456,789.00"
@@ -70,6 +88,10 @@ string(8) "1 234.57"
7088
string(9) "-1 234.57"
7189
string(13) "12 346 578.00"
7290
string(14) "-12 345 678.90"
91+
string(10) "999 999.00"
92+
string(11) "-999 999.00"
93+
string(10) "999 999.00"
94+
string(11) "-999 999.00"
7395
string(14) "305 450 479.00"
7496
string(14) "402 653 183.00"
7597
string(14) "123 456 789.00"
@@ -83,10 +105,31 @@ string(8) "1 234,57"
83105
string(9) "-1 234,57"
84106
string(13) "12 346 578,00"
85107
string(14) "-12 345 678,90"
108+
string(10) "999 999,00"
109+
string(11) "-999 999,00"
110+
string(10) "999 999,00"
111+
string(11) "-999 999,00"
86112
string(14) "305 450 479,00"
87113
string(14) "402 653 183,00"
88114
string(14) "123 456 789,00"
89115
string(6) "123,46"
90116
string(6) "123,46"
91117
string(4) "1,00"
92118
string(4) "0,00"
119+
120+
number_format tests.....multichar format
121+
string(26) "1 THOUSAND 234 DECIMALS 57"
122+
string(27) "-1 THOUSAND 234 DECIMALS 57"
123+
string(40) "12 THOUSAND 346 THOUSAND 578 DECIMALS 00"
124+
string(41) "-12 THOUSAND 345 THOUSAND 678 DECIMALS 90"
125+
string(28) "999 THOUSAND 999 DECIMALS 00"
126+
string(29) "-999 THOUSAND 999 DECIMALS 00"
127+
string(28) "999 THOUSAND 999 DECIMALS 00"
128+
string(29) "-999 THOUSAND 999 DECIMALS 00"
129+
string(41) "305 THOUSAND 450 THOUSAND 479 DECIMALS 00"
130+
string(41) "402 THOUSAND 653 THOUSAND 183 DECIMALS 00"
131+
string(41) "123 THOUSAND 456 THOUSAND 789 DECIMALS 00"
132+
string(15) "123 DECIMALS 46"
133+
string(15) "123 DECIMALS 46"
134+
string(13) "1 DECIMALS 00"
135+
string(13) "0 DECIMALS 00"

0 commit comments

Comments
 (0)