| // Copyright 2018 The Wuffs Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package interval |
| |
| // This file provides "radial numbers", which partition the infinite set |
| // ℤ∪{NaN} into a finite number of boxes. The source set ℤ∪{NaN} is the set of |
| // integers, ℤ, augmented with a "Not a Number" element to represent the result |
| // of computations such as a division by zero or a shift by a negative integer. |
| // NaN-ness is viral: if x or y is NaN then (x op y) is also NaN, for any |
| // binary operator op. |
| // |
| // Specifically, there are (2*R + 4) boxes, for some non-negative integer |
| // radius R, and all but two of them contain exactly one element. There are |
| // (2*R + 1) single-element boxes for "small" integers: those whose absolute |
| // values are less than or equal to R. There is another single-element box for |
| // NaN. The two remaining boxes hold "large" integers: those that are less than |
| // -R and those that are greater than +R. |
| // |
| // A rough analogy for a radial number is a primitive counting system: one, |
| // two, three, many. |
| // |
| // This file defines two concrete "radial number" types: |
| // - radialInput has a radius R of 15. |
| // - radialOutput has a radius R of 16 << 16 (which equals 1048576). |
| // |
| // If x and y are "small" radialInput values or one of the two "smallest large" |
| // radialInput values, i.e. x and y are in the range [-16 ..= +16], then (x op |
| // y) will always be a "small" radialOutput value, for the common binary |
| // operators: add, subtract, multiply, divide, left-shift, right-shift, and, |
| // or. |
| // |
| // Both of these radialInput and radialOutput types are encoded as an int32: |
| // - math.MinInt32 (which equals -1 << 31) encodes a NaN. |
| // - "small" numbers (within the interval [-R ..= +R]) encode themselves. |
| // - any other negative int32 encodes "less than -R". |
| // - any other positive int32 encodes "greater than +R". |
| // |
| // For example, radialInput(20) and radialInput(30) represent the same box: |
| // integers greater than +15. |
| // |
| // Binary operators take two radialInput values and produce a pair of |
| // radialOutput values: either a [min ..= max] interval, or [NaN ..= NaN]. For |
| // example, adding the "-3" box to the "greater than +15" box would produce the |
| // radialOutPair ["13" ..= "greater than +16 << 16"], or in more conventional |
| // notation, the half-open interval [13 ..= +∞]. |
| // |
| // These radial number types are not exported by package interval, as the |
| // radius values (15 and 16 << 16) are somewhat arbitrary and not so generally |
| // useful. They are only used for brute force testing in interval_test.go. When |
| // tests in that other file use radial numbers (via calling the bruteForce |
| // function), the test cases are constructed so that every non-nil member of an |
| // interval.IntRange maps to a "small" radialInput, and asSmallRadialInput will |
| // panic if that doesn't hold. |
| // |
| // These types exist in order to simplify calculations. The general problem of |
| // calculating bounds for (x * y), given intervals for x and y, becomes |
| // complicated if e.g. x's possible values include both positive and negative |
| // numbers. In comparison, a radial number's box is either a single value, or |
| // if multiple values, those values all have the same sign. Computation on |
| // "small" radial numbers can use Go's built-in operators (+, -, etc) instead |
| // of the clumsier *big.Int API. |
| // |
| // The radial number methods like Add and Mul also have a simpler API than the |
| // corresponding *big.Int methods, since the radial number methods don't |
| // allocate memory or therefore need a destination argument. |
| |
| import ( |
| "math/big" |
| ) |
| |
| const ( |
| radialNaN = -1 << 31 |
| |
| // Note that radialInput.And and radialInput.Or require that (riRadius + 1) |
| // is a power of 2. |
| riRadius = 15 |
| roRadius = 16 << 16 |
| |
| roLargeNeg = -(16<<16 + 1) |
| roLargePos = +(16<<16 + 1) |
| ) |
| |
| type ( |
| radialInput int32 |
| radialOutput int32 |
| radialOutPair [2]radialOutput |
| ) |
| |
| func (x radialInput) canonicalize() radialOutput { |
| o := radialOutput(x) |
| if o != radialNaN { |
| // No-op. |
| } else if o < -riRadius { |
| return -riRadius - 1 |
| } else if o > +riRadius { |
| return +riRadius + 1 |
| } |
| return o |
| } |
| |
| func (x radialOutput) bigInt() *big.Int { |
| if x < -roRadius || +roRadius < x { |
| return nil |
| } |
| return big.NewInt(int64(x)) |
| } |
| |
| func asSmallRadialInput(x *big.Int, defaultIfXIsNil radialInput) radialInput { |
| if x == nil { |
| return defaultIfXIsNil |
| } |
| if !x.IsInt64() { |
| panic("asSmallRadialInput passed too large a value") |
| } |
| i := x.Int64() |
| if i < -riRadius || +riRadius < i { |
| panic("asSmallRadialInput passed too large a value") |
| } |
| return radialInput(i) |
| } |
| |
| func enumerate(x IntRange) (min radialInput, max radialInput) { |
| if x.Empty() { |
| return +1, -1 |
| } |
| return asSmallRadialInput(x[0], -riRadius-1), asSmallRadialInput(x[1], +riRadius+1) |
| } |
| |
| func (x radialInput) Add(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := y.canonicalize() |
| |
| switch { |
| case ox < -riRadius: |
| if oy > +riRadius { |
| return radialOutPair{roLargeNeg, roLargePos} |
| } |
| return radialOutPair{roLargeNeg, ox + oy} |
| case ox > +riRadius: |
| if oy < -riRadius { |
| return radialOutPair{roLargeNeg, roLargePos} |
| } |
| return radialOutPair{ox + oy, roLargePos} |
| default: |
| switch { |
| case oy < -riRadius: |
| return radialOutPair{roLargeNeg, ox + oy} |
| case oy > +riRadius: |
| return radialOutPair{ox + oy, roLargePos} |
| default: |
| return radialOutPair{ox + oy, ox + oy} |
| } |
| } |
| } |
| |
| func (x radialInput) Sub(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := y.canonicalize() |
| |
| switch { |
| case ox < -riRadius: |
| switch { |
| case oy < -riRadius, oy > +riRadius: |
| return radialOutPair{roLargeNeg, roLargePos} |
| default: |
| return radialOutPair{roLargeNeg, ox - oy} |
| } |
| case ox > +riRadius: |
| switch { |
| case oy < -riRadius, oy > +riRadius: |
| return radialOutPair{roLargeNeg, roLargePos} |
| default: |
| return radialOutPair{ox - oy, roLargePos} |
| } |
| default: |
| switch { |
| case oy < -riRadius: |
| return radialOutPair{ox - oy, roLargePos} |
| case oy > +riRadius: |
| return radialOutPair{roLargeNeg, ox - oy} |
| default: |
| return radialOutPair{ox - oy, ox - oy} |
| } |
| } |
| } |
| |
| func (x radialInput) Mul(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := y.canonicalize() |
| |
| switch { |
| case ox < -riRadius: |
| if oy < 0 { |
| return radialOutPair{ox * oy, roLargePos} |
| } else if oy > 0 { |
| return radialOutPair{roLargeNeg, ox * oy} |
| } |
| return radialOutPair{0, 0} |
| case ox > +riRadius: |
| if oy < 0 { |
| return radialOutPair{roLargeNeg, ox * oy} |
| } else if oy > 0 { |
| return radialOutPair{ox * oy, roLargePos} |
| } |
| return radialOutPair{0, 0} |
| default: |
| switch { |
| case oy < -riRadius: |
| if ox < 0 { |
| return radialOutPair{ox * oy, roLargePos} |
| } else if ox > 0 { |
| return radialOutPair{roLargeNeg, ox * oy} |
| } |
| return radialOutPair{0, 0} |
| case oy > +riRadius: |
| if ox < 0 { |
| return radialOutPair{roLargeNeg, ox * oy} |
| } else if ox > 0 { |
| return radialOutPair{ox * oy, roLargePos} |
| } |
| return radialOutPair{0, 0} |
| default: |
| return radialOutPair{ox * oy, ox * oy} |
| } |
| } |
| } |
| |
| func (x radialInput) Lsh(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN || y < 0 { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := uint32(y.canonicalize()) |
| |
| switch { |
| case ox < -riRadius: |
| return radialOutPair{roLargeNeg, ox << oy} |
| case ox > +riRadius: |
| return radialOutPair{ox << oy, roLargePos} |
| default: |
| if oy <= +riRadius { |
| return radialOutPair{ox << oy, ox << oy} |
| } else if ox < 0 { |
| return radialOutPair{roLargeNeg, ox << oy} |
| } else if ox > 0 { |
| return radialOutPair{ox << oy, roLargePos} |
| } |
| return radialOutPair{0, 0} |
| } |
| } |
| |
| func (x radialInput) Quo(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN || y == 0 { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := y.canonicalize() |
| |
| switch { |
| case ox < -riRadius: |
| switch { |
| case oy < -riRadius: |
| return radialOutPair{0, roLargePos} |
| case oy > +riRadius: |
| return radialOutPair{roLargeNeg, 0} |
| default: |
| if oy < 0 { |
| return radialOutPair{ox / oy, roLargePos} |
| } |
| return radialOutPair{roLargeNeg, ox / oy} |
| } |
| case ox > +riRadius: |
| switch { |
| case oy < -riRadius: |
| return radialOutPair{roLargeNeg, 0} |
| case oy > +riRadius: |
| return radialOutPair{0, roLargePos} |
| default: |
| if oy < 0 { |
| return radialOutPair{roLargeNeg, ox / oy} |
| } |
| return radialOutPair{ox / oy, roLargePos} |
| } |
| default: |
| switch { |
| case oy < -riRadius, oy > +riRadius: |
| return radialOutPair{0, 0} |
| default: |
| return radialOutPair{ox / oy, ox / oy} |
| } |
| } |
| } |
| |
| func (x radialInput) Rsh(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN || y < 0 { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := uint32(y.canonicalize()) |
| |
| switch { |
| case ox < -riRadius: |
| if oy > +riRadius { |
| return radialOutPair{roLargeNeg, -1} |
| } |
| return radialOutPair{roLargeNeg, ox >> oy} |
| case ox > +riRadius: |
| if oy > +riRadius { |
| return radialOutPair{0, roLargePos} |
| } |
| return radialOutPair{ox >> oy, roLargePos} |
| default: |
| if oy <= +riRadius { |
| return radialOutPair{ox >> oy, ox >> oy} |
| } else if ox < 0 { |
| return radialOutPair{-1, -1} |
| } |
| return radialOutPair{0, 0} |
| } |
| } |
| |
| func (x radialInput) And(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := y.canonicalize() |
| |
| // r is a power of 2, so that its binary representation contains one "1" |
| // digit, and that digit is not shared with any "small" value <= riRadius. |
| const r = riRadius + 1 |
| |
| if ox < -riRadius { |
| if oy < -riRadius { |
| return radialOutPair{roLargeNeg, -r} |
| } else if oy > +riRadius { |
| return radialOutPair{0, roLargePos} |
| } else if oy < 0 { |
| return radialOutPair{roLargeNeg, -r} |
| } else { |
| return radialOutPair{0, oy} |
| } |
| } else if ox > +riRadius { |
| if oy < -riRadius { |
| return radialOutPair{0, roLargePos} |
| } else if oy > +riRadius { |
| return radialOutPair{0, roLargePos} |
| } else if oy < 0 { |
| return radialOutPair{+r, roLargePos} |
| } else { |
| return radialOutPair{0, oy} |
| } |
| } |
| |
| if oy < -riRadius { |
| if ox < 0 { |
| return radialOutPair{roLargeNeg, -r} |
| } else { |
| return radialOutPair{0, ox} |
| } |
| } else if oy > +riRadius { |
| if ox < 0 { |
| return radialOutPair{+r, roLargePos} |
| } else { |
| return radialOutPair{0, ox} |
| } |
| } |
| |
| return radialOutPair{ox & oy, ox & oy} |
| } |
| |
| func (x radialInput) Or(y radialInput) radialOutPair { |
| if x == radialNaN || y == radialNaN { |
| return radialOutPair{radialNaN, radialNaN} |
| } |
| ox := x.canonicalize() |
| oy := y.canonicalize() |
| |
| // r is a power of 2, so that its binary representation contains one "1" |
| // digit, and that digit is not shared with any "small" value <= riRadius. |
| const r = riRadius + 1 |
| |
| if ox < -riRadius { |
| if oy < -riRadius { |
| return radialOutPair{roLargeNeg, -1} |
| } else if oy > +riRadius { |
| return radialOutPair{roLargeNeg, -1} |
| } else if oy < 0 { |
| return radialOutPair{oy, -1} |
| } else { |
| return radialOutPair{roLargeNeg, oy | -r} |
| } |
| } else if ox > +riRadius { |
| if oy < -riRadius { |
| return radialOutPair{roLargeNeg, -1} |
| } else if oy > +riRadius { |
| return radialOutPair{+r, roLargePos} |
| } else if oy < 0 { |
| return radialOutPair{oy, -1} |
| } else { |
| return radialOutPair{oy | +r, roLargePos} |
| } |
| } |
| |
| if oy < -riRadius { |
| if ox < 0 { |
| return radialOutPair{ox, -1} |
| } else { |
| return radialOutPair{roLargeNeg, ox | -r} |
| } |
| } else if oy > +riRadius { |
| if ox < 0 { |
| return radialOutPair{ox, -1} |
| } else { |
| return radialOutPair{ox | +r, roLargePos} |
| } |
| } |
| |
| return radialOutPair{ox | oy, ox | oy} |
| } |
| |
| var riOperators = map[rune]func(radialInput, radialInput) radialOutPair{ |
| '+': radialInput.Add, |
| '-': radialInput.Sub, |
| '*': radialInput.Mul, |
| '/': radialInput.Quo, |
| '«': radialInput.Lsh, |
| '»': radialInput.Rsh, |
| '&': radialInput.And, |
| '|': radialInput.Or, |
| } |
| |
| func bruteForce(x IntRange, y IntRange, opKey rune) (z IntRange, ok bool) { |
| op := riOperators[opKey] |
| iMin, iMax := enumerate(x) |
| jMin, jMax := enumerate(y) |
| result := radialOutPair{} |
| first := true |
| merge := func(k radialOutPair) { |
| if first { |
| result = k |
| first = false |
| return |
| } |
| if result[0] > k[0] { |
| result[0] = k[0] |
| } |
| if result[1] < k[1] { |
| result[1] = k[1] |
| } |
| } |
| |
| switch opKey { |
| case '∪': |
| if iMinC, iMaxC := iMin.canonicalize(), iMax.canonicalize(); iMinC <= iMaxC { |
| merge(radialOutPair{iMinC, iMaxC}) |
| } |
| if jMinC, jMaxC := jMin.canonicalize(), jMax.canonicalize(); jMinC <= jMaxC { |
| merge(radialOutPair{jMinC, jMaxC}) |
| } |
| if result[0] < -riRadius { |
| result[0] = -roRadius - 1 |
| } |
| if result[1] > +riRadius { |
| result[1] = +roRadius + 1 |
| } |
| |
| case '∩': |
| iMinC, iMaxC := iMin.canonicalize(), iMax.canonicalize() |
| for j := jMin; j <= jMax; j++ { |
| jC := j.canonicalize() |
| if (iMinC <= jC) && (jC <= iMaxC) { |
| merge(radialOutPair{jC, jC}) |
| } |
| } |
| if result[0] < -riRadius { |
| result[0] = -roRadius - 1 |
| } |
| if result[1] > +riRadius { |
| result[1] = +roRadius + 1 |
| } |
| |
| default: |
| for i := iMin; i <= iMax; i++ { |
| for j := jMin; j <= jMax; j++ { |
| k := op(i, j) |
| if k[0] == radialNaN || k[1] == radialNaN { |
| return IntRange{}, false |
| } |
| merge(k) |
| } |
| } |
| } |
| |
| if first { |
| return makeEmptyRange(), true |
| } |
| return IntRange{result[0].bigInt(), result[1].bigInt()}, true |
| } |