Overview

Namespaces

  • chippyash
    • Type
      • Exceptions
      • Interfaces
      • Number
        • Complex
        • Rational
      • String

Classes

  • AbstractRationalType
  • GMPRationalType
  • RationalType
  • RationalTypeFactory
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Hard type support
  4:  * For when you absolutely want to know what you are getting
  5:  *
  6:  * @author Ashley Kitson <akitson@zf4.biz>
  7:  * @copyright Ashley Kitson, UK, 2014
  8:  * @licence GPL V3 or later : http://www.gnu.org/licenses/gpl.html
  9:  */
 10: 
 11: namespace chippyash\Type\Number\Rational;
 12: 
 13: use chippyash\Type\Exceptions\InvalidTypeException;
 14: use chippyash\Type\Number\IntType;
 15: use chippyash\Type\Number\GMPIntType;
 16: use chippyash\Type\Number\FloatType;
 17: use chippyash\Type\AbstractTypeFactory;
 18: 
 19: /**
 20:  * Static Factory for creating Rational types
 21:  */
 22: abstract class RationalTypeFactory extends AbstractTypeFactory
 23: {
 24:     /**
 25:      * default error tolerance for fromFloat()
 26:      */
 27:     const CF_DEFAULT_TOLERANCE = 1.e-15;
 28: 
 29:     /**
 30:      * Default error tolerance for from float
 31:      *
 32:      * @see setDefaultFromFloatTolerance()
 33:      * @see fromFloat()
 34:      *
 35:      * @var int
 36:      */
 37:     protected static $defaultTolerance = self::CF_DEFAULT_TOLERANCE;
 38: 
 39:     /**
 40:      * Rational type factory
 41:      * Construct and return a rational. You can send in
 42:      * - a string conforming to 'n/d'
 43:      * - a float
 44:      * - two ints (numerator, denominator)
 45:      *
 46:      * @param mixed $numerator
 47:      * @param mixed $denominator
 48:      *
 49:      * @return \chippyash\Type\Number\Rational\RationalType|\chippyash\Type\Number\Rational\GMPRationalType
 50:      *
 51:      * @throws InvalidTypeException
 52:      */
 53:     public static function create($numerator, $denominator = null)
 54:     {
 55:         if (is_string($numerator)) {
 56:             return self::fromString($numerator);
 57:         }
 58: 
 59:         if (is_float($numerator) || $numerator instanceof FloatType) {
 60:             return self::fromFloat($numerator);
 61:         }
 62: 
 63:         if (is_numeric($numerator)) {
 64:             return self::createFromNumericNumerator($numerator, $denominator);
 65:         }
 66: 
 67:         if ($numerator instanceof IntType) {
 68:             return self::createFromIntTypeNumerator($numerator, $denominator);
 69:         }
 70: 
 71:         self::throwCreateException($numerator, $denominator);
 72:     }
 73: 
 74:     /**
 75:      * Create a rational number from a float or FloatType
 76:      * Use Continued Fractions method of determining the rational number
 77:      *
 78:      * @param float|FloatType $float
 79:      * @param float|FloatType $tolerance -
 80:      *  Default is whatever is currently set but normally self::CF_DEFAULT_TOLERANCE
 81:      *
 82:      * @return \chippyash\Type\Number\Rational\RationalType|\chippyash\Type\Number\Rational\GMPRationalType
 83:      *
 84:      * @throws \InvalidArgumentException
 85:      */
 86:     public static function fromFloat($float, $tolerance = null)
 87:     {
 88:         if ($float instanceof FloatType) {
 89:             $float = $float();
 90:         }
 91:         if ($float == 0.0) {
 92:             return new RationalType(new IntType(0), new IntType(1));
 93:         }
 94:         if ($tolerance instanceof FloatType) {
 95:             $tolerance = $tolerance();
 96:         } elseif (is_null($tolerance)) {
 97:             $tolerance = self::$defaultTolerance;
 98:         }
 99: 
100:         $negative = ($float < 0);
101:         if ($negative) {
102:             $float = abs($float);
103:         }
104:         $num1 = 1;
105:         $num2 = 0;
106:         $den1 = 0;
107:         $den2 = 1;
108:         $oneOver = 1 / $float;
109:         do {
110:             $oneOver = 1 / $oneOver;
111:             $floor = floor($oneOver);
112:             $aux = $num1;
113:             $num1 = $floor * $num1 + $num2;
114:             $num2 = $aux;
115:             $aux = $den1;
116:             $den1 = $floor * $den1 + $den2;
117:             $den2 = $aux;
118:             $oneOver = $oneOver - $floor;
119:         } while (abs($float - $num1 / $den1) > $float * $tolerance);
120: 
121:         if ($negative) {
122:             $num1 *= -1;
123:         }
124:         
125:         return self::createCorrectRational($num1, $den1);
126:     }
127: 
128:     /**
129:      * Create a rational number from a string in form 'n/d'
130:      *
131:      * Thanks to Florian Wolters where I lifted this from and amended it
132:      * @link http://github.com/FlorianWolters/PHP-Component-Number-Fraction
133:      *
134:      * @param string $string
135:      *
136:      * @return \chippyash\Type\Number\Rational\RationalType|\chippyash\Type\Number\Rational\GMPRationalType
137:      *
138:      * @throws \InvalidArgumentException
139:      */
140:     public static function fromString($string)
141:     {
142:         $matches = array();
143:         $valid = \preg_match(
144:             '#^(-)? *?(\d+) *?/ *?(-)? *?(\d+)$#',
145:             \trim($string),
146:             $matches
147:         );
148: 
149:         if ($valid !== 1) {
150:             throw new \InvalidArgumentException(
151:                 'The string representation of the rational is invalid.'
152:             );
153:         }
154: 
155:         $numerator = $matches[2];
156:         $denominator = $matches[4];
157: 
158:         if ($matches[1] xor $matches[3]) {
159:             // There is one '-' sign: therefore the Rational is negative.
160:             $numerator *= -1;
161:         }
162: 
163:         return self::createCorrectRational($numerator, $denominator);
164:     }
165: 
166:     /**
167:      * Set the default tolerance for all fromFloat() operations
168:      * N.B. This sets a static so only needs to be done once
169:      *
170:      * @param int $tolerance
171:      *
172:      * @return void
173:      */
174:     public static function setDefaultFromFloatTolerance($tolerance)
175:     {
176:         self::$defaultTolerance = $tolerance;
177:     }
178:     
179:     /**
180:      * Create and return the correct number type rational
181:      * 
182:      * @param int $num
183:      * @param int $den
184:      *
185:      * @return \chippyash\Type\Number\Rational\RationalType|\chippyash\Type\Number\Rational\GMPRationalType
186:      */
187:     protected static function createCorrectRational($num, $den)
188:     {
189:         if (self::getRequiredType() == self::TYPE_GMP) {
190:             return new GMPRationalType(new GMPIntType($num), new GMPIntType($den));
191:         } else {
192:             return new RationalType(new IntType($num), new IntType($den));
193:         }
194:     }
195: 
196:     /**
197:      * Create where numerator is known to be numeric
198:      *
199:      * @param mixed $numerator Conforms to is_numeric()
200:      * @param mixed $denominator
201:      *
202:      * @return GMPRationalType|RationalType
203:      *
204:      * @throws InvalidTypeException
205:      */
206:     private static function createFromNumericNumerator($numerator, $denominator)
207:     {
208:         if (is_null($denominator)) {
209:             return self::createCorrectRational($numerator, 1);
210:         }
211: 
212:         if (is_numeric($denominator)) {
213:             return self::createCorrectRational($numerator, $denominator);
214:         }
215: 
216:         if ($denominator instanceof IntType) {
217:             return self::createCorrectRational($numerator, $denominator());
218:         }
219: 
220:         self::throwCreateException($numerator, $denominator);
221:     }
222: 
223:     /**
224:      * Create where numerator is known to be IntType
225:      *
226:      * @param IntType $numerator
227:      * @param mixed $denominator
228:      *
229:      * @return GMPRationalType|RationalType
230:      *
231:      * @throws InvalidTypeException
232:      */
233:     private static function createFromIntTypeNumerator(IntType $numerator, $denominator)
234:     {
235:         if ($denominator instanceof IntType) {
236:             return self::createCorrectRational($numerator(), $denominator());
237:         }
238: 
239:         if (is_null($denominator)) {
240:             return self::createCorrectRational($numerator(), 1);
241:         }
242: 
243:         if (is_numeric($denominator)) {
244:             return self::createCorrectRational($numerator(), $denominator);
245:         }
246: 
247:         self::throwCreateException($numerator, $denominator);
248:     }
249: 
250:     /**
251:      * Throw a create exception
252:      *
253:      * @param $numerator
254:      * @param $denominator
255:      *
256:      * @throws InvalidTypeException
257:      */
258:     private static function throwCreateException($numerator, $denominator)
259:     {
260:         $typeN = gettype($numerator);
261:         $typeD = gettype($denominator);
262:         throw new InvalidTypeException("{$typeN}:{$typeD} for Rational type construction");
263:     }
264: }
265: 
Chippyash Strong Types API documentation generated by ApiGen 2.8.0