Daniel Werner has submitted this change and it was merged.

Change subject: Introducing QuantityValue::transform
......................................................................


Introducing QuantityValue::transform

This introduces a general purpose method for transforming quantities,
keeping the amount, bounds and number of digits consistent.

Change-Id: I3c49e98a9183c0af000bfbf1f5753f4396d1dad3
---
M DataValuesCommon/src/DataValues/QuantityValue.php
M DataValuesCommon/tests/DataValues/QuantityValueTest.php
2 files changed, 122 insertions(+), 0 deletions(-)

Approvals:
  Daniel Werner: Checked; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/DataValuesCommon/src/DataValues/QuantityValue.php 
b/DataValuesCommon/src/DataValues/QuantityValue.php
index 35a3edc..11a9766 100644
--- a/DataValuesCommon/src/DataValues/QuantityValue.php
+++ b/DataValuesCommon/src/DataValues/QuantityValue.php
@@ -399,6 +399,80 @@
        }
 
        /**
+        * Returns a transformed value derived from this QuantityValue by 
applying
+        * the given transformation to the amount and the upper and lower 
bounds.
+        * The resulting amount and bounds are rounded to the significant 
number of
+        * digits. Note that for exact quantities (with at least one bound 
equal to
+        * the amount), no rounding is applied (since they are considered to 
have
+        * infinite precision).
+        *
+        * The transformation is provided as a callback, which must implement a
+        * monotonously increasing, fully differentiable function mapping a 
DecimalValue
+        * to a DecimalValue. Typically, it will be a linear transformation 
applying a
+        * factor and an offset.
+        *
+        * @param string $newUnit The unit of the transformed quantity.
+        *
+        * @param callable $transformation A callback that implements the 
desired transformation.
+        *        The transformation will be called three times, once for the 
amount, once
+        *        for the lower bound, and once for the upper bound. It must 
return a DecimalValue.
+        *        The first parameter passed to $transformation is the 
DecimalValue to transform
+        *        In addition, any extra parameters passed to transform() will 
be passed through
+        *        to the transformation callback.
+        *
+        * @param mixed ... Any extra parameters will be passed to the 
$transformation function.
+        *
+        * @throws \InvalidArgumentException
+        * @return QuantityValue
+        */
+       public function transform( $newUnit, $transformation ) {
+               if ( !is_callable( $transformation ) ) {
+                       throw new \InvalidArgumentException( '$transformation 
must be callable.' );
+               }
+
+               if ( !is_string( $newUnit ) ) {
+                       throw new \InvalidArgumentException( '$newUnit must be 
a string. Use "1" as the unit for unit-less quantities.' );
+               }
+
+               if ( $newUnit === '' ) {
+                       throw new \InvalidArgumentException( '$newUnit must not 
be empty. Use "1" as the unit for unit-less quantities.' );
+               }
+
+               $oldUnit = $this->getUnit();
+
+               if ( $newUnit === null ) {
+                       $newUnit = $oldUnit;
+               }
+
+               // Apply transformation by calling the $transform callback.
+               // The first argument for the callback is teh DataValue to 
transform. In addition,
+               // any extra arguments given for transform() are passed through.
+               $args = func_get_args();
+               array_shift( $args );
+
+               $args[0] = $this->getAmount();
+               $amount = call_user_func_array( $transformation, $args );
+
+               $args[0] = $this->getUpperBound();
+               $upperBound = call_user_func_array( $transformation, $args );
+
+               $args[0] = $this->getLowerBound();
+               $lowerBound = call_user_func_array( $transformation, $args );
+
+               // use a preliminary QuantityValue to determine the significant 
number of digits
+               $transformed = new QuantityValue( $amount, $newUnit, 
$upperBound, $lowerBound );
+               $digits = $transformed->getSignificantDigits();
+
+               // apply rounding to the significant digits
+               $math = new DecimalMath(  ); //TODO: Perhaps transform() should 
go into a QuantityTransformer class.
+               $amount = $math->round( $amount,  $digits );
+               $upperBound = $math->round( $upperBound, $digits );
+               $lowerBound = $math->round( $lowerBound, $digits );
+
+               return new QuantityValue( $amount, $newUnit, $upperBound, 
$lowerBound );
+       }
+
+       /**
         * @see DataValue::getArrayValue
         *
         * @since 0.1
diff --git a/DataValuesCommon/tests/DataValues/QuantityValueTest.php 
b/DataValuesCommon/tests/DataValues/QuantityValueTest.php
index 2ce727e..0acd85f 100644
--- a/DataValuesCommon/tests/DataValues/QuantityValueTest.php
+++ b/DataValuesCommon/tests/DataValues/QuantityValueTest.php
@@ -249,4 +249,52 @@
                        26 => array( QuantityValue::newFromDecimal( '+1000', 
'1', '+1100', '+900' ), 2 ),
                );
        }
+
+       /**
+        * @dataProvider transformProvider
+        */
+       public function testTransform( QuantityValue $quantity, 
$transformation, QuantityValue $expected ) {
+               $args = func_get_args();
+               $extraArgs = array_slice( $args, 3 );
+
+               $call = array( $quantity, 'transform' );
+               $callArgs = array_merge( array( 'x', $transformation ), 
$extraArgs );
+               $actual = call_user_func_array( $call, $callArgs );
+
+               $this->assertEquals( 'x', $actual->getUnit() );
+               $this->assertEquals( $expected->getAmount()->getValue(), 
$actual->getAmount()->getValue(), 'value' );
+               $this->assertEquals( $expected->getUpperBound()->getValue(), 
$actual->getUpperBound()->getValue(), 'upper bound' );
+               $this->assertEquals( $expected->getLowerBound()->getValue(), 
$actual->getLowerBound()->getValue(), 'lower bound' );
+       }
+
+       public function transformProvider() {
+               $identity = function ( DecimalValue $value ) {
+                       return $value;
+               };
+
+               $square = function ( DecimalValue $value ) {
+                       $v = $value->getValueFloat();
+                       return new DecimalValue( $v * $v * $v );
+               };
+
+               $scale = function ( DecimalValue $value, $factor ) {
+                       return new DecimalValue( $value->getValueFloat() * 
$factor );
+               };
+
+               return array(
+                        0 => array( QuantityValue::newFromDecimal( '+10',   
'1', '+11',  '+9' ),   $identity, QuantityValue::newFromDecimal(   '+10',    
'?',   '+11',    '+9' ) ),
+                        1 => array( QuantityValue::newFromDecimal(  '-0.5', 
'1', '-0.4', '-0.6' ), $identity, QuantityValue::newFromDecimal(    '-0.5',  
'?',    '-0.4',  '-0.6' ) ),
+                        2 => array( QuantityValue::newFromDecimal(  '+0',   
'1', '+1',   '-1' ),   $square,   QuantityValue::newFromDecimal(    '+0',    
'?',    '+1',    '-1' ) ),
+                        3 => array( QuantityValue::newFromDecimal( '+10',   
'1', '+11',  '+9' ),   $square,   QuantityValue::newFromDecimal( '+1000',    
'?', '+1300',  '+730' ) ), // note how rounding applies to bounds
+                        4 => array( QuantityValue::newFromDecimal(  '+0.5', 
'1', '+0.6', '+0.4' ), $scale,    QuantityValue::newFromDecimal(    '+0.25', 
'?',    '+0.3',  '+0.2' ), 0.5 ),
+
+                       // note: absolutely exact values require conversion 
with infinite precision!
+                       10 => array( QuantityValue::newFromDecimal( '+100', 
'1', '+100',   '+100' ),    $scale, QuantityValue::newFromDecimal( '+12825.0', 
'?', '+12825.0', '+12825.0' ), 128.25 ),
+
+                       11 => array( QuantityValue::newFromDecimal( '+100', 
'1', '+110',    '+90' ),    $scale, QuantityValue::newFromDecimal( '+330',    
'?', '+370',    '+300' ), 3.3333 ),
+                       12 => array( QuantityValue::newFromDecimal( '+100', 
'1', '+100.1',  '+99.9' ),  $scale, QuantityValue::newFromDecimal( '+333.3',  
'?', '+333.7',  '+333.0' ), 3.3333 ),
+                       13 => array( QuantityValue::newFromDecimal( '+100', 
'1', '+100.01', '+99.99' ), $scale, QuantityValue::newFromDecimal( '+333.33', 
'?', '+333.36', '+333.30' ), 3.3333 ),
+               );
+       }
+
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/89777
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I3c49e98a9183c0af000bfbf1f5753f4396d1dad3
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/DataValues
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Daniel Werner <[email protected]>
Gerrit-Reviewer: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to