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