/** * Returns the order of magnitude of the uncertainty as the exponent of * last significant digit in the amount-string. The value returned by this * is suitable for use with @see DecimalMath::roundToExponent(). * * @example: if two digits after the decimal point are significant, this * returns -2. * * @example: if the last two digits before the decimal point are insignificant, * this returns 2. * * Note that this calculation assumes a symmetric uncertainty interval, * and can be misleading. * * @since 0.1 * * @return int */ public function getOrderOfUncertainty() { // the desired precision is given by the distance between the amount and // whatever is closer, the upper or lower bound. //TODO: use DecimalMath to avoid floating point errors! $amount = $this->getAmount()->getValueFloat(); $upperBound = $this->getUpperBound()->getValueFloat(); $lowerBound = $this->getLowerBound()->getValueFloat(); $precision = min($amount - $lowerBound, $upperBound - $amount); if ($precision === 0.0) { // If there is no uncertainty, the order of uncertainty is a bit more than what we have digits for. return -strlen($this->amount->getFractionalPart()); } // e.g. +/- 200 -> 2; +/- 0.02 -> -2 // note: we really want floor( log10( $precision ) ), but have to account for // small errors made in the floating point operations above. // @todo: use bcmath (via DecimalMath) to avoid this if possible $orderOfUncertainty = floor(log10($precision + 5.0E-10)); return (int) $orderOfUncertainty; }
/** * Compares this DecimalValue to another DecimalValue. * * @since 0.1 * * @param DecimalValue $that * * @throws LogicException * @return int +1 if $this > $that, 0 if $this == $that, -1 if $this < $that */ public function compare(DecimalValue $that) { if ($this === $that) { return 0; } $a = $this->getValue(); $b = $that->getValue(); if ($a === $b) { return 0; } if ($a[0] === '+' && $b[0] === '-') { return 1; } if ($a[0] === '-' && $b[0] === '+') { return -1; } // compare the integer parts $aInt = ltrim($this->getIntegerPart(), '0'); $bInt = ltrim($that->getIntegerPart(), '0'); $sense = $a[0] === '+' ? 1 : -1; // per precondition, there are no leading zeros, so the longer nummber is greater if (strlen($aInt) > strlen($bInt)) { return $sense; } if (strlen($aInt) < strlen($bInt)) { return -$sense; } // if both have equal length, compare alphanumerically $cmp = strcmp($aInt, $bInt); if ($cmp > 0) { return $sense; } if ($cmp < 0) { return -$sense; } // compare fractional parts $aFract = rtrim($this->getFractionalPart(), '0'); $bFract = rtrim($that->getFractionalPart(), '0'); // the fractional part is left-aligned, so just check alphanumeric ordering $cmp = strcmp($aFract, $bFract); return $cmp > 0 ? $sense : ($cmp < 0 ? -$sense : 0); }
/** * Shift the decimal point according to the given exponent. * * @param DecimalValue $decimal * @param int $exponent The exponent to apply (digits to shift by). A Positive exponent * shifts the decimal point to the right, a negative exponent shifts to the left. * * @throws InvalidArgumentException * @return DecimalValue */ public function shift(DecimalValue $decimal, $exponent) { if (!is_int($exponent)) { throw new InvalidArgumentException('$exponent must be an integer'); } if ($exponent == 0) { return $decimal; } $sign = $decimal->getSign(); $intPart = $decimal->getIntegerPart(); $fractPart = $decimal->getFractionalPart(); if ($exponent < 0) { $intPart = $this->shiftLeft($intPart, $exponent); } else { $fractPart = $this->shiftRight($fractPart, $exponent); } $digits = $sign . $intPart . $fractPart; $digits = $this->stripLeadingZeros($digits); return new DecimalValue($digits); }