Daniel Kinzler has uploaded a new change for review. https://gerrit.wikimedia.org/r/89777
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, 126 insertions(+), 12 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DataValues refs/changes/77/89777/1 diff --git a/DataValuesCommon/src/DataValues/QuantityValue.php b/DataValuesCommon/src/DataValues/QuantityValue.php index ca8462f..535afaa 100644 --- a/DataValuesCommon/src/DataValues/QuantityValue.php +++ b/DataValuesCommon/src/DataValues/QuantityValue.php @@ -404,6 +404,73 @@ } /** + * 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) + * + * @param string $newUnit The unit of the transformed quantity. + * + * @param callable $transformation A callback that implements the desired transformation. + * The transformation will be called with a DecimalValue to transform as it's first + * parameter. 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 dc1a8f9..f8f9b2f 100644 --- a/DataValuesCommon/tests/DataValues/QuantityValueTest.php +++ b/DataValuesCommon/tests/DataValues/QuantityValueTest.php @@ -197,11 +197,11 @@ return array( array( QuantityValue::newFromDecimal( '+0', '1', '+0', '+0' ), 0 ), - array( QuantityValue::newFromDecimal( '+0', '1', '+1', '-1' ), 2 ), - array( QuantityValue::newFromDecimal( '+0.00', '1', '+0.01', '-0.01' ), 0.02 ), - array( QuantityValue::newFromDecimal( '+100', '1', '+101', '+99' ), 2 ), - array( QuantityValue::newFromDecimal( '+100.0', '1', '+100.1', '+99.9' ), 0.2 ), - array( QuantityValue::newFromDecimal( '+12.34', '1', '+12.35', '+12.33' ), 0.02 ), + array( QuantityValue::newFromDecimal( '+0' ), 2 ), + array( QuantityValue::newFromDecimal( '+0.00' ), 0.02 ), + array( QuantityValue::newFromDecimal( '+100' ), 2 ), + array( QuantityValue::newFromDecimal( '+100.0' ), 0.2 ), + array( QuantityValue::newFromDecimal( '+12.34' ), 0.02 ), array( QuantityValue::newFromDecimal( '+0', '1', '+0.2', '-0.6' ), 0.8 ), array( QuantityValue::newFromDecimal( '+7.3', '1', '+7.7', '+5.2' ), 2.5 ), @@ -219,8 +219,8 @@ public function getUncertaintyMarginProvider() { return array( - array( QuantityValue::newFromDecimal( '+0', '1', '+1', '-1' ), '+1' ), - array( QuantityValue::newFromDecimal( '+0.00', '1', '+0.01', '-0.01' ), '+0.01' ), + array( QuantityValue::newFromDecimal( '+0' ), '+1' ), + array( QuantityValue::newFromDecimal( '+0.00' ), '+0.01' ), array( QuantityValue::newFromDecimal( '+0', '1', '+0.2', '-0.6' ), '+0.6' ), array( QuantityValue::newFromDecimal( '+7.5', '1', '+7.5', '+5.5' ), '+2' ), @@ -241,12 +241,12 @@ public function getSignificantDigitsProvider() { return array( 0 => array( QuantityValue::newFromDecimal( '+0' ), 1 ), - 1 => array( QuantityValue::newFromDecimal( '-123', '1', '-123', '-123' ), 3 ), - 2 => array( QuantityValue::newFromDecimal( '-1.23', '1', '-1.23', '-1.23' ), 4 ), + 1 => array( QuantityValue::newFromDecimal( '-123' ), 3 ), + 2 => array( QuantityValue::newFromDecimal( '-1.23' ), 4 ), - 10 => array( QuantityValue::newFromDecimal( '-100', '1', '-99', '-101' ), 3 ), - 11 => array( QuantityValue::newFromDecimal( '+0.00', '1', '+0.01', '-0.01' ), 4 ), - 12 => array( QuantityValue::newFromDecimal( '-117.3', '1', '-117.2', '-117.4' ), 5 ), + 10 => array( QuantityValue::newFromDecimal( '-100' ), 3 ), + 11 => array( QuantityValue::newFromDecimal( '+0.00' ), 4 ), + 12 => array( QuantityValue::newFromDecimal( '-117.3' ), 5 ), 20 => array( QuantityValue::newFromDecimal( '+100', '1', '+100.01', '+99.97' ), 6 ), 21 => array( QuantityValue::newFromDecimal( '-0.002', '1', '-0.001', '-0.004' ), 5 ), @@ -257,4 +257,51 @@ 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; + }; + + $double = function ( DecimalValue $value ) { + return new DecimalValue( $value->getValueFloat() * 2 ); + }; + + $scale = function ( DecimalValue $value, $factor ) { + return new DecimalValue( $value->getValueFloat() * $factor ); + }; + + return array( + array( QuantityValue::newFromDecimal( '+0' ), $double, QuantityValue::newFromDecimal( '+0', '?', '+2', '-2' ) ), + array( QuantityValue::newFromDecimal( '+10' ), $identity, QuantityValue::newFromDecimal( '+10' ) ), + array( QuantityValue::newFromDecimal( '+10' ), $double, QuantityValue::newFromDecimal( '+20', '?', '+22', '+18' ) ), + array( QuantityValue::newFromDecimal( '-0.5' ), $identity, QuantityValue::newFromDecimal( '-0.5' ) ), + array( QuantityValue::newFromDecimal( '+0.5' ), $scale, QuantityValue::newFromDecimal( '+0.25', '?', '+0.3', '+0.2' ), 0.5 ), + + // note: absolutely exact values require conversion with infinite precision! + array( QuantityValue::newFromDecimal( '+100', '1', '+100', '+100' ), $scale, QuantityValue::newFromDecimal( '+12825.0', '?', '+12825.0', '+12825.0' ), 128.25 ), + + array( QuantityValue::newFromDecimal( '+100', '1', '+110', '+90' ), $scale, QuantityValue::newFromDecimal( '+330', '?', '+370', '+300' ), 3.3333 ), + array( QuantityValue::newFromDecimal( '+100', '1', '+100.1', '+99.9' ), $scale, QuantityValue::newFromDecimal( '+333.3', '?', '+333.7', '+333.0' ), 3.3333 ), + 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: newchange Gerrit-Change-Id: I3c49e98a9183c0af000bfbf1f5753f4396d1dad3 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