Daniel Kinzler has uploaded a new change for review. https://gerrit.wikimedia.org/r/97907
Change subject: (bug 57588) Scientific notation for quantities. ...................................................................... (bug 57588) Scientific notation for quantities. This adds support scientific (exponent) notation to DecimalParser and QuantityParser. Change-Id: Iff70e26f46b10eb18b79861cc60dac8e4e15fa25 --- M DataValuesCommon/src/DataValues/DecimalMath.php M DataValuesCommon/src/ValueParsers/DecimalParser.php M DataValuesCommon/src/ValueParsers/QuantityParser.php M DataValuesCommon/tests/DataValues/DecimalMathTest.php M DataValuesCommon/tests/ValueParsers/DecimalParserTest.php M DataValuesCommon/tests/ValueParsers/QuantityParserTest.php 6 files changed, 156 insertions(+), 5 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DataValues refs/changes/07/97907/1 diff --git a/DataValuesCommon/src/DataValues/DecimalMath.php b/DataValuesCommon/src/DataValues/DecimalMath.php index 9930146..4ec0c6c 100644 --- a/DataValuesCommon/src/DataValues/DecimalMath.php +++ b/DataValuesCommon/src/DataValues/DecimalMath.php @@ -482,7 +482,7 @@ $slumped = substr( $value, 0, $i ) . $slumped; // strip leading zeros - $slumped = preg_replace( '/^([-+])(0+)([0-9]+(\.|$))/', '\1\3', $slumped ); + $slumped = $this->stripLeadingZeros( $slumped ); if ( $slumped === '-0' ) { $slumped = '+0'; @@ -490,4 +490,60 @@ return $slumped; } + + /** + * @param string $digits + * + * @return string + */ + protected function stripLeadingZeros( $digits ) { + $digits = preg_replace( '/^([-+])(0+)([0-9]+(\.|$))/', '\1\3', $digits ); + return $digits; + } + + /** + * Shift the decimal point according to the given exponent. + * + * @param DecimalValue $decimal + * @param int $exponent The exponent to apply (digits to shift by). A Positive exponent + * shifts the decimal point to the right, a negative exponent shifts to the left. + * + * @throws \InvalidArgumentException + * @return DecimalValue + */ + public function shift( DecimalValue $decimal, $exponent ) { + if ( !is_int( $exponent ) ) { + throw new \InvalidArgumentException( '$exponent must be an integer' ); + } + + if ( $exponent == 0 ) { + return $decimal; + } + + $sign = $decimal->getSign(); + $intPart = $decimal->getIntegerPart(); + $fractPart = $decimal->getFractionalPart(); + + if ( $exponent < 0 ) { + // shift left + if ( -$exponent < strlen( $intPart ) ) { + $intPart = substr( $intPart, 0, -$exponent ) . '.' . substr( $intPart, -$exponent ); + } else { + $intPart = '0.' . str_pad( $intPart, -$exponent, '0', STR_PAD_LEFT ); + } + } else { + // shift right + if ( $exponent < strlen( $fractPart ) ) { + $fractPart = substr( $fractPart, 0, $exponent ) . '.' . substr( $fractPart, $exponent ); + } else { + $fractPart = str_pad( $fractPart, $exponent, '0', STR_PAD_RIGHT ); + } + } + + // assemble result + $digits = $sign . $intPart . $fractPart; + $digits = $this->stripLeadingZeros( $digits ); + + return new DecimalValue( $digits ); + } } diff --git a/DataValuesCommon/src/ValueParsers/DecimalParser.php b/DataValuesCommon/src/ValueParsers/DecimalParser.php index 5d8e548..bc72ed8 100644 --- a/DataValuesCommon/src/ValueParsers/DecimalParser.php +++ b/DataValuesCommon/src/ValueParsers/DecimalParser.php @@ -2,6 +2,7 @@ namespace ValueParsers; +use DataValues\DecimalMath; use DataValues\DecimalValue; use DataValues\IllegalValueException; @@ -16,13 +17,29 @@ class DecimalParser extends StringValueParser { /** + * @var DecimalMath + */ + private $math; + + /** + * @return DecimalMath + */ + private function getMath() { + if ( $this->math === null ) { + $this->math = new DecimalMath(); + } + + return $this->math; + } + + /** * Creates a DecimalValue from a given string. * * The decimal notation for the value is based on ISO 31-0, with some modifications: * - the decimal separator is '.' (period). Comma is not used anywhere. * - leading and trailing as well as any internal whitespace is ignored * - the following characters are ignored: comma (","), apostrophe ("'"). - * - scientific (exponential) notation is not used. + * - scientific (exponential) notation is supported using the pattern /e[-+][0-9]+/ * - the number may start (or end) with a decimal point. * - leading zeroes are stripped, except directly before the decimal point * - trailing zeroes are stripped, except directly after the decimal point @@ -38,6 +55,17 @@ * @throws ParseException */ protected function stringParse( $value ) { + + //handle scientific notation + if ( preg_match( '/^(.*)([eE]|x10\^)([-+]?[,\d]+)$/', $value, $matches ) ) { + $exponent = $this->normalizeDecimal( $matches[3] ); + $exponent = intval( $exponent ); + + $value = $matches[1]; + } else { + $exponent = 0; + } + $value = $this->normalizeDecimal( $value ); if ( $value === '' ) { @@ -46,6 +74,12 @@ try { $decimal = new DecimalValue( $value ); + + if ( $exponent ) { + $math = $this->getMath(); + $decimal = $math->shift( $decimal, $exponent ); + } + return $decimal; } catch ( IllegalValueException $ex ) { throw new ParseException( $ex->getMessage() ); @@ -80,5 +114,4 @@ return $number; } - } diff --git a/DataValuesCommon/src/ValueParsers/QuantityParser.php b/DataValuesCommon/src/ValueParsers/QuantityParser.php index 3d1d1b5..611ddfb 100644 --- a/DataValuesCommon/src/ValueParsers/QuantityParser.php +++ b/DataValuesCommon/src/ValueParsers/QuantityParser.php @@ -17,7 +17,7 @@ */ class QuantityParser extends StringValueParser { - const NUMBER_PATTERN = '(?:[-+]\s*)?(?:[0-9,\'`]+\.[0-9,\'`]*|\.?[0-9,\'`]+)?'; + const NUMBER_PATTERN = '(?:[-+]\s*)?(?:[0-9,\'`]+\.[0-9,\'`]*|\.?[0-9,\'`]+)(?:[eE][-+]?[0-9,\'`]+)?'; const UNIT_PATTERN = '[a-zA-ZµåÅöÖ°%][-.a-zA-Z0-9åÅöÖ°%²³^]*'; @@ -105,7 +105,7 @@ $pattern = '@^' . '\s*(' . self::NUMBER_PATTERN . ')' // $1: amount . '\s*(?:' - . '([!~])' // $2: '!' for "exact", '~' for "approx", or nothing + . '([~!])' // $2: '!' for "exact", '~' for "approx", or nothing . '|(?:\+/?-|±)\s*(' . self::NUMBER_PATTERN . ')' // $3: plus/minus offset (uncertainty margin) . '|' // or nothing . ')' diff --git a/DataValuesCommon/tests/DataValues/DecimalMathTest.php b/DataValuesCommon/tests/DataValues/DecimalMathTest.php index 4ca9c9c..f9078e8 100644 --- a/DataValuesCommon/tests/DataValues/DecimalMathTest.php +++ b/DataValuesCommon/tests/DataValues/DecimalMathTest.php @@ -387,4 +387,55 @@ return $argLists; } + + /** + * @dataProvider shiftProvider + * + * @param DecimalValue $value + * @param $exponent + * @param $expected + */ + public function testShift( DecimalValue $value, $exponent, $expected ) { + $math = new DecimalMath(); + + $actual = $math->shift( $value, $exponent ); + $this->assertEquals( $expected, $actual->getValue() ); + } + + public function shiftProvider() { + $argLists = array(); + + $argLists[] = array( new DecimalValue( '+0' ), 0, '+0' ); + $argLists[] = array( new DecimalValue( '+0' ), 1, '+0' ); + $argLists[] = array( new DecimalValue( '+0' ), 2, '+0' ); + $argLists[] = array( new DecimalValue( '+0' ), -1, '+0.0' ); + $argLists[] = array( new DecimalValue( '+0' ), -2, '+0.00' ); + + $argLists[] = array( new DecimalValue( '+0.0' ), 0, '+0.0' ); + $argLists[] = array( new DecimalValue( '+0.0' ), 1, '+0' ); + $argLists[] = array( new DecimalValue( '+0.0' ), 2, '+0' ); + $argLists[] = array( new DecimalValue( '+0.0' ), -1, '+0.00' ); + $argLists[] = array( new DecimalValue( '+0.0' ), -2, '+0.000' ); + + $argLists[] = array( new DecimalValue( '-25' ), 0, '-25' ); + $argLists[] = array( new DecimalValue( '-25' ), 1, '-250' ); + $argLists[] = array( new DecimalValue( '-25' ), 2, '-2500' ); + $argLists[] = array( new DecimalValue( '-25' ), -1, '-2.5' ); + $argLists[] = array( new DecimalValue( '-25' ), -2, '-0.25' ); + $argLists[] = array( new DecimalValue( '-25' ), -3, '-0.025' ); + $argLists[] = array( new DecimalValue( '-25' ), -4, '-0.0025' ); + + $argLists[] = array( new DecimalValue( '-2.5' ), 0, '-2.5' ); + $argLists[] = array( new DecimalValue( '-2.5' ), 1, '-25' ); + $argLists[] = array( new DecimalValue( '-2.5' ), 2, '-250' ); + $argLists[] = array( new DecimalValue( '-2.5' ), -1, '-0.25' ); + $argLists[] = array( new DecimalValue( '-2.5' ), -2, '-0.025' ); + $argLists[] = array( new DecimalValue( '-2.5' ), -3, '-0.0025' ); + + $argLists[] = array( new DecimalValue( '+5' ), -4, '+0.0005' ); + $argLists[] = array( new DecimalValue( '+5.0' ), -4, '+0.00050' ); + $argLists[] = array( new DecimalValue( '+5.00' ), -4, '+0.000500' ); + + return $argLists; + } } diff --git a/DataValuesCommon/tests/ValueParsers/DecimalParserTest.php b/DataValuesCommon/tests/ValueParsers/DecimalParserTest.php index bcca87d..5ce83e1 100644 --- a/DataValuesCommon/tests/ValueParsers/DecimalParserTest.php +++ b/DataValuesCommon/tests/ValueParsers/DecimalParserTest.php @@ -45,6 +45,10 @@ ',3,' => 3, '2.125' => 2.125, '2.1250' => '+2.1250', + '2.1250e0' => '+2.1250', + '2.1250e3' => '+2125.0', + '2.1250e+3' => '+2125.0', + '2.1250e-2' => '+0.021250', ' 5 ' => 5, '100,000' => 100000, '100 000' => 100000, diff --git a/DataValuesCommon/tests/ValueParsers/QuantityParserTest.php b/DataValuesCommon/tests/ValueParsers/QuantityParserTest.php index 3b2082a..fcda9e4 100644 --- a/DataValuesCommon/tests/ValueParsers/QuantityParserTest.php +++ b/DataValuesCommon/tests/ValueParsers/QuantityParserTest.php @@ -50,6 +50,13 @@ '100,003' => QuantityValue::newFromNumber( 100003, '1', 100004, 100002 ), '100\'003' => QuantityValue::newFromNumber( 100003, '1', 100004, 100002 ), + '1.4e-2' => QuantityValue::newFromNumber( '+0.014', '1', '+0.015', '+0.013' ), + '1.4e3' => QuantityValue::newFromNumber( '+1400', '1', '+1401', '+1399' ), + '1.4e3!m' => QuantityValue::newFromNumber( '+1400', 'm', '+1400', '+1400' ), + '1.4e3m2' => QuantityValue::newFromNumber( '+1400', 'm2', '+1401', '+1399' ), + '1.4ev' => QuantityValue::newFromNumber( '+1.4', 'ev', '+1.5', '+1.3' ), + '1.4e' => QuantityValue::newFromNumber( '+1.4', 'e', '+1.5', '+1.3' ), + // precision '0!' => QuantityValue::newFromNumber( 0, '1', 0, 0 ), '10.003!' => QuantityValue::newFromNumber( '+10.003', '1', '+10.003', '+10.003' ), -- To view, visit https://gerrit.wikimedia.org/r/97907 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Iff70e26f46b10eb18b79861cc60dac8e4e15fa25 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