2
* Copyright 2015-2016 DataStax, Inc.
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
17
#include "php_driver.h"
18
#include "php_driver_types.h"
19
#include "util/hash.h"
20
#include "util/math.h"
21
#include "util/types.h"
26
#include <ext/spl/spl_exceptions.h>
28
zend_class_entry *php_driver_decimal_ce = NULL;
31
to_mpf(mpf_t result, php_driver_numeric *decimal)
35
/* result = unscaled * pow(10, -scale) */
36
mpf_set_z(result, decimal->data.decimal.value);
38
scale = decimal->data.decimal.scale;
39
mpf_init_set_si(scale_factor, 10);
40
mpf_pow_ui(scale_factor, scale_factor, scale < 0 ? -scale : scale);
43
mpf_ui_div(scale_factor, 1, scale_factor);
46
mpf_mul(result, result, scale_factor);
48
mpf_clear(scale_factor);
52
* IEEE 754 double precision floating point representation:
54
* S EEEEEEEEEEE MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
55
* [63][ 62 - 52 ][ 51 - 0 ]
61
#define DOUBLE_MANTISSA_BITS 52
62
#define DOUBLE_MANTISSA_MASK (cass_int64_t) ((1LL << DOUBLE_MANTISSA_BITS) - 1)
63
#define DOUBLE_EXPONENT_BITS 11
64
#define DOUBLE_EXPONENT_MASK (cass_int64_t) ((1LL << DOUBLE_EXPONENT_BITS) - 1)
67
from_double(php_driver_numeric *result, double value)
70
char mantissa_str[32];
71
cass_int64_t raw, mantissa, exponent;
73
// Copy the bits of value into an int64 so that we can do bit manipulations on it.
74
memcpy(&raw, &value, 8);
76
mantissa = raw & DOUBLE_MANTISSA_MASK;
77
exponent = (raw >> DOUBLE_MANTISSA_BITS) & DOUBLE_EXPONENT_MASK;
79
/* This exponent is offset using 1023 unless it's a denormal value then its value
80
* is the minimum value -1022
83
/* If the exponent is a zero then we have a denormal (subnormal) number. These are numbers
84
* that represent small values around 0.0. The mantissa has the form of 0.xxxxxxxx...
86
* http://en.wikipedia.org/wiki/Denormal_number
91
/* Normal number The mantissa has the form of 1.xxxxxxx... */
96
/* Move the factional parts in the mantissa to the exponent. The significand
97
* represents fractional parts:
99
* S = 1 + B51 * 2^-51 + B50 * 2^-52 ... + B0
102
exponent -= DOUBLE_MANTISSA_BITS;
105
/* Normal numbers have an implied one i.e. 1.xxxxxx... */
106
mantissa |= (1LL << DOUBLE_MANTISSA_BITS);
109
/* Remove trailing zeros and move them to the exponent */
110
while (exponent < 0 && (mantissa & 1) == 0) {
115
/* There isn't any "long long" setter method */
117
sprintf(mantissa_str, "%I64d", mantissa);
119
sprintf(mantissa_str, "%lld", mantissa);
121
mpz_set_str(result->data.decimal.value, mantissa_str, 10);
123
/* Change the sign if negative */
125
mpz_neg(result->data.decimal.value, result->data.decimal.value);
129
/* Convert from pow(2, exponent) to pow(10, exponent):
131
* mantissa * pow(2, exponent) equals
132
* mantissa * (pow(10, exponent) / pow(5, exponent))
136
mpz_ui_pow_ui(pow_5, 5, -exponent);
137
mpz_mul(result->data.decimal.value, result->data.decimal.value, pow_5);
139
result->data.decimal.scale = -exponent;
141
mpz_mul_2exp(result->data.decimal.value, result->data.decimal.value, exponent);
142
result->data.decimal.scale = 0;
147
to_double(zval* result, php_driver_numeric *decimal TSRMLS_DC)
151
to_mpf(value, decimal);
153
if (mpf_cmp_d(value, -DBL_MAX) < 0) {
154
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC, "Value is too small");
159
if (mpf_cmp_d(value, DBL_MAX) > 0) {
160
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC, "Value is too big");
165
ZVAL_DOUBLE(result, mpf_get_d(value));
171
to_long(zval* result, php_driver_numeric *decimal TSRMLS_DC)
175
to_mpf(value, decimal);
177
if (mpf_cmp_si(value, LONG_MIN) < 0) {
178
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC, "Value is too small");
183
if (mpf_cmp_si(value, LONG_MAX) > 0) {
184
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC, "Value is too big");
189
ZVAL_LONG(result, mpf_get_si(value));
195
to_string(zval* result, php_driver_numeric *decimal TSRMLS_DC)
199
php_driver_format_decimal(decimal->data.decimal.value, decimal->data.decimal.scale, &string, &string_len);
201
PHP5TO7_ZVAL_STRINGL(result, string, string_len);
208
align_decimals(php_driver_numeric *lhs, php_driver_numeric *rhs)
212
if (lhs->data.decimal.scale < rhs->data.decimal.scale) {
213
mpz_ui_pow_ui(pow_10, 10, rhs->data.decimal.scale - lhs->data.decimal.scale);
214
mpz_mul(lhs->data.decimal.value, lhs->data.decimal.value, pow_10);
215
} else if (lhs->data.decimal.scale > rhs->data.decimal.scale) {
216
mpz_ui_pow_ui(pow_10, 10, lhs->data.decimal.scale - rhs->data.decimal.scale);
217
mpz_mul(rhs->data.decimal.value, rhs->data.decimal.value, pow_10);
223
php_driver_decimal_init(INTERNAL_FUNCTION_PARAMETERS)
225
php_driver_numeric *self;
228
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &value) == FAILURE) {
232
if (getThis() && instanceof_function(Z_OBJCE_P(getThis()), php_driver_decimal_ce TSRMLS_CC)) {
233
self = PHP_DRIVER_GET_NUMERIC(getThis());
235
object_init_ex(return_value, php_driver_decimal_ce);
236
self = PHP_DRIVER_GET_NUMERIC(return_value);
239
if (Z_TYPE_P(value) == IS_LONG) {
240
mpz_set_si(self->data.decimal.value, Z_LVAL_P(value));
241
self->data.decimal.scale = 0;
242
} else if (Z_TYPE_P(value) == IS_DOUBLE) {
243
double val = Z_DVAL_P(value);
244
if (zend_isnan(val) || zend_isinf(val)) {
245
zend_throw_exception_ex(php_driver_invalid_argument_exception_ce, 0 TSRMLS_CC,
246
"Value of NaN or +/- infinity is not supported");
249
from_double(self, val);
250
} else if (Z_TYPE_P(value) == IS_STRING) {
251
if (!php_driver_parse_decimal(Z_STRVAL_P(value), Z_STRLEN_P(value),
252
&self->data.decimal.value, &self->data.decimal.scale TSRMLS_CC)) {
255
} else if (Z_TYPE_P(value) == IS_OBJECT &&
256
instanceof_function(Z_OBJCE_P(value), php_driver_decimal_ce TSRMLS_CC)) {
257
php_driver_numeric *decimal = PHP_DRIVER_GET_NUMERIC(value);
258
mpz_set(self->data.decimal.value, decimal->data.decimal.value);
259
self->data.decimal.scale = decimal->data.decimal.scale;
261
INVALID_ARGUMENT(value, "a long, a double, a numeric string or a " \
262
PHP_DRIVER_NAMESPACE "\\Decimal");
266
/* {{{ Decimal::__construct(string) */
267
PHP_METHOD(Decimal, __construct)
269
php_driver_decimal_init(INTERNAL_FUNCTION_PARAM_PASSTHRU);
273
/* {{{ Decimal::__toString() */
274
PHP_METHOD(Decimal, __toString)
276
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
278
to_string(return_value, self TSRMLS_CC);
282
/* {{{ Decimal::type() */
283
PHP_METHOD(Decimal, type)
285
php5to7_zval type = php_driver_type_scalar(CASS_VALUE_TYPE_DECIMAL TSRMLS_CC);
286
RETURN_ZVAL(PHP5TO7_ZVAL_MAYBE_P(type), 1, 1);
290
/* {{{ Decimal::value() */
291
PHP_METHOD(Decimal, value)
293
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
297
php_driver_format_integer(self->data.decimal.value, &string, &string_len);
299
PHP5TO7_RETVAL_STRINGL(string, string_len);
304
PHP_METHOD(Decimal, scale)
306
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
308
RETURN_LONG(self->data.decimal.scale);
311
/* {{{ Decimal::add() */
312
PHP_METHOD(Decimal, add)
315
php_driver_numeric *result = NULL;
317
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &num) == FAILURE) {
321
if (Z_TYPE_P(num) == IS_OBJECT &&
322
instanceof_function(Z_OBJCE_P(num), php_driver_decimal_ce TSRMLS_CC)) {
323
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
324
php_driver_numeric *decimal = PHP_DRIVER_GET_NUMERIC(num);
326
object_init_ex(return_value, php_driver_decimal_ce);
327
result = PHP_DRIVER_GET_NUMERIC(return_value);
329
align_decimals(self, decimal);
330
mpz_add(result->data.decimal.value, self->data.decimal.value, decimal->data.decimal.value);
331
result->data.decimal.scale = MAX(self->data.decimal.scale, decimal->data.decimal.scale);
333
INVALID_ARGUMENT(num, "a " PHP_DRIVER_NAMESPACE "\\Decimal");
338
/* {{{ Decimal::sub() */
339
PHP_METHOD(Decimal, sub)
342
php_driver_numeric *result = NULL;
344
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &num) == FAILURE) {
348
if (Z_TYPE_P(num) == IS_OBJECT &&
349
instanceof_function(Z_OBJCE_P(num), php_driver_decimal_ce TSRMLS_CC)) {
350
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
351
php_driver_numeric *decimal = PHP_DRIVER_GET_NUMERIC(num);
353
object_init_ex(return_value, php_driver_decimal_ce);
354
result = PHP_DRIVER_GET_NUMERIC(return_value);
356
align_decimals(self, decimal);
357
mpz_sub(result->data.decimal.value, self->data.decimal.value, decimal->data.decimal.value);
358
result->data.decimal.scale = MAX(self->data.decimal.scale, decimal->data.decimal.scale);
360
INVALID_ARGUMENT(num, "a " PHP_DRIVER_NAMESPACE "\\Decimal");
365
/* {{{ Decimal::mul() */
366
PHP_METHOD(Decimal, mul)
369
php_driver_numeric *result = NULL;
371
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &num) == FAILURE) {
375
if (Z_TYPE_P(num) == IS_OBJECT &&
376
instanceof_function(Z_OBJCE_P(num), php_driver_decimal_ce TSRMLS_CC)) {
377
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
378
php_driver_numeric *decimal = PHP_DRIVER_GET_NUMERIC(num);
380
object_init_ex(return_value, php_driver_decimal_ce);
381
result = PHP_DRIVER_GET_NUMERIC(return_value);
383
mpz_mul(result->data.decimal.value, self->data.decimal.value, decimal->data.decimal.value);
384
result->data.decimal.scale = self->data.decimal.scale + decimal->data.decimal.scale;
386
INVALID_ARGUMENT(num, "a " PHP_DRIVER_NAMESPACE "\\Decimal");
391
/* {{{ Decimal::div() */
392
PHP_METHOD(Decimal, div)
394
/* TODO: Implementation of this a bit more difficult than anticipated. */
395
zend_throw_exception_ex(php_driver_runtime_exception_ce, 0 TSRMLS_CC, "Not implemented");
399
/* {{{ Decimal::mod() */
400
PHP_METHOD(Decimal, mod)
402
/* TODO: We could implement a remainder method */
403
zend_throw_exception_ex(php_driver_runtime_exception_ce, 0 TSRMLS_CC, "Not implemented");
406
/* {{{ Decimal::abs() */
407
PHP_METHOD(Decimal, abs)
409
php_driver_numeric *result = NULL;
410
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
412
object_init_ex(return_value, php_driver_decimal_ce);
413
result = PHP_DRIVER_GET_NUMERIC(return_value);
415
mpz_abs(result->data.decimal.value, self->data.decimal.value);
416
result->data.decimal.scale = self->data.decimal.scale;
420
/* {{{ Decimal::neg() */
421
PHP_METHOD(Decimal, neg)
423
php_driver_numeric *result = NULL;
424
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
426
object_init_ex(return_value, php_driver_decimal_ce);
427
result = PHP_DRIVER_GET_NUMERIC(return_value);
429
mpz_neg(result->data.decimal.value, self->data.decimal.value);
430
result->data.decimal.scale = self->data.decimal.scale;
434
/* {{{ Decimal::sqrt() */
435
PHP_METHOD(Decimal, sqrt)
437
zend_throw_exception_ex(php_driver_runtime_exception_ce, 0 TSRMLS_CC, "Not implemented");
439
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
445
mpf_sqrt(value, value);
448
char* mantissa = mpf_get_str(NULL, &exponent, 10, 0, value);
450
object_init_ex(return_value, php_driver_decimal_ce);
451
php_driver_numeric *result = PHP_DRIVER_GET_NUMERIC(return_value);
453
mpz_set_str(result->value.decimal_value, mantissa, 10);
454
mp_bitcnt_t prec = mpf_get_prec(value);
456
result->value.decimal_scale = -exponent;
464
/* {{{ Decimal::toInt() */
465
PHP_METHOD(Decimal, toInt)
467
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
469
to_long(return_value, self TSRMLS_CC);
473
/* {{{ Decimal::toDouble() */
474
PHP_METHOD(Decimal, toDouble)
476
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(getThis());
478
to_double(return_value, self TSRMLS_CC);
482
ZEND_BEGIN_ARG_INFO_EX(arginfo__construct, 0, ZEND_RETURN_VALUE, 1)
483
ZEND_ARG_INFO(0, value)
486
ZEND_BEGIN_ARG_INFO_EX(arginfo_none, 0, ZEND_RETURN_VALUE, 0)
489
ZEND_BEGIN_ARG_INFO_EX(arginfo_num, 0, ZEND_RETURN_VALUE, 1)
490
ZEND_ARG_INFO(0, num)
493
static zend_function_entry php_driver_decimal_methods[] = {
494
PHP_ME(Decimal, __construct, arginfo__construct, ZEND_ACC_CTOR|ZEND_ACC_PUBLIC)
495
PHP_ME(Decimal, __toString, arginfo_none, ZEND_ACC_PUBLIC)
496
PHP_ME(Decimal, type, arginfo_none, ZEND_ACC_PUBLIC)
497
PHP_ME(Decimal, value, arginfo_none, ZEND_ACC_PUBLIC)
498
PHP_ME(Decimal, scale, arginfo_none, ZEND_ACC_PUBLIC)
499
PHP_ME(Decimal, add, arginfo_num, ZEND_ACC_PUBLIC)
500
PHP_ME(Decimal, sub, arginfo_num, ZEND_ACC_PUBLIC)
501
PHP_ME(Decimal, mul, arginfo_num, ZEND_ACC_PUBLIC)
502
PHP_ME(Decimal, div, arginfo_num, ZEND_ACC_PUBLIC)
503
PHP_ME(Decimal, mod, arginfo_num, ZEND_ACC_PUBLIC)
504
PHP_ME(Decimal, abs, arginfo_none, ZEND_ACC_PUBLIC)
505
PHP_ME(Decimal, neg, arginfo_none, ZEND_ACC_PUBLIC)
506
PHP_ME(Decimal, sqrt, arginfo_none, ZEND_ACC_PUBLIC)
507
PHP_ME(Decimal, toInt, arginfo_none, ZEND_ACC_PUBLIC)
508
PHP_ME(Decimal, toDouble, arginfo_none, ZEND_ACC_PUBLIC)
512
static php_driver_value_handlers php_driver_decimal_handlers;
515
php_driver_decimal_gc(zval *object, php5to7_zval_gc table, int *n TSRMLS_DC)
519
return zend_std_get_properties(object TSRMLS_CC);
523
php_driver_decimal_properties(zval *object TSRMLS_DC)
531
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(object);
532
HashTable *props = zend_std_get_properties(object TSRMLS_CC);
534
type = php_driver_type_scalar(CASS_VALUE_TYPE_DECIMAL TSRMLS_CC);
535
PHP5TO7_ZEND_HASH_UPDATE(props, "type", sizeof("type"), PHP5TO7_ZVAL_MAYBE_P(type), sizeof(zval));
537
php_driver_format_integer(self->data.decimal.value, &string, &string_len);
538
PHP5TO7_ZVAL_MAYBE_MAKE(PHP5TO7_ZVAL_MAYBE_P(value));
539
PHP5TO7_ZVAL_STRINGL(PHP5TO7_ZVAL_MAYBE_P(value), string, string_len);
541
PHP5TO7_ZEND_HASH_UPDATE(props, "value", sizeof("value"), PHP5TO7_ZVAL_MAYBE_P(value), sizeof(zval));
543
PHP5TO7_ZVAL_MAYBE_MAKE(scale);
544
ZVAL_LONG(PHP5TO7_ZVAL_MAYBE_P(scale), self->data.decimal.scale);
545
PHP5TO7_ZEND_HASH_UPDATE(props, "scale", sizeof("scale"), PHP5TO7_ZVAL_MAYBE_P(scale), sizeof(zval));
551
php_driver_decimal_compare(zval *obj1, zval *obj2 TSRMLS_DC)
553
php_driver_numeric *decimal1 = NULL;
554
php_driver_numeric *decimal2 = NULL;
556
if (Z_OBJCE_P(obj1) != Z_OBJCE_P(obj2))
557
return 1; /* different classes */
559
decimal1 = PHP_DRIVER_GET_NUMERIC(obj1);
560
decimal2 = PHP_DRIVER_GET_NUMERIC(obj2);
562
if (decimal1->data.decimal.scale == decimal2->data.decimal.scale) {
563
return mpz_cmp(decimal1->data.decimal.value, decimal2->data.decimal.value);
564
} else if (decimal1->data.decimal.scale < decimal2->data.decimal.scale) {
572
php_driver_decimal_hash_value(zval *obj TSRMLS_DC)
574
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(obj);
575
return php_driver_mpz_hash((unsigned)self->data.decimal.scale, self->data.decimal.value);
579
php_driver_decimal_cast(zval *object, zval *retval, int type TSRMLS_DC)
581
php_driver_numeric *self = PHP_DRIVER_GET_NUMERIC(object);
585
return to_long(retval, self TSRMLS_CC);
587
return to_double(retval, self TSRMLS_CC);
589
return to_string(retval, self TSRMLS_CC);
598
php_driver_decimal_free(php5to7_zend_object_free *object TSRMLS_DC)
600
php_driver_numeric *self = PHP5TO7_ZEND_OBJECT_GET(numeric, object);
602
mpz_clear(self->data.decimal.value);
604
zend_object_std_dtor(&self->zval TSRMLS_CC);
605
PHP5TO7_MAYBE_EFREE(self);
608
static php5to7_zend_object
609
php_driver_decimal_new(zend_class_entry *ce TSRMLS_DC)
611
php_driver_numeric *self =
612
PHP5TO7_ZEND_OBJECT_ECALLOC(numeric, ce);
614
self->type = PHP_DRIVER_DECIMAL;
615
self->data.decimal.scale = 0;
616
mpz_init(self->data.decimal.value);
618
PHP5TO7_ZEND_OBJECT_INIT_EX(numeric, decimal, self, ce);
621
void php_driver_define_Decimal(TSRMLS_D)
625
INIT_CLASS_ENTRY(ce, PHP_DRIVER_NAMESPACE "\\Decimal", php_driver_decimal_methods);
626
php_driver_decimal_ce = zend_register_internal_class(&ce TSRMLS_CC);
627
zend_class_implements(php_driver_decimal_ce TSRMLS_CC, 2, php_driver_value_ce, php_driver_numeric_ce);
628
php_driver_decimal_ce->ce_flags |= PHP5TO7_ZEND_ACC_FINAL;
629
php_driver_decimal_ce->create_object = php_driver_decimal_new;
631
memcpy(&php_driver_decimal_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
632
php_driver_decimal_handlers.std.get_properties = php_driver_decimal_properties;
633
#if PHP_VERSION_ID >= 50400
634
php_driver_decimal_handlers.std.get_gc = php_driver_decimal_gc;
636
php_driver_decimal_handlers.std.compare_objects = php_driver_decimal_compare;
637
php_driver_decimal_handlers.std.cast_object = php_driver_decimal_cast;
639
php_driver_decimal_handlers.hash_value = php_driver_decimal_hash_value;
640
php_driver_decimal_handlers.std.clone_obj = NULL;