/** * {@inheritDoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } try { return Decimal::create($value); } catch (\Exception $e) { throw ConversionException::conversionFailedFormat($value, $this->getName(), '0.0'); } }
private function isDecimal($v) { try { BigNumbers\Decimal::create($v); return true; } catch (\Exception $e) { return false; } }
public function testCreateFromDecimal() { $this->assertTrue(Decimal::create(Decimal::fromString('345.76'), 1)->equals(Decimal::fromString('345.8'))); $this->assertTrue(Decimal::create(Decimal::fromString('345.76'), 2)->equals(Decimal::fromString('345.76'))); }
function tableSchema($db, $table) { $pdo = $this->getPDO(); $db = str_replace('`', '', $db); $table = str_replace('`', '', $table); $stmt = $pdo->query("EXPLAIN `{$db}`.`{$table}`"); $props = []; while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $checks = []; $textLength = null; $dateFormat = null; $required = $row['Null'] == 'NO'; $default = $row['Default']; easy_matches: switch ($row['Type']) { case 'float': // FIXME: cheating. this should change the tolerance but // for expedience we will treat it as the same for now. // FIXME: cheating. this should change the tolerance but // for expedience we will treat it as the same for now. case 'double': $check = ['required' => $required, 'allowString' => true]; if ($default !== null) { $check['initial'] = (double) $default; } $checks[] = $this->registry->construct('check', 'double', $check); break; case 'date': case 'datetime': case 'timestamp': $check = ['required' => $required, 'initial' => $default, 'formats' => ['Y-m-d H:i:s', 'Y-m-d']]; $checks[] = $this->registry->construct('check', 'dateFormat', $check); $checks[] = $this->registry->construct('check', 'date', []); break; case 'time': $check = ['required' => $required, 'pattern' => '^[0-9]?[0-9]:[0-9]?[0-9]$', 'emptyMode' => \Fulfil\Check\String_::EMPTY_BLOCK, 'initial' => $default]; $checks[] = $this->registry->construct('check', 'string', $check); break; case 'tinyint(1)': if ($this->interpretBools) { $checks[] = $this->registry->construct('check', 'bool', ['required' => $required, 'trueValues' => [1, '1', true], 'falseValues' => [0, '0', false], 'initial' => $default === null ? null : $default !== '0']); } break; case 'text': case 'blob': $textLength = 65535; case 'mediumtext': case 'mediumblob': $textLength = 16777215; case 'longtext': case 'longblob': $textLength = 4294967295; case 'tinytext': case 'tinyblob': $textLength = $textLength ?: 255; $check = ['required' => $required, 'emptyMode' => \Fulfil\Check\String_::EMPTY_ALLOW, 'initial' => $default]; if (substr($row['Type'], -4) == 'text') { $check['lengthMax'] = $textLength; } else { $check['bytesMax'] = $textLength; } $checks[] = $this->registry->construct('check', 'string', $check); break; } if (!$checks) { if (preg_match('/^ (?P<kind> medium | tiny | big )? int \\( (?P<len> [0-9]+ ) \\) (?P<unsigned> \\s+ unsigned )? $/ix ', $row['Type'], $intMatch)) { integer_type: $max = null; switch ($intMatch['kind']) { case 'big': $check = ['required' => $required]; $max = Num::create(2); if (isset($match['unsigned'])) { $check['min'] = 0; $check['max'] = $max->pow(Num::create(64))->sub(Num::create(1)); } else { $check['max'] = $max->pow(Num::create(63))->sub(Num::create(1)); $check['min'] = $max->pow(Num::create(63))->mul(Num::create(-1)); } if ($default !== null) { $check['default'] = Num::create($default); } $checks[] = $this->registry->construct('check', 'decimal', $check); break; case '': $max = $max ?: 4294967296; case 'medium': $max = $max ?: 65536; case 'tiny': $max = $max ?: 256; default: $check = ['required' => $required, 'allowString' => true]; if ($default !== null) { $check['initial'] = (int) $default; } if (isset($intMatch['unsigned'])) { $check['min'] = 0; $check['max'] = $max - 1; } else { $check['min'] = -($max / 2); $check['max'] = $max / 2 - 1; } $checks[] = $this->registry->construct('check', 'int', $check); break; } } elseif (preg_match('/ ^ ( decimal | numeric ) \\( (?P<precision> [0-9]+ ) , [ ]? (?P<scale> [0-9]+ ) \\) (?P<unsigned> [ ] unsigned )? $/xi', $row['Type'], $decimalMatch)) { decimal_type: $check = ['required' => $required, 'precision' => $decimalMatch['precision'], 'scale' => $decimalMatch['scale']]; if ($default !== null) { $check['default'] = Num::create($default); } $range = Num::create(10)->pow(Num::create($check['precision']))->sub(Num::create(1)->div(Num::create(10)->pow(Num::create($check['scale'])))); $check['min'] = isset($decimalMatch['unsigned']) ? 0 : $range->mul(Num::create(-1)); $check['max'] = $range; $checks[] = $this->registry->construct('check', 'decimal', $check); } elseif (preg_match('/ ^ (?P<var> var)? (?P<kind> char|binary) \\( (?P<len>[0-9]+) \\) $/xi', $row['Type'], $charMatch)) { string_type: $check = ['required' => $required, 'emptyMode' => \Fulfil\Check\String_::EMPTY_ALLOW, 'initial' => $default]; if ($charMatch['kind'] == 'char') { $check['lengthMax'] = (int) $charMatch['len']; } else { $check['bytesMax'] = (int) $charMatch['len']; } $checks[] = $this->registry->construct('check', 'string', $check); } elseif (preg_match('/ ^ (?P<kind> enum | set ) \\( (?P<values> .* ) \\) $/xi', $row['Type'], $enumMatch)) { enum_type: $check = ['required' => $required, 'initial' => $default]; // FIXME: enums and sets seem to allow an empty string, but // there's some malarkey about "error strings" $values = ['']; foreach (token_get_all('<?php ' . $enumMatch['values']) as $idx => $t) { if (!$idx) { continue; } elseif ($t == ',') { continue; } elseif ($t[0] == T_CONSTANT_ENCAPSED_STRING) { $values[] = substr($t[1], 1, -1); } else { throw new \Exception(); } } $check['values'] = $values; $check['emptyMode'] = \Fulfil\Check\String_::EMPTY_ALLOW; $check = $this->registry->construct('check', 'string', $check); if ($enumMatch['kind'] == 'enum') { $checks[] = $check; } else { if ($check->initial) { $check->initial = explode(',', $check->initial); } $checks[] = $this->registry->construct('check', 'list', ['defaultItem' => $check]); } } } if (!$checks) { throw new \Exception("No checks for {$row['Type']}"); } $props[$row['Field']] = $checks; } $schema = $this->registry->construct('check', 'schema', ['props' => $props]); return $schema; }
protected function applyValue($input, Context $ctx) { $output = $input; cast: $allowString = $this->allowString === null ? true : $this->allowString; try { $cast = $input; if ($allowString && is_string($input)) { if ($input === '') { $cast = null; } else { $cast = BigNumbers\Decimal::fromString($input); } } elseif ($this->allowInt && is_int($input)) { $cast = BigNumbers\Decimal::fromInteger($input); } elseif ($this->allowDouble && is_float($input)) { $cast = BigNumbers\Decimal::fromFloat($input); } if ($cast !== $input) { $ctx->setChange(Change::Internal); } $output = $cast; } catch (\Exception $ex) { $ctx->addReason($this, ['id' => 'decimal.invalid']); goto done; } // must cast before this so we can cast to null if ($output === null) { goto done; } type: if (!$output instanceof BigNumbers\Decimal) { $ctx->addReason($this, ['id' => 'decimal.invalid']); goto done; } scale: if ($this->scale !== null) { if (preg_match('/\\.([0-9]+)$/', $output . '', $match)) { $inScale = strlen($match[1]); if ($inScale > $this->scale) { $ctx->addReason($this, ['id' => 'decimal.scale', 'params' => ['scale' => $inScale, 'expected' => $this->scale]]); } } } precision: if ($this->precision !== null) { $digits = preg_replace("/[^0-9]/", '', $output . ''); $inPrecision = strlen($digits); if ($inPrecision > $this->precision) { $ctx->addReason($this, ['id' => 'decimal.precision', 'params' => ['precision' => $inPrecision, 'expected' => $this->precision]]); } } minmax: $min = $this->min !== null ? BigNumbers\Decimal::create($this->min) : null; $max = $this->max !== null ? BigNumbers\Decimal::create($this->max) : null; if ($min !== null && $max !== null) { if ($output->comp($min) < 0 || $output->comp($max) > 0) { $ctx->addReason($this, ['id' => 'decimal.between', 'params' => ['atLeast' => $min . '', 'atMost' => $max . '']]); } } elseif ($min !== null) { if ($output->comp($min) < 0) { $ctx->addReason($this, ['id' => 'decimal.atLeast', 'params' => ['atLeast' => $min . '']]); } } elseif ($max !== null) { if ($output->comp($max) > 0) { $ctx->addReason($this, ['id' => 'decimal.atMost', 'params' => ['atMost' => $max . '']]); } } divisibleBy: if ($this->divisibleBy !== null) { $divisibleBy = !$this->divisibleBy instanceof BigNumbers\Decimal ? BigNumbers\Decimal::create($this->divisibleBy) : $this->divisibleBy; if (!$output->mod($divisibleBy)->isZero()) { $dvFmt = $this->removeTrailingZeroes($divisibleBy); $ctx->addReason($this, ['id' => 'decimal.divisibleBy', 'params' => ['divisibleBy' => $dvFmt]]); } } done: if ($this->emitString && $output instanceof BigNumbers\Decimal) { $output = $output->innerValue(); if (!is_string($input)) { $ctx->setChange(Change::Internal); } } return $output; }
/** * {@inheritDoc} */ public function unserialize($serialized) { $unserialized = unserialize($serialized); $this->amount = Decimal::create($unserialized['amount']); $this->currency = unserialize($unserialized['currency']); }
/** * Get item price (with or without VAT based on _pricesWithVat setting) * * @param CartItemInterface item * @param int quantity (null to use item quantity) * @return void */ public function getItemPrice(CartItemInterface $item, $quantity = null) { $price = Decimal::create($item->getUnitPrice()); // when listed as gross if ($this->_pricesWithVat) { $price = $price->mul(Decimal::fromFloat(1 + (double) $item->getTaxRate() / 100)); } return $price->mul(Decimal::fromInteger(is_null($quantity) ? $item->getCartQuantity() : (int) $quantity))->round($this->_roundingDecimals); }
/** * BCRound implementation * * @param number|Decimal $number * @param int $precision * @param int $roundingMode * @return Decimal * @throws InvalidArgumentException */ public static final function bcround($number, $precision, $roundingMode = self::ROUND_HALF_UP) { if ($number instanceof Decimal) { $number = (string) $number; } $precision = self::normalizePrecision($precision); if (self::isNotDecimalString($number)) { return Decimal::create($number, $precision); } if ($roundingMode === self::ROUND_HALF_UP) { return Decimal::create(self::bcRoundHalfUp($number, $precision), $precision); } $firstDecimalAfterPrecision = self::getFirstDecimalAfterPrecision($number, $precision); if ($firstDecimalAfterPrecision === self::HALF) { $result = self::roundTied($number, $precision, $roundingMode); } else { $result = self::roundNotTied($firstDecimalAfterPrecision, $number, $precision); } /* * Arbitrary precision arithmetic allows for '-0.0' which is not equal to '0.0' if compared with bccomp. * We have no use for this behaviour, so negative numbers have to be checked if they are minus zero, * so we can convert them into unsigned zero and return that. */ $result = self::normalizeZero($result, $precision); return Decimal::create($result, $precision); }
function dataEqual() { return [[true, 1, 1], [false, 1, 2, 'equal.intNotEqual'], [true, 1, 1.0], [true, 1.0, 1.0], [false, 1.0, 2, 'equal.doubleNotEqual'], [true, -1, -1], [true, Decimal::create("1"), Decimal::create("1")], [false, Decimal::create("2"), Decimal::create("1"), 'equal.decimalNotEqual'], [true, new \DateTime('2015-01-01'), new \DateTime('2015-01-01')], [true, new \DateTime('2015-01-01 14:00', new \DateTimeZone('Australia/Melbourne')), new \DateTime('2015-01-01 03:00', new \DateTimeZone('UTC'))], [false, new \DateTime('2015-01-01 01:00'), new \DateTime('2015-01-01 00:00'), 'equal.dateNotEqual'], [true, 'foo', 'foo'], [false, 'foo', 'bar', 'equal.stringNotEqual'], [true, \Normalizer::normalize('zwölf', \Normalizer::FORM_C), \Normalizer::normalize('zwölf', \Normalizer::FORM_D)], [true, true, true], [true, false, false], [false, true, false]]; }
/** * {@inheritdoc} */ public function isNegative() { return -1 == $this->amount->comp(Decimal::create(0), 10); }