1 /*
  2     Copyright 2008-2021
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Alfred Wassermann
  7 
  8     This file is part of JSXGraph.
  9 
 10     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 11 
 12     You can redistribute it and/or modify it under the terms of the
 13 
 14       * GNU Lesser General Public License as published by
 15         the Free Software Foundation, either version 3 of the License, or
 16         (at your option) any later version
 17       OR
 18       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 19 
 20     JSXGraph is distributed in the hope that it will be useful,
 21     but WITHOUT ANY WARRANTY; without even the implied warranty of
 22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 23     GNU Lesser General Public License for more details.
 24 
 25     You should have received a copy of the GNU Lesser General Public License and
 26     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 27     and <http://opensource.org/licenses/MIT/>.
 28  */
 29 
 30 
 31 /*global JXG: true, define: true*/
 32 /*jslint nomen: true, plusplus: true*/
 33 
 34 /* depends:
 35  jxg
 36  math/math
 37  utils/type
 38  */
 39 
 40 define(['jxg', 'math/math', 'utils/type'], function (JXG, Mat, Type) {
 41 
 42     "use strict";
 43 
 44     JXG.Math.DoubleBits = function() {
 45         var hasTypedArrays = false,
 46             DOUBLE_VIEW = new Float64Array(1),
 47             UINT_VIEW   = new Uint32Array(DOUBLE_VIEW.buffer),
 48             doubleBitsLE, toDoubleLE, lowUintLE, highUintLE,
 49             doubleBitsBE, toDoubleBE, lowUintBE, highUintBE,
 50             doubleBits, toDouble, lowUint, highUint;
 51 
 52         if (Float64Array !== undefined) {
 53 
 54             DOUBLE_VIEW[0] = 1.0;
 55             hasTypedArrays = true;
 56             if (UINT_VIEW[1] === 0x3ff00000) {
 57                 // Use little endian
 58                 doubleBitsLE = function(n) {
 59                     DOUBLE_VIEW[0] = n;
 60                     return [UINT_VIEW[0], UINT_VIEW[1]];
 61                 };
 62                 toDoubleLE = function(lo, hi) {
 63                     UINT_VIEW[0] = lo;
 64                     UINT_VIEW[1] = hi;
 65                     return DOUBLE_VIEW[0];
 66                 };
 67 
 68                 lowUintLE = function(n) {
 69                     DOUBLE_VIEW[0] = n;
 70                     return UINT_VIEW[0];
 71                 };
 72 
 73                 highUintLE = function(n) {
 74                     DOUBLE_VIEW[0] = n;
 75                     return UINT_VIEW[1];
 76                 };
 77 
 78                 this.doubleBits = doubleBitsLE;
 79                 this.pack = toDoubleLE;
 80                 this.lo = lowUintLE;
 81                 this.hi = highUintLE;
 82             } else if (UINT_VIEW[0] === 0x3ff00000) {
 83                 //Use big endian
 84                 doubleBitsBE = function(n) {
 85                     DOUBLE_VIEW[0] = n;
 86                     return [UINT_VIEW[1], UINT_VIEW[0]];
 87                 };
 88 
 89                 toDoubleBE = function(lo, hi) {
 90                     UINT_VIEW[1] = lo;
 91                     UINT_VIEW[0] = hi;
 92                     return DOUBLE_VIEW[0];
 93                 };
 94 
 95                 lowUintBE = function(n) {
 96                     DOUBLE_VIEW[0] = n;
 97                     return UINT_VIEW[1];
 98                 };
 99 
100                 highUintBE = function(n) {
101                     DOUBLE_VIEW[0] = n;
102                     return UINT_VIEW[0];
103                 };
104 
105                 this.doubleBits = doubleBitsBE;
106                 this.pack = toDoubleBE;
107                 this.lo = lowUintBE;
108                 this.hi = highUintBE;
109             } else {
110                 hasTypedArrays = false;
111             }
112         }
113 
114         // if (!hasTypedArrays) {
115         //     var buffer = new Buffer(8)
116         //     doubleBits = function(n) {
117         //         buffer.writeDoubleLE(n, 0, true);
118         //         return [buffer.readUInt32LE(0, true), buffer.readUInt32LE(4, true)];
119         //     };
120 
121         //     toDouble = function(lo, hi) {
122         //         buffer.writeUInt32LE(lo, 0, true);
123         //         buffer.writeUInt32LE(hi, 4, true);
124         //         return buffer.readDoubleLE(0, true);
125         //     };
126         //     lowUint = function(n) {
127         //         buffer.writeDoubleLE(n, 0, true);
128         //         return buffer.readUInt32LE(0, true);
129         //     };
130 
131         //     highUint = function(n) {
132         //         buffer.writeDoubleLE(n, 0, true);
133         //         return buffer.readUInt32LE(4, true);
134         //     };
135 
136         //     this.doubleBits = doubleBits;
137         //     this.pack = toDouble;
138         //     this.lo = lowUint;
139         //     this.hi = highUint;
140         // }
141     };
142 
143     JXG.extend(JXG.Math.DoubleBits.prototype, /** @lends JXG.Math.DoubleBits.prototype */ {
144 
145         sign: function(n) {
146             return this.hi(n) >>> 31;
147         },
148 
149         exponent: function(n) {
150             var b = this.hi(n);
151             return ((b<<1) >>> 21) - 1023;
152         },
153 
154         fraction: function(n) {
155             var lo = this.lo(n),
156                 hi = this.hi(n),
157                 b = hi & ((1<<20) - 1);
158 
159             if (hi & 0x7ff00000) {
160                 b += (1<<20);
161             }
162             return [lo, b];
163         },
164 
165         denormalized: function(n) {
166             var hi = this.hi(n);
167             return !(hi & 0x7ff00000);
168         }
169     });
170 
171     var doubleBits = new JXG.Math.DoubleBits(),
172 
173         /**
174          * Object for interval arithmetic
175          * @name JXG.Math.Interval
176          * @exports MatInterval as JXG.Math.Interval
177          * @namespace
178          */
179         MatInterval = function (lo, hi) {
180             if (lo !== undefined && hi !== undefined) {
181                 // possible cases:
182                 // - Interval(1, 2)
183                 // - Interval(Interval(1, 1), Interval(2, 2))     // singletons are required
184                 if (Mat.IntervalArithmetic.isInterval(lo)) {
185                     if (!Mat.IntervalArithmetic.isSingleton(lo)) {
186                         throw new TypeError('JXG.Math.IntervalArithmetic: interval `lo` must be a singleton');
187                     }
188                     this.lo = lo.lo;
189                 } else {
190                     this.lo = lo;
191                 }
192                 if (Mat.IntervalArithmetic.isInterval(hi)) {
193                     if (!Mat.IntervalArithmetic.isSingleton(hi)) {
194                         throw new TypeError('JXG.Math.IntervalArithmetic: interval `hi` must be a singleton');
195                     }
196                     this.hi = hi.hi;
197                 } else {
198                     this.hi = hi;
199                 }
200             } else if (lo !== undefined) {
201                 // possible cases:
202                 // - Interval([1, 2])
203                 // - Interval([Interval(1, 1), Interval(2, 2)])
204                 if (Array.isArray(lo)) {
205                   return new MatInterval(lo[0], lo[1]);
206                 }
207                 // - Interval(1)
208                 return new MatInterval(lo, lo);
209             } else { // This else is necessary even if jslint declares it as redundant
210                 // possible cases:
211                 // - Interval()
212                 this.lo = this.hi = 0;
213             }
214         };
215 
216     JXG.extend(MatInterval.prototype, {
217         print: function() {
218             console.log('[',this.lo, this.hi,']');
219         },
220 
221         set: function(lo, hi) {
222             this.lo = lo;
223             this.hi = hi;
224             return this;
225         },
226 
227         bounded: function(lo, hi) {
228             return this.set(Mat.IntervalArithmetic.prev(lo), Mat.IntervalArithmetic.next(hi));
229         },
230 
231         boundedSingleton: function(v) {
232             return this.bounded(v, v);
233         },
234 
235         assign: function(lo, hi) {
236             if (typeof lo !== 'number' || typeof hi !== 'number') {
237                 throw new TypeError('JXG.Math.Interval#assign: arguments must be numbers');
238             }
239             if (isNaN(lo) || isNaN(hi) || lo > hi) {
240                 return this.setEmpty();
241             }
242             return this.set(lo, hi);
243         },
244 
245         setEmpty: function() {
246             return this.set(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY);
247         },
248 
249         setWhole: function() {
250             return this.set(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY);
251         },
252 
253         open: function(lo, hi){
254             return this.assign(Mat.IntervalArithmetic.next(lo), Mat.IntervalArithmetic.prev(hi));
255         },
256 
257         halfOpenLeft: function(lo, hi) {
258             return this.assign(Mat.IntervalArithmetic.next(lo), hi);
259         },
260 
261         halfOpenRight: function(lo, hi) {
262             return this.assign(lo, Mat.IntervalArithmetic.prev(hi));
263         },
264 
265         toArray: function() {
266             return [this.lo, this.hi];
267         },
268 
269         clone: function() {
270             return new MatInterval().set(this.lo, this.hi);
271         }
272     });
273 
274     /**
275      * Object for interval arithmetic
276      * @name JXG.Math.Interval
277      * @exports Mat.Interval as JXG.Math.Interval
278      * @namespace
279      */
280     JXG.Math.IntervalArithmetic =  {
281 
282         Interval: function(lo, hi) {
283             return new MatInterval(lo, hi);
284         },
285 
286         isInterval: function(i) {
287             return i !== null && typeof i === 'object' && typeof i.lo === 'number' && typeof i.hi === 'number';
288         },
289 
290         isSingleton: function(i) {
291             return i.lo === i.hi;
292         },
293 
294         /*
295          * Arithmetics
296          */
297         add: function(x, y) {
298             if (Type.isNumber(x)) {
299                 x = this.Interval(x);
300             }
301             if (Type.isNumber(y)) {
302                 y = this.Interval(y);
303             }
304             return new MatInterval(this.addLo(x.lo, y.lo), this.addHi(x.hi, y.hi));
305         },
306 
307         sub: function(x, y) {
308             if (Type.isNumber(x)) {
309                 x = this.Interval(x);
310             }
311             if (Type.isNumber(y)) {
312                 y = this.Interval(y);
313             }
314             return new MatInterval(this.subLo(x.lo, y.hi), this.subHi(x.hi, y.lo));
315         },
316 
317         mul: function(x, y) {
318             var xl, xh, yl, yh, out;
319 
320             if (Type.isNumber(x)) {
321                 x = this.Interval(x);
322             }
323             if (Type.isNumber(y)) {
324                 y = this.Interval(y);
325             }
326 
327             if (this.isEmpty(x) || this.isEmpty(y)) {
328               return this.EMPTY.clone();
329             }
330             xl = x.lo;
331             xh = x.hi;
332             yl = y.lo;
333             yh = y.hi;
334             out = new MatInterval();
335 
336             if (xl < 0) {
337                 if (xh > 0) {
338                     if (yl < 0) {
339                         if (yh > 0) {
340                             // mixed * mixed
341                             out.lo = Math.min(this.mulLo(xl, yh), this.mulLo(xh, yl));
342                             out.hi = Math.max(this.mulHi(xl, yl), this.mulHi(xh, yh));
343                         } else {
344                             // mixed * negative
345                             out.lo = this.mulLo(xh, yl);
346                             out.hi = this.mulHi(xl, yl);
347                         }
348                     } else {
349                         if (yh > 0) {
350                             // mixed * positive
351                             out.lo = this.mulLo(xl, yh);
352                             out.hi = this.mulHi(xh, yh);
353                         } else {
354                             // mixed * zero
355                             out.lo = 0;
356                             out.hi = 0;
357                         }
358                     }
359                 } else {
360                     if (yl < 0) {
361                         if (yh > 0) {
362                             // negative * mixed
363                             out.lo = this.mulLo(xl, yh);
364                             out.hi = this.mulHi(xl, yl);
365                         } else {
366                             // negative * negative
367                             out.lo = this.mulLo(xh, yh);
368                             out.hi = this.mulHi(xl, yl);
369                         }
370                     } else {
371                         if (yh > 0) {
372                             // negative * positive
373                             out.lo = this.mulLo(xl, yh);
374                             out.hi = this.mulHi(xh, yl);
375                         } else {
376                             // negative * zero
377                             out.lo = 0;
378                             out.hi = 0;
379                         }
380                     }
381                 }
382             } else {
383                 if (xh > 0) {
384                     if (yl < 0) {
385                         if (yh > 0) {
386                             // positive * mixed
387                             out.lo = this.mulLo(xh, yl);
388                             out.hi = this.mulHi(xh, yh);
389                         } else {
390                             // positive * negative
391                             out.lo = this.mulLo(xh, yl);
392                             out.hi = this.mulHi(xl, yh);
393                         }
394                     } else {
395                         if (yh > 0) {
396                             // positive * positive
397                             out.lo = this.mulLo(xl, yl);
398                             out.hi = this.mulHi(xh, yh);
399                         } else {
400                             // positive * zero
401                             out.lo = 0;
402                             out.hi = 0;
403                         }
404                     }
405                 } else {
406                     // zero * any other value
407                     out.lo = 0;
408                     out.hi = 0;
409                 }
410             }
411             return out;
412         },
413 
414         div: function(x, y) {
415             if (Type.isNumber(x)) {
416                 x = this.Interval(x);
417             }
418             if (Type.isNumber(y)) {
419                 y = this.Interval(y);
420             }
421 
422             if (this.isEmpty(x) || this.isEmpty(y)) {
423                 return this.EMPTY.clone();
424             }
425             if (this.zeroIn(y)) {
426                 if (y.lo !== 0) {
427                     if (y.hi !== 0) {
428                         return this.divZero(x);
429                     }
430                     return this.divNegative(x, y.lo);
431                 }
432                 if (y.hi !== 0) {
433                     return this.divPositive(x, y.hi);
434                 }
435                 return this.EMPTY.clone();
436             }
437             return this.divNonZero(x, y);
438         },
439 
440         positive: function(x) {
441             return new MatInterval(x.lo, x.hi);
442         },
443 
444         negative: function(x) {
445             if (Type.isNumber(x)) {
446                 return new MatInterval(-x);
447             }
448             return new MatInterval(-x.hi, -x.lo);
449         },
450 
451         /*
452          * Utils
453          */
454         isEmpty: function(i) {
455             return i.lo > i.hi;
456         },
457 
458         isWhole: function(i){
459             return i.lo === -Infinity && i.hi === Infinity;
460         },
461 
462         zeroIn: function(i) {
463             return this.hasValue(i, 0);
464         },
465 
466         hasValue: function(i, value) {
467             if (this.isEmpty(i)) {
468                 return false;
469             }
470             return i.lo <= value && value <= i.hi;
471         },
472 
473         hasInterval: function(x, y) {
474             if (this.isEmpty(x)) {
475                 return true;
476             }
477             return !this.isEmpty(y) && y.lo <= x.lo && x.hi <= y.hi;
478         },
479 
480         intervalsOverlap: function(x, y) {
481             if (this.isEmpty(x) || this.isEmpty(y)) {
482                 return false;
483             }
484             return (x.lo <= y.lo && y.lo <= x.hi) || (y.lo <= x.lo && x.lo <= y.hi);
485         },
486 
487         /*
488          * Division
489          */
490         divNonZero: function(x, y) {
491             var xl = x.lo,
492                 xh = x.hi,
493                 yl = y.lo,
494                 yh = y.hi,
495                 out = new MatInterval();
496 
497             if (xh < 0) {
498                 if (yh < 0) {
499                     out.lo = this.divLo(xh, yl);
500                     out.hi = this.divHi(xl, yh);
501                 } else {
502                     out.lo = this.divLo(xl, yl);
503                     out.hi = this.divHi(xh, yh);
504                 }
505             } else if (xl < 0) {
506                 if (yh < 0) {
507                     out.lo = this.divLo(xh, yh);
508                     out.hi = this.divHi(xl, yh);
509                 } else {
510                     out.lo = this.divLo(xl, yl);
511                     out.hi = this.divHi(xh, yl);
512                 }
513             } else {
514                 if (yh < 0) {
515                     out.lo = this.divLo(xh, yh);
516                     out.hi = this.divHi(xl, yl);
517                 } else {
518                     out.lo = this.divLo(xl, yh);
519                     out.hi = this.divHi(xh, yl);
520                 }
521             }
522             return out;
523         },
524 
525         divPositive: function(x, v) {
526             if (x.lo === 0 && x.hi === 0) {
527                 return x;
528             }
529 
530             if (this.zeroIn(x)) {
531                 // mixed considering zero in both ends
532                 return this.WHOLE;
533             }
534 
535             if (x.hi < 0) {
536                 // negative / v
537                 return new MatInterval(Number.NEGATIVE_INFINITY, this.divHi(x.hi, v));
538             }
539             // positive / v
540             return new MatInterval(this.divLo(x.lo, v), Number.POSITIVE_INFINITY);
541         },
542 
543         divNegative: function(x, v) {
544             if (x.lo === 0 && x.hi === 0) {
545                 return x;
546             }
547 
548             if (this.zeroIn(x)) {
549                 // mixed considering zero in both ends
550                 return this.WHOLE;
551             }
552 
553             if (x.hi < 0) {
554                 // negative / v
555                 return new MatInterval(this.divLo(x.hi, v), Number.POSITIVE_INFINITY);
556             }
557             // positive / v
558             return new MatInterval(Number.NEGATIVE_INFINITY, this.divHi(x.lo, v));
559         },
560 
561         divZero: function(x) {
562             if (x.lo === 0 && x.hi === 0) {
563                 return x;
564             }
565             return this.WHOLE;
566         },
567 
568         /*
569          * Algebra
570          */
571         fmod: function(x, y) {
572             var yb, n;
573             if (Type.isNumber(x)) {
574                 x = this.Interval(x);
575             }
576             if (Type.isNumber(y)) {
577                 y = this.Interval(y);
578             }
579             if (this.isEmpty(x) || this.isEmpty(y)) {
580                 return this.EMPTY.clone();
581             }
582             yb = x.lo < 0 ? y.lo : y.hi;
583             n = x.lo / yb;
584             if (n < 0) {
585                 n = Math.ceil(n);
586             } else {
587                 n = Math.floor(n);
588             }
589             // x mod y = x - n * y
590             return this.sub(x, this.mul(y, new MatInterval(n)));
591         },
592 
593         multiplicativeInverse: function(x) {
594             if (Type.isNumber(x)) {
595                 x = this.Interval(x);
596             }
597             if (this.isEmpty(x)) {
598                 return this.EMPTY.clone();
599             }
600             if (this.zeroIn(x)) {
601                 if (x.lo !== 0) {
602                     if (x.hi !== 0) {
603                         // [negative, positive]
604                         return this.WHOLE;
605                     }
606                     // [negative, zero]
607                     return new MatInterval(Number.NEGATIVE_INFINITY, this.divHi(1, x.lo));
608                 }
609                 if (x.hi !== 0) {
610                     // [zero, positive]
611                     return new MatInterval(this.divLo(1, x.hi), Number.POSITIVE_INFINITY);
612                 }
613                 // [zero, zero]
614                 return this.EMPTY.clone();
615             }
616             // [positive, positive]
617             return new MatInterval(this.divLo(1, x.hi), this.divHi(1, x.lo));
618         },
619 
620         pow: function(x, power) {
621             var yl, yh;
622 
623             if (Type.isNumber(x)) {
624                 x = this.Interval(x);
625             }
626             if (this.isEmpty(x)) {
627                 return this.EMPTY.clone();
628             }
629             if (this.isInterval(power)) {
630                 if (!this.isSingleton(power)) {
631                     return this.EMPTY.clone();
632                 }
633                 power = power.lo;
634             }
635 
636             if (power === 0) {
637                 if (x.lo === 0 && x.hi === 0) {
638                     // 0^0
639                     return this.EMPTY.clone();
640                 }
641                 // x^0
642                 return this.ONE.clone();
643             }
644             if (power < 0) {
645                 // compute [1 / x]^-power if power is negative
646                 return this.pow(this.multiplicativeInverse(x), -power);
647             }
648 
649             // power > 0
650             if (power % 1 === 0) { // isSafeInteger(power) as boolean) {
651                 // power is integer
652                 if (x.hi < 0) {
653                     // [negative, negative]
654                     // assume that power is even so the operation will yield a positive interval
655                     // if not then just switch the sign and order of the interval bounds
656                     yl = this.powLo(-x.hi, power);
657                     yh = this.powHi(-x.lo, power);
658                     if ((power & 1) === 1) {
659                         // odd power
660                         return new MatInterval(-yh, -yl);
661                     }
662                     // even power
663                     return new MatInterval(yl, yh);
664                 }
665                 if (x.lo < 0) {
666                     // [negative, positive]
667                     if ((power & 1) === 1) {
668                         return new MatInterval(-this.powLo(-x.lo, power), this.powHi(x.hi, power));
669                     }
670                     // even power means that any negative number will be zero (min value = 0)
671                     // and the max value will be the max of x.lo^power, x.hi^power
672                     return new MatInterval(0, this.powHi(Math.max(-x.lo, x.hi), power));
673                 }
674                 // [positive, positive]
675                 return new MatInterval(this.powLo(x.lo, power), this.powHi(x.hi, power));
676             }
677             console.warn('power is not an integer, you should use nth-root instead, returning an empty interval');
678             return this.EMPTY.clone();
679         },
680 
681         sqrt: function(x) {
682             if (Type.isNumber(x)) {
683                 x = this.Interval(x);
684             }
685             return this.nthRoot(x, 2);
686         },
687 
688         nthRoot: function(x, n) {
689             var power,yl, yh, yp, yn;
690 
691             if (Type.isNumber(x)) {
692                 x = this.Interval(x);
693             }
694             if (this.isEmpty(x) || n < 0) {
695               // compute 1 / x^-power if power is negative
696               return this.EMPTY.clone();
697             }
698 
699             // singleton interval check
700             if (this.isInterval(n)) {
701                 if (!this.isSingleton(n)) {
702                     return this.EMPTY.clone();
703                 }
704                 n = n.lo;
705             }
706 
707             power = 1 / n;
708             if (x.hi < 0) {
709                 // [negative, negative]
710                 //if ((isSafeInteger(n) as boolean) && (n & 1) === 1) {
711                 if (n % 1 === 0 && (n & 1) === 1) {
712                     // when n is odd we can always take the nth root
713                     yl = this.powHi(-x.lo, power);
714                     yh = this.powLo(-x.hi, power);
715                     return new MatInterval(-yl, -yh);
716                 }
717 
718                 // n is not odd therefore there's no nth root
719                 return this.EMPTY.clone();
720             }
721             if (x.lo < 0) {
722                 // [negative, positive]
723                 yp = this.powHi(x.hi, power);
724                 // if ((isSafeInteger(n) as boolean) && (n & 1) === 1) {
725                 if (n % 1 === 0 && (n & 1) === 1) {
726                     // nth root of x.lo is possible (n is odd)
727                     yn = -this.powHi(-x.lo, power);
728                     return new MatInterval(yn, yp);
729                 }
730                 return new MatInterval(0, yp);
731             }
732             // [positive, positive]
733             return new MatInterval(this.powLo(x.lo, power), this.powHi(x.hi, power));
734         },
735 
736         /*
737          * Misc
738          */
739         exp: function(x) {
740             if (Type.isNumber(x)) {
741                 x = this.Interval(x);
742             }
743             if (this.isEmpty(x)) {
744                 return this.EMPTY.clone();
745             }
746             return new MatInterval(this.expLo(x.lo), this.expHi(x.hi));
747         },
748 
749         log: function(x) {
750             var l;
751             if (Type.isNumber(x)) {
752                 x = this.Interval(x);
753             }
754             if (this.isEmpty(x)) {
755                 return this.EMPTY.clone();
756             }
757             l = x.lo <= 0 ? Number.NEGATIVE_INFINITY : this.logLo(x.lo);
758             return new MatInterval(l, this.logHi(x.hi));
759         },
760 
761         ln: function(x) {
762             return this.log(x);
763         },
764 
765         // export const LOG_EXP_10 = this.log(new MatInterval(10, 10))
766         // export const LOG_EXP_2 = log(new MatInterval(2, 2))
767         log10: function(x) {
768             if (this.isEmpty(x)) {
769                 return this.EMPTY.clone();
770             }
771             return this.div(this.log(x), this.log(new MatInterval(10, 10)));
772         },
773 
774         log2: function(x) {
775             if (this.isEmpty(x)) {
776                 return this.EMPTY.clone();
777             }
778             return this.div(this.log(x), this.log(new MatInterval(2, 2)));
779         },
780 
781         hull: function(x, y) {
782             var badX = this.isEmpty(x),
783                 badY = this.isEmpty(y);
784             if (badX && badY) {
785                 return this.EMPTY.clone();
786             }
787             if (badX) {
788                 return y.clone();
789             }
790             if (badY) {
791                 return x.clone();
792             }
793             return new MatInterval(Math.min(x.lo, y.lo), Math.max(x.hi, y.hi));
794         },
795 
796         intersection: function(x, y) {
797             var lo, hi;
798             if (this.isEmpty(x) || this.isEmpty(y)) {
799                 return this.EMPTY.clone();
800             }
801             lo = Math.max(x.lo, y.lo);
802             hi = Math.min(x.hi, y.hi);
803             if (lo <= hi) {
804                 return new MatInterval(lo, hi);
805             }
806             return this.EMPTY.clone();
807         },
808 
809         union: function(x, y) {
810             if (!this.intervalsOverlap(x, y)) {
811                 throw new Error('Interval#unions do not overlap');
812             }
813             return new MatInterval(Math.min(x.lo, y.lo), Math.max(x.hi, y.hi));
814         },
815 
816         difference: function(x, y) {
817             if (this.isEmpty(x) || this.isWhole(y)) {
818               return this.EMPTY.clone();
819             }
820             if (this.intervalsOverlap(x, y)) {
821                 if (x.lo < y.lo && y.hi < x.hi) {
822                     // difference creates multiple subsets
823                     throw new Error('Interval.difference: difference creates multiple intervals');
824                 }
825 
826                 // handle corner cases first
827                 if ((y.lo <= x.lo && y.hi === Infinity) || (y.hi >= x.hi && y.lo === -Infinity)) {
828                     return this.EMPTY.clone();
829                 }
830 
831                 // NOTE: empty interval is handled automatically
832                 // e.g.
833                 //
834                 //    n = difference([0,1], [0,1]) // n = Interval(next(1), 1) = EMPTY
835                 //    isEmpty(n) === true
836                 //
837                 if (y.lo <= x.lo) {
838                     return new MatInterval().halfOpenLeft(y.hi, x.hi);
839                 }
840 
841                 // y.hi >= x.hi
842                 return new MatInterval().halfOpenRight(x.lo, y.lo);
843             }
844             return x.clone();
845         },
846 
847         width: function(x) {
848             if (this.isEmpty(x)) {
849               return 0;
850             }
851             return this.subHi(x.hi, x.lo);
852         },
853 
854         abs: function(x) {
855             if (Type.isNumber(x)) {
856                 x = this.Interval(x);
857             }
858             if (this.isEmpty(x)) {
859                 return this.EMPTY.clone();
860             }
861             if (x.lo >= 0) {
862                 return x.clone();
863             }
864             if (x.hi <= 0) {
865                 return this.negative(x);
866             }
867             return new MatInterval(0, Math.max(-x.lo, x.hi));
868         },
869 
870         max: function(x, y) {
871             var badX = this.isEmpty(x),
872                 badY = this.isEmpty(y);
873             if (badX && badY) {
874                 return this.EMPTY.clone();
875             }
876             if (badX) {
877                 return y.clone();
878             }
879             if (badY) {
880                 return x.clone();
881             }
882             return new MatInterval(Math.max(x.lo, y.lo), Math.max(x.hi, y.hi));
883         },
884 
885         min: function(x, y) {
886             var badX = this.isEmpty(x),
887                 badY = this.isEmpty(y);
888             if (badX && badY) {
889                 return this.EMPTY.clone();
890             }
891             if (badX) {
892                 return y.clone();
893             }
894             if (badY) {
895                 return x.clone();
896             }
897             return new MatInterval(Math.min(x.lo, y.lo), Math.min(x.hi, y.hi));
898         },
899 
900         /*
901          * Trigonometric
902          */
903         onlyInfinity: function(x) {
904             return !isFinite(x.lo) && x.lo === x.hi;
905         },
906 
907         _handleNegative: function(interval) {
908             var n;
909             if (interval.lo < 0) {
910                 if (interval.lo === -Infinity) {
911                     interval.lo = 0;
912                     interval.hi = Infinity;
913                 } else {
914                     n = Math.ceil(-interval.lo / this.piTwiceLow);
915                     interval.lo += this.piTwiceLow * n;
916                     interval.hi += this.piTwiceLow * n;
917                 }
918             }
919             return interval;
920         },
921 
922         cos: function(x) {
923             var cache, pi2, t, cosv,
924                 lo, hi, rlo, rhi;
925 
926             if (this.isEmpty(x) || this.onlyInfinity(x)) {
927                 return this.EMPTY.clone();
928             }
929 
930             // create a clone of `x` because the clone is going to be modified
931             cache = new MatInterval().set(x.lo, x.hi);
932             this._handleNegative(cache);
933 
934             pi2 = this.PI_TWICE;
935             t = this.fmod(cache, pi2);
936             if (this.width(t) >= pi2.lo) {
937                 return new MatInterval(-1, 1);
938             }
939 
940             // when t.lo > pi it's the same as
941             // -cos(t - pi)
942             if (t.lo >= this.piHigh) {
943                 cosv = this.cos(this.sub(t, this.PI));
944                 return this.negative(cosv);
945             }
946 
947             lo = t.lo;
948             hi = t.hi;
949             rlo = this.cosLo(hi);
950             rhi = this.cosHi(lo);
951             // it's ensured that t.lo < pi and that t.lo >= 0
952             if (hi <= this.piLow) {
953                 // when t.hi < pi
954                 // [cos(t.lo), cos(t.hi)]
955                 return new MatInterval(rlo, rhi);
956             }
957             if (hi <= pi2.lo) {
958                 // when t.hi < 2pi
959                 // [-1, max(cos(t.lo), cos(t.hi))]
960                 return new MatInterval(-1, Math.max(rlo, rhi));
961             }
962             // t.lo < pi and t.hi > 2pi
963             return new MatInterval(-1, 1);
964         },
965 
966         sin: function(x) {
967             if (this.isEmpty(x) || this.onlyInfinity(x)) {
968                 return this.EMPTY.clone();
969             }
970             return this.cos(this.sub(x, this.PI_HALF));
971         },
972 
973         tan: function(x) {
974             var cache, t, pi;
975             if (this.isEmpty(x) || this.onlyInfinity(x)) {
976                 return this.EMPTY.clone();
977             }
978 
979             // create a clone of `x` because the clone is going to be modified
980             cache = new MatInterval().set(x.lo, x.hi);
981             this._handleNegative(cache);
982 
983             pi = this.PI;
984             t = this.fmod(cache, pi);
985             if (t.lo >= this.piHalfLow) {
986                 t = this.sub(t, pi);
987             }
988             if (t.lo <= -this.piHalfLow || t.hi >= this.piHalfLow) {
989                 return this.WHOLE.clone();
990             }
991             return new MatInterval(this.tanLo(t.lo), this.tanHi(t.hi));
992         },
993 
994         asin: function(x) {
995             var lo, hi;
996             if (this.isEmpty(x) || x.hi < -1 || x.lo > 1) {
997                 return this.EMPTY.clone();
998             }
999             lo = x.lo <= -1 ? -this.piHalfHigh : this.asinLo(x.lo);
1000             hi = x.hi >= 1 ? this.piHalfHigh : this.asinHi(x.hi);
1001             return new MatInterval(lo, hi);
1002         },
1003 
1004         acos: function(x) {
1005             var lo, hi;
1006             if (this.isEmpty(x) || x.hi < -1 || x.lo > 1) {
1007                   return this.EMPTY.clone();
1008             }
1009             lo = x.hi >= 1 ? 0 : this.acosLo(x.hi);
1010             hi = x.lo <= -1 ? this.piHigh : this.acosHi(x.lo);
1011             return new MatInterval(lo, hi);
1012         },
1013 
1014         atan: function(x) {
1015             if (this.isEmpty(x)) {
1016                 return this.EMPTY.clone();
1017             }
1018             return new MatInterval(this.atanLo(x.lo), this.atanHi(x.hi));
1019         },
1020 
1021         sinh: function(x) {
1022             if (this.isEmpty(x)) {
1023                 return this.EMPTY.clone();
1024             }
1025             return new MatInterval(this.sinhLo(x.lo), this.sinhHi(x.hi));
1026         },
1027 
1028         cosh: function(x) {
1029             if (this.isEmpty(x)) {
1030               return this.EMPTY.clone();
1031             }
1032             if (x.hi < 0) {
1033                 return new MatInterval(this.coshLo(x.hi), this.coshHi(x.lo));
1034             }
1035             if (x.lo >= 0) {
1036                 return new MatInterval(this.coshLo(x.lo), this.coshHi(x.hi));
1037             }
1038             return new MatInterval(1, this.coshHi(-x.lo > x.hi ? x.lo : x.hi));
1039         },
1040 
1041         tanh: function(x) {
1042             if (this.isEmpty(x)) {
1043                 return this.EMPTY.clone();
1044             }
1045             return new MatInterval(this.tanhLo(x.lo), this.tanhHi(x.hi));
1046         },
1047 
1048         /*
1049          * Relational
1050          */
1051 
1052         equal: function(x, y) {
1053             if (this.isEmpty(x)) {
1054                 return this.isEmpty(y);
1055             }
1056             return !this.isEmpty(y) && x.lo === y.lo && x.hi === y.hi;
1057         },
1058 
1059         // almostEqual: function(x, y): void {
1060         //     x = Array.isArray(x) ? x : x.toArray();
1061         //     y = Array.isArray(y) ? y : y.toArray();
1062         //     assertEps(x[0], y[0])
1063         //     assertEps(x[1], y[1])
1064         // },
1065 
1066         notEqual: function(x, y) {
1067             if (this.isEmpty(x)) {
1068                 return !this.isEmpty(y);
1069             }
1070             return this.isEmpty(y) || x.hi < y.lo || x.lo > y.hi;
1071         },
1072 
1073         lt: function(x, y) {
1074             if (Type.isNumber(x)) {
1075                 x = this.Interval(x);
1076             }
1077             if (Type.isNumber(y)) {
1078                 y = this.Interval(y);
1079             }
1080             if (this.isEmpty(x) || this.isEmpty(y)) {
1081                 return false;
1082             }
1083             return x.hi < y.lo;
1084         },
1085 
1086         gt: function(x, y) {
1087             if (Type.isNumber(x)) {
1088                 x = this.Interval(x);
1089             }
1090             if (Type.isNumber(y)) {
1091                 y = this.Interval(y);
1092             }
1093             if (this.isEmpty(x) || this.isEmpty(y)) {
1094                 return false;
1095             }
1096             return x.lo > y.hi;
1097         },
1098 
1099         leq: function(x, y) {
1100             if (Type.isNumber(x)) {
1101                 x = this.Interval(x);
1102             }
1103             if (Type.isNumber(y)) {
1104                 y = this.Interval(y);
1105             }
1106             if (this.isEmpty(x) || this.isEmpty(y)) {
1107                 return false;
1108             }
1109             return x.hi <= y.lo;
1110         },
1111 
1112         geq: function(x, y) {
1113             if (Type.isNumber(x)) {
1114                 x = this.Interval(x);
1115             }
1116             if (Type.isNumber(y)) {
1117                 y = this.Interval(y);
1118             }
1119             if (this.isEmpty(x) || this.isEmpty(y)) {
1120                 return false;
1121             }
1122             return x.lo >= y.hi;
1123         },
1124 
1125         /*
1126          * Constants
1127          */
1128         piLow: (3373259426.0 + 273688.0 / (1 << 21)) / (1 << 30),
1129         piHigh: (3373259426.0 + 273689.0 / (1 << 21)) / (1 << 30),
1130         piHalfLow: (3373259426.0 + 273688.0 / (1 << 21)) / (1 << 30) * 0.5,
1131         piHalfHigh: (3373259426.0 + 273689.0 / (1 << 21)) / (1 << 30) * 0.5,
1132         piTwiceLow: (3373259426.0 + 273688.0 / (1 << 21)) / (1 << 30) * 2,
1133         piTwiceHigh: (3373259426.0 + 273689.0 / (1 << 21)) / (1 << 30) * 2,
1134 
1135         /*
1136          * Round
1137          * Rounding functions for numbers
1138          */
1139         identity: function(v) {
1140             return v;
1141         },
1142 
1143         _prev: function(v) {
1144             if (v === Infinity) {
1145               return v;
1146             }
1147             return this.nextafter(v, -Infinity);
1148         },
1149 
1150         _next: function(v) {
1151             if (v === -Infinity) {
1152               return v;
1153             }
1154             return this.nextafter(v, Infinity);
1155         },
1156 
1157         prev: function(v) {
1158             return this._prev(v);
1159         },
1160 
1161         next: function(v) {
1162             return this._next(v);
1163         },
1164 
1165         toInteger: function(x) {
1166             return x < 0 ? Math.ceil(x) : Math.floor(x);
1167         },
1168 
1169         addLo: function(x, y) { return this.prev(x + y); },
1170         addHi: function(x, y) { return this.next(x + y); },
1171         subLo: function(x, y) { return this.prev(x - y); },
1172         subHi: function(x, y) { return this.next(x - y); },
1173         mulLo: function(x, y) { return this.prev(x * y); },
1174         mulHi: function(x, y) { return this.next(x * y); },
1175         divLo: function(x, y) { return this.prev(x / y); },
1176         divHi: function(x, y) { return this.next(x / y); },
1177         intLo: function(x) { return this.toInteger(this.prev(x)); },
1178         intHi: function(x) { return this.toInteger(this.next(x)); },
1179         logLo: function(x) { return this.prev(Math.log(x)); },
1180         logHi: function(x) { return this.next(Math.log(x)); },
1181         expLo: function(x) { return this.prev(Math.exp(x)); },
1182         expHi: function(x) { return this.next(Math.exp(x)); },
1183         sinLo: function(x) { return this.prev(Math.sin(x)); },
1184         sinHi: function(x) { return this.next(Math.sin(x)); },
1185         cosLo: function(x) { return this.prev(Math.cos(x)); },
1186         cosHi: function(x) { return this.next(Math.cos(x)); },
1187         tanLo: function(x) { return this.prev(Math.tan(x)); },
1188         tanHi: function(x) { return this.next(Math.tan(x)); },
1189         asinLo: function(x) { return this.prev(Math.asin(x)); },
1190         asinHi: function(x) { return this.next(Math.asin(x)); },
1191         acosLo: function(x) { return this.prev(Math.acos(x)); },
1192         acosHi: function(x) { return this.next(Math.acos(x)); },
1193         atanLo: function(x) { return this.prev(Math.atan(x)); },
1194         atanHi: function(x) { return this.next(Math.atan(x)); },
1195         sinhLo: function(x) { return this.prev(Mat.sinh(x)); },
1196         sinhHi: function(x) { return this.next(Mat.sinh(x)); },
1197         coshLo: function(x) { return this.prev(Mat.cosh(x)); },
1198         coshHi: function(x) { return this.next(Mat.cosh(x)); },
1199         tanhLo: function(x) { return this.prev(Mat.tanh(x)); },
1200         tanhHi: function(x) { return this.next(Mat.tanh(x)); },
1201         sqrtLo: function(x) { return this.prev(Math.sqrt(x)); },
1202         sqrtHi: function(x) { return this.next(Math.sqrt(x)); },
1203 
1204         powLo: function(x, power) {
1205             var y;
1206             if (power % 1 !== 0) {
1207                 // power has decimals
1208                 return this.prev(Math.pow(x, power));
1209             }
1210 
1211             y = (power & 1) === 1 ? x : 1;
1212             power >>= 1;
1213             while (power > 0) {
1214                 x = this.mulLo(x, x);
1215                 if ((power & 1) === 1) {
1216                     y = this.mulLo(x, y);
1217                 }
1218                 power >>= 1;
1219             }
1220             return y;
1221         },
1222 
1223         powHi: function(x, power) {
1224             var y;
1225             if (power % 1 !== 0) {
1226                 // power has decimals
1227                 return this.next(Math.pow(x, power));
1228             }
1229 
1230             y = (power & 1) === 1 ? x : 1;
1231             power >>= 1;
1232             while (power > 0) {
1233                 x = this.mulHi(x, x);
1234                 if ((power & 1) === 1) {
1235                     y = this.mulHi(x, y);
1236                 }
1237                 power >>= 1;
1238             }
1239             return y;
1240         },
1241 
1242         disable: function() {
1243             this.next = this.prev = this.identity;
1244         },
1245 
1246         enable: function() {
1247             this.prev = function(v) {
1248                 return this._prev(v);
1249             };
1250 
1251             this.next = function(v) {
1252                 return this._next(v);
1253             };
1254         },
1255 
1256 
1257         /*
1258          * nextafter
1259          */
1260         SMALLEST_DENORM: Math.pow(2, -1074),
1261         UINT_MAX: (-1)>>>0,
1262 
1263         nextafter: function(x, y) {
1264             var lo, hi;
1265 
1266             if (isNaN(x) || isNaN(y)) {
1267                 return NaN;
1268             }
1269             if (x === y) {
1270                 return x;
1271             }
1272             if (x === 0) {
1273                 if (y < 0) {
1274                     return -this.SMALLEST_DENORM;
1275                 }
1276                 return this.SMALLEST_DENORM;
1277             }
1278             hi = doubleBits.hi(x);
1279             lo = doubleBits.lo(x);
1280             if ((y > x) === (x > 0)) {
1281                 if (lo === this.UINT_MAX) {
1282                     hi += 1;
1283                     lo = 0;
1284                 } else {
1285                   lo += 1;
1286                 }
1287             } else {
1288                 if (lo === 0) {
1289                     lo = this.UINT_MAX;
1290                     hi -= 1;
1291                 } else {
1292                     lo -= 1;
1293                 }
1294             }
1295             return doubleBits.pack(lo, hi);
1296         }
1297 
1298     };
1299 
1300     JXG.Math.IntervalArithmetic.PI       = new MatInterval(Mat.IntervalArithmetic.piLow, Mat.IntervalArithmetic.piHigh);
1301     JXG.Math.IntervalArithmetic.PI_HALF  = new MatInterval(Mat.IntervalArithmetic.piHalfLow, Mat.IntervalArithmetic.piHalfHigh);
1302     JXG.Math.IntervalArithmetic.PI_TWICE = new MatInterval(Mat.IntervalArithmetic.piTwiceLow, Mat.IntervalArithmetic.piTwiceHigh);
1303     JXG.Math.IntervalArithmetic.ZERO     = new MatInterval(0);
1304     JXG.Math.IntervalArithmetic.ONE      = new MatInterval(1);
1305     JXG.Math.IntervalArithmetic.WHOLE    = new MatInterval().setWhole();
1306     JXG.Math.IntervalArithmetic.EMPTY    = new MatInterval().setEmpty();
1307 
1308     return JXG.Math.IntervalArithmetic;
1309 });
1310 
1311 
1312