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 <daniel.kinz...@wikimedia.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to