Daniel Kinzler has uploaded a new change for review.
https://gerrit.wikimedia.org/r/88771
Change subject: (bug #55511) Introducing DecimalValue
......................................................................
(bug #55511) Introducing DecimalValue
DecimalValue represent an arbitrary precision decimal number.
Change-Id: I964734c711f9949be9389a97953520ec7f562ca6
---
A DataValuesCommon/src/DataValues/DecimalValue.php
A DataValuesCommon/tests/DataValues/DecimalValueTest.php
2 files changed, 943 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DataValues
refs/changes/71/88771/1
diff --git a/DataValuesCommon/src/DataValues/DecimalValue.php
b/DataValuesCommon/src/DataValues/DecimalValue.php
new file mode 100644
index 0000000..9e81424
--- /dev/null
+++ b/DataValuesCommon/src/DataValues/DecimalValue.php
@@ -0,0 +1,542 @@
+<?php
+
+namespace DataValues;
+
+/**
+ * Class representing a decimal number with (nearly) arbitrary precision.
+ *
+ * For simple numeric values use @see NumberValue.
+ *
+ * The value notation for the value follows ISO 31-0, with some additional
restrictions:
+ * - the decimal separator is '.' (period). Comma is not used anywhere.
+ * - no spacing or other separators are included for groups of digits.
+ * - the first character in the string always gives the sign, either plus (+)
or minus (-).
+ * - scientific (exponential) notation is not used.
+ * - there no trailing zeros, except directly after the decimal point
+ * - there no leading zeros, except directly before the decimal point
+ *
+ * These rules are enforced by @see QUANTITY_VALUE_PATTERN
+ *
+ * @since 0.1
+ *
+ * @licence GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class DecimalValue extends DataValueObject {
+
+ /**
+ * The $value as a decimal string. See the class level documentation
for details.
+ *
+ * @see QUANTITY_VALUE_PATTERN
+ *
+ * @since 0.1
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * Regular expression for matching valid numbers.
+ */
+ const QUANTITY_VALUE_PATTERN = '/^[-+]([1-9][0-9]*|[0-9])(\.[0-9]+)?$/';
+
+ /**
+ * Constructs a new DecimalValue object, representing the given value.
+ *
+ * @since 0.1
+ *
+ * @param string|int|float $value If given as a string, the value must
match
+ * QUANTITY_VALUE_PATTERN.
+ * @throws IllegalValueException
+ */
+ public function __construct( $value ) {
+ if ( is_int( $value ) || is_float( $value ) ) {
+ $value = self::decimal( $value );
+ }
+
+ self::assertNumberString( $value, '$value' );
+
+ // round "negative" zero
+ $value = preg_replace( '/^-(0+(\.0+)?)$/', '+\1', $value );
+
+ $this->value = $value;
+ }
+
+ /**
+ * Checks that the given value is a number string.
+ *
+ * @param string $number The value to check
+ * @param string $name The name to use in error messages
+ *
+ * @throws IllegalValueException
+ */
+ protected static function assertNumberString( $number, $name ) {
+ if ( !is_string( $number ) ) {
+ throw new IllegalValueException( $name . ' must be a
numeric string' );
+ }
+
+ if ( !preg_match( self::QUANTITY_VALUE_PATTERN, $number ) ) {
+ throw new IllegalValueException( $name . ' must match
the pattern for numeric values: bad value: `' . $number . '`' );
+ }
+
+ if ( strlen( $number ) > 127 ) {
+ throw new IllegalValueException( $name . ' must be at
most 127 characters long.' );
+ }
+ }
+
+ /**
+ * Converts the given number to decimal notation
+ *
+ * @param int|float $number
+ *
+ * @return string
+ * @throws \InvalidArgumentException
+ */
+ protected static function decimal( $number ) {
+ if ( !is_int( $number ) && !is_float( $number ) ) {
+ throw new \InvalidArgumentException( '$number must be
an int or float' );
+ }
+
+ if ( $number === NAN || abs( $number ) === INF ) {
+ throw new \InvalidArgumentException( '$number must not
be NAN or INF' );
+ }
+
+ if ( is_int( $number ) ) {
+ $decimal = strval( abs( $number ) );
+ } else {
+ $decimal = trim( number_format( abs( $number ), 100,
'.', '' ), 0 );
+
+ if ( $decimal[0] === '.' ) {
+ $decimal = '0' . $decimal;
+ }
+
+ $last = strlen($decimal)-1;
+
+ if ( $decimal[$last] === '.' ) {
+ $decimal = $decimal . '0';
+ }
+ }
+
+ $decimal = ( ( $number >= 0.0 ) ? '+' : '-' ) . $decimal;
+
+ self::assertNumberString( $decimal, '$number' );
+ return $decimal;
+ }
+
+ /**
+ * Compares this DecimalValue to another DecimalValue.
+ *
+ * @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;
+ }
+
+ //FIXME: handle "minus null"!
+
+ // compare the integer parts
+ $aIntDigits = strpos( $a, '.' );
+ $bIntDigits = strpos( $b, '.' );
+ $aInt = ltrim( substr( $a, 1, ( $aIntDigits ? $aIntDigits :
strlen( $a ) ) -1 ), '0' );
+ $bInt = ltrim( substr( $b, 1, ( $bIntDigits ? $bIntDigits :
strlen( $b ) ) -1 ), '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
+ if ( $aInt > $bInt ) {
+ return $sense;
+ }
+
+ if ( $aInt < $bInt ) {
+ return -$sense;
+ }
+
+ // compare fractional parts
+ $aFract = rtrim( substr( $a, $aIntDigits +1 ), '0' );
+ $bFract = rtrim( substr( $b, $bIntDigits +1 ), '0' );
+
+ // the fractional part is left-aligned, so just check
alphanumeric ordering
+ $cmp = strcmp( $aFract, $bFract );
+ return ( $cmp > 0 ? 1 : ( $cmp < 0 ? -1 : 0 ) );
+ }
+
+ /**
+ * Returns the given value string, with any insignificant digits
removed or zeroed.
+ * Rounding is applied using the "round half away from zero" rule
(that is, +0.5 is
+ * rounded to +1 and -0.5 is rounded to -1).
+ *
+ * @since 0.1
+ *
+ * @param string $value
+ * @param int $significantDigits
+ *
+ * @return string
+ */
+ protected static function roundDecimal( $value, $significantDigits ) {
+ // whether the last character is already part of the integer
part of the decimal value
+ $inIntPart = ( strpos( $value, '.' ) === false );
+
+ $five = ord( '5' );
+ $length = strlen( $value );
+
+ $rounded = '';
+
+ // iterator over characters from back to front
+ for ( $i = $length -1; $i > 0 && $i > $significantDigits; $i--
) {
+ $ch = $value[$i];
+
+ if ( $ch === '.' ) {
+ $inIntPart = true;
+ $next = '.';
+ } else {
+ if ( $inIntPart ) {
+ // in the integer part, zero out
insignificant digits
+ $next = '0';
+ } else {
+ // in the fractional part, strip
insignificant digits
+ $next = '';
+ }
+
+ if ( ord( $ch ) >= $five ) {
+ // strip and apply rounding.
+ $remaining = substr( $value, 0, $i );
+
+ if ( $remaining[ strlen( $remaining )
-1 ] === '.' ) {
+ $remaining = rtrim( $remaining,
'.' );
+ $inIntPart = true;
+ }
+
+ // rounding may add digits, adjust $i
for that
+ $value = self::bumpDecimal( $remaining
);
+ $i = strlen( $value );
+ }
+ }
+
+ $rounded = $next . $rounded;
+ }
+
+ $rounded = substr( $value, 0, $i +1 ) . $rounded;
+
+ // strip trailing decimal point
+ $rounded = rtrim( $rounded, '.' );
+ return $rounded;
+ }
+
+ /**
+ * Increment the least significant digit by one if it is less than 9,
and
+ * set it to zero and continue to the next more significant digit if it
is 9.
+ * Exception: bump( 0 ) == 1;
+ *
+ * E.g.: bump( 0.2 ) == 0.3, bump( -0.09 ) == -0.10, bump( 9.99 ) ==
10.00
+ *
+ * This is the inverse of @see slump()
+ *
+ * @since 0.1
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function bumpDecimal( $value ) {
+ if ( $value === '+0' ) {
+ return '+1';
+ }
+
+ $bumped = '';
+
+ for ( $i = strlen( $value ) -1; $i >= 0; $i-- ) {
+ $ch = $value[$i];
+
+ if ( $ch === '.' ) {
+ $bumped = '.' . $bumped;
+ continue;
+ } elseif ( $ch === '9' ) {
+ $bumped = '0' . $bumped;
+ continue;
+ } elseif ( $ch === '+' || $ch === '-' ) {
+ $bumped = $ch . '1' . $bumped;
+ break;
+ } else {
+ $bumped = chr( ord( $ch ) + 1 ) . $bumped;
+ break;
+ }
+ }
+
+ $bumped = substr( $value, 0, $i ) . $bumped;
+ return $bumped;
+ }
+
+ /**
+ * Decrement the least significant digit by one if it is more than 0,
and
+ * set it to 9 and continue to the next more significant digit if it is
0.
+ * Exception: slump( 0 ) == -1;
+ *
+ * E.g.: slump( 0.2 ) == 0.1, slump( -0.10 ) == -0.01, slump( 0.0 ) ==
-1.0
+ *
+ * This is the inverse of @see bump()
+ *
+ * @since 0.1
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function slumpDecimal( $value ) {
+ self::assertNumberString( $value, '$value' );
+
+ if ( $value === '+0' ) {
+ return '-1';
+ }
+
+ // a "percise zero" will become negative
+ if ( preg_match( '/^\+0\.(0*)0$/', $value, $m ) ) {
+ return '-0.' . $m[1] . '1';
+ }
+
+ $slumped = '';
+
+ for ( $i = strlen( $value ) -1; $i >= 0; $i-- ) {
+ $ch = substr( $value, $i, 1 );
+
+ if ( $ch === '.' ) {
+ $slumped = '.' . $slumped;
+ continue;
+ } elseif ( $ch === '0' ) {
+ $slumped = '9' . $slumped;
+ continue;
+ } elseif ( $ch === '+' || $ch === '-' ) {
+ $slumped = '0';
+ break;
+ } else {
+ $slumped = chr( ord( $ch ) - 1 ) . $slumped;
+ break;
+ }
+ }
+
+ // preserve prefix
+ $slumped = substr( $value, 0, $i ) . $slumped;
+
+ // strip leading zeros
+ $slumped = preg_replace( '/^([-+])(0+)([0-9](\.|$))/', '\1\3',
$slumped );
+
+ if ( $slumped === '-0' ) {
+ $slumped = '+0';
+ }
+
+ return $slumped;
+ }
+
+ /**
+ * @see Serializable::serialize
+ *
+ * @since 0.1
+ *
+ * @return string
+ */
+ public function serialize() {
+ return serialize( $this->value );
+ }
+
+ /**
+ * @see Serializable::unserialize
+ *
+ * @since 0.1
+ *
+ * @param string $data
+ *
+ * @return DecimalValue
+ */
+ public function unserialize( $data ) {
+ $value = unserialize( $data );
+ $this->__construct( $value );
+ }
+
+ /**
+ * @see DataValue::getType
+ *
+ * @since 0.1
+ *
+ * @return string
+ */
+ public static function getType() {
+ return 'decimal';
+ }
+
+ /**
+ * @see DataValue::getSortKey
+ *
+ * @since 0.1
+ *
+ * @return float
+ */
+ public function getSortKey() {
+ return $this->getValueFloat();
+ }
+
+ /**
+ * Returns the quantity object.
+ * @see DataValue::getValue
+ *
+ * @since 0.1
+ *
+ * @return string
+ */
+ public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Returns the sign of the amount (+ or -).
+ *
+ * @return string "+" or "-".
+ */
+ public function getSign() {
+ return substr( $this->value, 0, 1 );
+ }
+
+ /**
+ * Returns the value held by this object, as a float.
+ * Equivalent to floatval( $this->getvalue() ).
+ *
+ * @since 0.1
+ *
+ * @return float
+ */
+ public function getValueFloat() {
+ return floatval( $this->getValue() );
+ }
+
+ /**
+ * Returns the value held by this object, as a string,
+ * with any insignificant digits removed or zeroed. Rounding is applied
+ * using the "round half away from zero" rule (that is, +0.5 is rounded
to +1 and
+ * -0.5 is rounded to -1).
+ *
+ * @since 0.1
+ *
+ * @param int $digits The significant number of sigits (not counting
the sign,
+ * but counting the decimal point )
+ *
+ * @return string
+ */
+ public function getValueRounded( $digits ) {
+ return self::roundDecimal( $this->value, $digits );
+ }
+
+ /**
+ * Returns the value held by this object, as a string,
+ * with the least significant digit incremented, e.g.:
+ * 2.01 becomes 2.02, -2.10 becomes -2.11, -2.9 becomes -3,
+ * and 10 becomes 11.
+ *
+ * @since 0.1
+ *
+ * @return string
+ */
+ public function getValueBumped() {
+ return self::bumpDecimal( $this->value );
+ }
+
+ /**
+ * Returns the value held by this object, as a string,
+ * with the least significant digit decremented, e.g.:
+ * 2.02 becomes 2.01, -2.10 becomes -2.09, -3 becomes -2,
+ * and 20 becomes 19.
+ *
+ * @since 0.1
+ *
+ * @return string
+ */
+ public function getValueSlumped() {
+ return self::slumpDecimal( $this->value );
+ }
+
+ /**
+ * Returns the product of this DecimalValue and the $factor parameter.
+ *
+ * @param DecimalValue $factor
+ *
+ * @return DataValue
+ */
+ public function product( DecimalValue $factor ) {
+ //TODO: use bcmath if available
+ $product = $this->getValueFloat() * $factor->getValueFloat();
+
+ return new DecimalValue( $product );
+ }
+
+ /**
+ * Returns the sum of this DecimalValue and the $offset parameter.
+ *
+ * @param DecimalValue $offset
+ *
+ * @return DataValue
+ */
+ public function sum( DecimalValue $offset ) {
+ //TODO: use bcmath if available
+ $product = $this->getValueFloat() + $offset->getValueFloat();
+
+ return new DecimalValue( $product );
+ }
+
+ /**
+ * @see DataValue::getArrayValue
+ *
+ * @since 0.1
+ *
+ * @return string
+ */
+ public function getArrayValue() {
+ return $this->value;
+ }
+
+ /**
+ * Constructs a new instance of the DataValue from the provided data.
+ * This can round-trip with @see getArrayValue
+ *
+ * @since 0.1
+ *
+ * @param string $data
+ *
+ * @return DecimalValue
+ * @throws IllegalValueException
+ */
+ public static function newFromArray( $data ) {
+ return new static( $data );
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString() {
+ return $this->value;
+ }
+}
diff --git a/DataValuesCommon/tests/DataValues/DecimalValueTest.php
b/DataValuesCommon/tests/DataValues/DecimalValueTest.php
new file mode 100644
index 0000000..2009258
--- /dev/null
+++ b/DataValuesCommon/tests/DataValues/DecimalValueTest.php
@@ -0,0 +1,401 @@
+<?php
+
+namespace DataValues\Tests;
+
+use DataValues\DecimalValue;
+
+/**
+ * @covers DataValues\DecimalValue
+ *
+ * @since 0.1
+ *
+ * @ingroup DataValue
+ *
+ * @group DataValue
+ * @group DataValueExtensions
+ *
+ * @licence GNU GPL v2+
+ *
+ * @author Daniel Kinzler
+ */
+class DecimalValueTest extends DataValueTest {
+
+ /**
+ * @see DataValueTest::getClass
+ *
+ * @since 0.1
+ *
+ * @return string
+ */
+ public function getClass() {
+ return 'DataValues\DecimalValue';
+ }
+
+ public function validConstructorArgumentsProvider() {
+ $argLists = array();
+
+ $argLists[] = array( 42 );
+ $argLists[] = array( -42 );
+ $argLists[] = array( '-42' );
+ $argLists[] = array( 4.2 );
+ $argLists[] = array( -4.2 );
+ $argLists[] = array( '+4.2' );
+ $argLists[] = array( 0 );
+ $argLists[] = array( 0.2 );
+ $argLists[] = array( '-0.42' );
+ $argLists[] = array( '-0.0' );
+ $argLists[] = array( '-0' );
+ $argLists[] = array( '+0.0' );
+ $argLists[] = array( '+0' );
+
+ return $argLists;
+ }
+
+ public function invalidConstructorArgumentsProvider() {
+ $argLists = array();
+
+ $argLists[] = array();
+
+
+ $argLists[] = array( 'foo' );
+ $argLists[] = array( '' );
+ $argLists[] = array( '4.2' );
+ $argLists[] = array( '++4.2' );
+ $argLists[] = array( '--4.2' );
+ $argLists[] = array( '-+4.2' );
+ $argLists[] = array( '+-4.2' );
+ $argLists[] = array( '-.42' );
+ $argLists[] = array( '+.42' );
+ $argLists[] = array( '.42' );
+ $argLists[] = array( '.0' );
+ $argLists[] = array( '-00' );
+ $argLists[] = array( '+01.2' );
+ $argLists[] = array( 'x2' );
+ $argLists[] = array( '2x' );
+ $argLists[] = array( '+0100' );
+ $argLists[] = array( false );
+ $argLists[] = array( true );
+ $argLists[] = array( null );
+ $argLists[] = array( '0x20' );
+
+ return $argLists;
+ }
+
+ /**
+ * @dataProvider compareProvider
+ *
+ * @since 0.1
+ */
+ public function testCompare( DecimalValue $a, DecimalValue $b,
$expected ) {
+ $actual = $a->compare( $b );
+ $this->assertSame( $expected, $actual );
+
+ $actual = $b->compare( $a );
+ $this->assertSame( -$expected, $actual );
+ }
+
+ public function compareProvider() {
+ return array(
+ 'zero/equal' => array( new DecimalValue( 0 ), new
DecimalValue( 0 ), 0 ),
+ 'zero-signs/equal' => array( new DecimalValue( '+0' ),
new DecimalValue( '-0' ), 0 ),
+ 'zero-digits/equal' => array( new DecimalValue( '+0' ),
new DecimalValue( '+0.000' ), 0 ),
+ 'digits/equal' => array( new DecimalValue( '+2.2' ),
new DecimalValue( '+2.2000' ), 0 ),
+ 'conversion/equal' => array( new DecimalValue( 2.5 ),
new DecimalValue( '+2.50' ), 0 ),
+ 'negative/equal' => array( new DecimalValue( '-1.33' ),
new DecimalValue( '-1.33' ), 0 ),
+
+ 'simple/smaller' => array( new DecimalValue( '+1' ),
new DecimalValue( '+2' ), -1 ),
+ 'simple/greater' => array( new DecimalValue( '+2' ),
new DecimalValue( '+1' ), +1 ),
+ 'negative/greater' => array( new DecimalValue( '-1' ),
new DecimalValue( '-2' ), +1 ),
+ 'negative/smaller' => array( new DecimalValue( '-2' ),
new DecimalValue( '-1' ), -1 ),
+
+ 'digits/greater' => array( new DecimalValue( '+11' ),
new DecimalValue( '+8' ), +1 ),
+ 'digits-sub/greater' => array( new DecimalValue( '+11'
), new DecimalValue( '+8.0' ), +1 ),
+ 'negative-digits/greater' => array( new DecimalValue(
'-11' ), new DecimalValue( '-80' ), +1 ),
+ 'small/greater' => array( new DecimalValue( '+0.050' ),
new DecimalValue( '+0.005' ), +1 ),
+
+ 'signs/greater' => array( new DecimalValue( '+1' ), new
DecimalValue( '-8' ), +1 ),
+ 'signs/less' => array( new DecimalValue( '-8' ), new
DecimalValue( '+1' ), -1 ),
+ );
+ }
+
+ /**
+ * @dataProvider getSignProvider
+ *
+ * @since 0.1
+ */
+ public function testGetSign( DecimalValue $value, $expected ) {
+ $actual = $value->getSign();
+ $this->assertSame( $expected, $actual );
+ }
+
+ public function getSignProvider() {
+ return array(
+ 'zero is positive' => array( new DecimalValue( 0 ), '+'
),
+ 'zero is always positive' => array( new DecimalValue(
'-0' ), '+' ),
+ 'zero is ALWAYS positive' => array( new DecimalValue(
'-0.00' ), '+' ),
+ '+1 is positive' => array( new DecimalValue( '+1' ),
'+' ),
+ '-1 is negative' => array( new DecimalValue( '-1' ),
'-' ),
+ '+0.01 is positive' => array( new DecimalValue( '+0.01'
), '+' ),
+ '-0.01 is negative' => array( new DecimalValue( '-0.01'
), '-' ),
+ );
+ }
+
+ /**
+ * @dataProvider getValueProvider
+ *
+ * @since 0.1
+ */
+ public function testGetValue( DecimalValue $value, $expected ) {
+ $actual = $value->getValue();
+ $this->assertSame( $expected, $actual );
+ }
+
+ public function getValueProvider() {
+ $argLists = array();
+
+ $argLists[] = array( new DecimalValue( 42 ), '+42' );
+ $argLists[] = array( new DecimalValue( -42 ), '-42' );
+ $argLists[] = array( new DecimalValue( '-42' ), '-42' );
+ $argLists[] = array( new DecimalValue( 4.5 ), '+4.5' );
+ $argLists[] = array( new DecimalValue( -4.5 ), '-4.5' );
+ $argLists[] = array( new DecimalValue( '+4.2' ), '+4.2' );
+ $argLists[] = array( new DecimalValue( 0 ), '+0' );
+ $argLists[] = array( new DecimalValue( 0.5 ), '+0.5' );
+ $argLists[] = array( new DecimalValue( '-0.42' ), '-0.42' );
+ $argLists[] = array( new DecimalValue( '-0.0' ), '+0.0' );
+ $argLists[] = array( new DecimalValue( '-0' ), '+0' );
+ $argLists[] = array( new DecimalValue( '+0.0' ), '+0.0' );
+ $argLists[] = array( new DecimalValue( '+0' ), '+0' );
+
+ return $argLists;
+ }
+
+ /**
+ * @dataProvider getValueFloatProvider
+ *
+ * @since 0.1
+ */
+ public function testGetValueFloat( DecimalValue $value, $expected ) {
+ $actual = $value->getValueFloat();
+ $this->assertSame( $expected, $actual );
+ }
+
+ public function getValueFloatProvider() {
+ $argLists = array();
+
+ $argLists[] = array( new DecimalValue( 42 ), 42.0 );
+ $argLists[] = array( new DecimalValue( -42 ), -42.0 );
+ $argLists[] = array( new DecimalValue( '-42' ), -42.0 );
+ $argLists[] = array( new DecimalValue( 4.5 ), 4.5 );
+ $argLists[] = array( new DecimalValue( -4.5 ), -4.5 );
+ $argLists[] = array( new DecimalValue( '+4.2' ), 4.2 );
+ $argLists[] = array( new DecimalValue( 0 ), 0.0 );
+ $argLists[] = array( new DecimalValue( 0.5 ), 0.5 );
+ $argLists[] = array( new DecimalValue( '-0.42' ), -0.42 );
+ $argLists[] = array( new DecimalValue( '-0.0' ), 0.0 );
+ $argLists[] = array( new DecimalValue( '-0' ), 0.0 );
+ $argLists[] = array( new DecimalValue( '+0.0' ), 0.0 );
+ $argLists[] = array( new DecimalValue( '+0' ), 0.0 );
+
+ return $argLists;
+ }
+
+ /**
+ * @dataProvider getGetValueBumpedProvider
+ *
+ * @since 0.1
+ */
+ public function testGetValueBumped( DecimalValue $value, $expected ) {
+ $actual = $value->getValueBumped();
+ $this->assertSame( $expected, $actual );
+ }
+
+ public function getGetValueBumpedProvider() {
+ return array(
+ array( new DecimalValue( '+0' ), '+1' ),
+ array( new DecimalValue( '-0' ), '+1' ),
+ array( new DecimalValue( '+0.0' ), '+0.1' ),
+ array( new DecimalValue( '-0.0' ), '+0.1' ),
+ array( new DecimalValue( '+1' ), '+2' ),
+ array( new DecimalValue( '-1' ), '-2' ),
+ array( new DecimalValue( '+10' ), '+11' ),
+ array( new DecimalValue( '-10' ), '-11' ),
+ array( new DecimalValue( '+9' ), '+10' ),
+ array( new DecimalValue( '-9' ), '-10' ),
+ array( new DecimalValue( '+0.01' ), '+0.02' ),
+ array( new DecimalValue( '-0.01' ), '-0.02' ),
+ array( new DecimalValue( '+0.09' ), '+0.10' ),
+ array( new DecimalValue( '-0.09' ), '-0.10' ),
+ array( new DecimalValue( '+0.9' ), '+1.0' ),
+ array( new DecimalValue( '-0.9' ), '-1.0' ),
+ );
+ }
+
+ /**
+ * @dataProvider getGetValueSlumpedProvider
+ *
+ * @since 0.1
+ */
+ public function testGetValueSlumped( DecimalValue $value, $expected ) {
+ $actual = $value->getValueSlumped();
+ $this->assertSame( $expected, $actual );
+ }
+
+ public function getGetValueSlumpedProvider() {
+ return array(
+ array( new DecimalValue( '+0' ), '-1' ),
+ array( new DecimalValue( '-0' ), '-1' ),
+ array( new DecimalValue( '+0.0' ), '-0.1' ),
+ array( new DecimalValue( '-0.0' ), '-0.1' ),
+ array( new DecimalValue( '+0.00' ), '-0.01' ),
+ array( new DecimalValue( '-0.00' ), '-0.01' ),
+ array( new DecimalValue( '+1' ), '+0' ),
+ array( new DecimalValue( '-1' ), '+0' ),
+ array( new DecimalValue( '+1.0' ), '+0.9' ),
+ array( new DecimalValue( '-1.0' ), '-0.9' ),
+ array( new DecimalValue( '+0.1' ), '+0.0' ),
+ array( new DecimalValue( '-0.1' ), '-0.0' ),
+ array( new DecimalValue( '+0.01' ), '+0.00' ),
+ array( new DecimalValue( '-0.01' ), '-0.00' ),
+ array( new DecimalValue( '+12' ), '+11' ),
+ array( new DecimalValue( '-12' ), '-11' ),
+ array( new DecimalValue( '+10' ), '+9' ),
+ array( new DecimalValue( '-10' ), '-9' ),
+ array( new DecimalValue( '+0.02' ), '+0.01' ),
+ array( new DecimalValue( '-0.02' ), '-0.01' ),
+ array( new DecimalValue( '+0.10' ), '+0.09' ),
+ array( new DecimalValue( '-0.10' ), '-0.09' ),
+ );
+ }
+
+ /**
+ * @dataProvider productProvider
+ */
+ public function testProduct( DecimalValue $a, DecimalValue $b, $value )
{
+ $actual = $a->product( $b );
+ $this->assertEquals( $value, $actual->getValue() );
+
+ $actual = $b->product( $a );
+ $this->assertEquals( $value, $actual->getValue() );
+ }
+
+ public function productProvider() {
+ return array(
+ array( new DecimalValue( '+0' ), new DecimalValue(
'+0' ), '+0' ),
+ array( new DecimalValue( '+0' ), new DecimalValue(
'+1' ), '+0' ),
+ array( new DecimalValue( '+0' ), new DecimalValue(
'+2' ), '+0' ),
+
+ array( new DecimalValue( '+1' ), new DecimalValue(
'+0' ), '+0' ),
+ array( new DecimalValue( '+1' ), new DecimalValue(
'+1' ), '+1' ),
+ array( new DecimalValue( '+1' ), new DecimalValue(
'+2' ), '+2' ),
+
+ array( new DecimalValue( '+2' ), new DecimalValue(
'+0' ), '+0' ),
+ array( new DecimalValue( '+2' ), new DecimalValue(
'+1' ), '+2' ),
+ array( new DecimalValue( '+2' ), new DecimalValue(
'+2' ), '+4' ),
+
+ array( new DecimalValue( '+0.5' ), new DecimalValue(
'+0' ), '+0' ),
+ array( new DecimalValue( '+0.5' ), new DecimalValue(
'+1' ), '+0.5' ),
+ array( new DecimalValue( '+0.5' ), new DecimalValue(
'+2' ), '+1' ),
+ );
+ }
+
+ /**
+ * @dataProvider sumProvider
+ */
+ public function testSum( DecimalValue $a, DecimalValue $b, $value ) {
+ $actual = $a->sum( $b );
+ $this->assertEquals( $value, $actual->getValue() );
+
+ $actual = $b->sum( $a );
+ $this->assertEquals( $value, $actual->getValue() );
+ }
+
+ public function sumProvider() {
+ return array(
+ array( new DecimalValue( '+0' ), new DecimalValue(
'+0' ), '+0' ),
+ array( new DecimalValue( '+0' ), new DecimalValue(
'+1' ), '+1' ),
+ array( new DecimalValue( '+0' ), new DecimalValue(
'+2' ), '+2' ),
+
+ array( new DecimalValue( '+2' ), new DecimalValue(
'+0' ), '+2' ),
+ array( new DecimalValue( '+2' ), new DecimalValue(
'+1' ), '+3' ),
+ array( new DecimalValue( '+2' ), new DecimalValue(
'+2' ), '+4' ),
+
+ array( new DecimalValue( '+0.5' ), new DecimalValue(
'+0' ), '+0.5' ),
+ array( new DecimalValue( '+0.5' ), new DecimalValue(
'+0.5' ), '+1.0' ),
+ array( new DecimalValue( '+0.5' ), new DecimalValue(
'+2' ), '+2.5' ),
+ );
+ }
+
+ /**
+ * @dataProvider getValueRoundedProvider
+ *
+ * @since 0.1
+ */
+ public function testGetValueRounded( DecimalValue $value, $digits,
$expected ) {
+ $actual = $value->getValueRounded( $digits );
+ $this->assertSame( $expected, $actual );
+ }
+
+ public function getValueRoundedProvider() {
+ $argLists = array();
+
+ //NOTE: Rounding is applied using the "round half away from
zero" logic.
+
+ $argLists[] = array( new DecimalValue( '+0' ), 1, '+0' );
+ $argLists[] = array( new DecimalValue( '+0' ), 2, '+0' );
+ $argLists[] = array( new DecimalValue( '+0.0' ), 1, '+0' );
+ $argLists[] = array( new DecimalValue( '+0.0' ), 2, '+0' );
+ $argLists[] = array( new DecimalValue( '+0.0' ), 3, '+0.0' );
+
+ $argLists[] = array( new DecimalValue( '-2' ), 1, '-2' );
+ $argLists[] = array( new DecimalValue( '-2' ), 2, '-2' );
+
+ $argLists[] = array( new DecimalValue( '+23' ), 1, '+20' );
+ $argLists[] = array( new DecimalValue( '+23' ), 2, '+23' );
+ $argLists[] = array( new DecimalValue( '+23' ), 3, '+23' );
+
+ $argLists[] = array( new DecimalValue( '-234' ), 1, '-200' );
+ $argLists[] = array( new DecimalValue( '-234' ), 2, '-230' );
+ $argLists[] = array( new DecimalValue( '-234' ), 3, '-234' );
+
+ $argLists[] = array( new DecimalValue( '-2.0' ), 1, '-2' );
+ $argLists[] = array( new DecimalValue( '-2.0' ), 2, '-2' );
// edge case, may change
+ $argLists[] = array( new DecimalValue( '-2.0' ), 3, '-2.0' );
+ $argLists[] = array( new DecimalValue( '-2.0' ), 4, '-2.0' );
// edge case, may change
+
+ $argLists[] = array( new DecimalValue( '-2.000' ), 1, '-2' );
+ $argLists[] = array( new DecimalValue( '-2.000' ), 2, '-2' );
+ $argLists[] = array( new DecimalValue( '-2.000' ), 3, '-2.0' );
+ $argLists[] = array( new DecimalValue( '-2.000' ), 4, '-2.00' );
+
+ $argLists[] = array( new DecimalValue( '+2.5' ), 1, '+3' ); //
rounded up
+ $argLists[] = array( new DecimalValue( '+2.5' ), 2, '+3' );
+ $argLists[] = array( new DecimalValue( '+2.5' ), 3, '+2.5' );
+ $argLists[] = array( new DecimalValue( '+2.5' ), 4, '+2.5' );
+
+ $argLists[] = array( new DecimalValue( '+2.05' ), 1, '+2' );
+ $argLists[] = array( new DecimalValue( '+2.05' ), 2, '+2' );
+ $argLists[] = array( new DecimalValue( '+2.05' ), 3, '+2.1' );
// rounded up
+ $argLists[] = array( new DecimalValue( '+2.05' ), 4, '+2.05' );
+
+ $argLists[] = array( new DecimalValue( '-23.05' ), 1, '-20' );
+ $argLists[] = array( new DecimalValue( '-23.05' ), 2, '-23' );
+ $argLists[] = array( new DecimalValue( '-23.05' ), 3, '-23' );
// edge case, may change
+ $argLists[] = array( new DecimalValue( '-23.05' ), 4, '-23.1'
); // rounded down
+ $argLists[] = array( new DecimalValue( '-23.05' ), 5, '-23.05'
);
+
+ $argLists[] = array( new DecimalValue( '+9.33' ), 1, '+9' ); //
no rounding
+ $argLists[] = array( new DecimalValue( '+9.87' ), 1, '+10' );
// rounding ripples up
+ $argLists[] = array( new DecimalValue( '+9.87' ), 3, '+9.9' );
// rounding ripples up
+ $argLists[] = array( new DecimalValue( '+99' ), 1, '+100' ); //
rounding ripples up
+ $argLists[] = array( new DecimalValue( '+99' ), 2, '+99' ); //
rounding ripples up
+
+ $argLists[] = array( new DecimalValue( '-9.33' ), 1, '-9' ); //
no rounding
+ $argLists[] = array( new DecimalValue( '-9.87' ), 1, '-10' );
// rounding ripples down
+ $argLists[] = array( new DecimalValue( '-9.87' ), 3, '-9.9' );
// rounding ripples down
+ $argLists[] = array( new DecimalValue( '-99' ), 1, '-100' ); //
rounding ripples down
+ $argLists[] = array( new DecimalValue( '-99' ), 2, '-99' ); //
rounding ripples down
+
+ return $argLists;
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/88771
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I964734c711f9949be9389a97953520ec7f562ca6
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/DataValues
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits