github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/efixed/fixed_point.go (about) 1 // efixed is a utility subpackage containing functions for working with 2 // fixed point [fixed.Int26_6 numbers]. You most likely will never need 3 // to use this package, but if you are rolling your own emask.Rasterizer 4 // or ecache.GlyphCacheHandler maybe you find something useful here. 5 // 6 // [fixed.Int26_6 numbers]: https://github.com/Kintar/etxt/blob/main/docs/fixed-26-6.md 7 package efixed 8 9 import "math" 10 import "strconv" 11 import "golang.org/x/image/math/fixed" 12 13 // Converts a value from its fixed.Int26_6 representation to its float64 14 // representation. Conversion is always exact. 15 func ToFloat64(value fixed.Int26_6) float64 { 16 return float64(value) / 64.0 17 } 18 19 // Converts the given float64 to the nearest fixed.Int26_6. 20 // If there's a tie, returned values will be different, and 21 // the first will always be smaller than the second. 22 // 23 // The function will panic if the given float64 is not closely 24 // representable by any fixed.Int26_6 (including Inf, -Inf and NaN). 25 func FromFloat64(value float64) (fixed.Int26_6, fixed.Int26_6) { 26 // TODO: overflows may still be possible, and faster conversion 27 // methods must exist, but go figure 28 candidateA := fixed.Int26_6(value * 64) 29 diffA := abs64(float64(candidateA)/64.0 - value) 30 if diffA == 0 { 31 return candidateA, candidateA 32 } // fast exact conversion 33 34 // fast path didn't succeed, proceed now to the more complex cases 35 36 // check NaN 37 if math.IsNaN(value) { 38 panic("can't convert NaN to fixed.Int26_6") 39 } 40 41 // check bounds 42 if value > 33554431.984375 { 43 if value <= 33554432 { 44 result := fixed.Int26_6(0x7FFFFFFF) 45 return result, result 46 } 47 given := strconv.FormatFloat(value, 'f', -1, 64) 48 panic("can't convert " + given + " to fixed.Int26_6, the biggest representable value is 33554431.984375") 49 } else if value < -33554432 { 50 if value >= -33554432.015625 { 51 result := -fixed.Int26_6(0x7FFFFFFF) - 1 52 return result, result 53 } 54 given := strconv.FormatFloat(value, 'f', -1, 64) 55 panic("can't convert " + given + " to fixed.Int26_6, the smallest representable value is -33554432.0") 56 } 57 58 // compare current candidate with the next and previous ones 59 candidateB := candidateA + 1 60 candidateC := candidateA - 1 61 diffB := abs64(float64(candidateB)/64.0 - value) 62 diffC := abs64(float64(candidateC)/64.0 - value) 63 64 if diffA < diffB { 65 if diffA == diffC { 66 return candidateC, candidateA 67 } 68 if diffA < diffC { 69 return candidateA, candidateA 70 } 71 return candidateC, candidateC 72 } else if diffB < diffA { 73 if diffB == diffC { 74 panic(value) 75 } // this shouldn't be possible, but just to be safe 76 if diffB < diffC { 77 return candidateB, candidateB 78 } 79 return candidateC, candidateC 80 } else { // diffA == diffB 81 return candidateA, candidateB 82 } 83 } 84 85 // Same as [FromFloat64](), but returning a single value. 86 // In case of ties, the result closest to zero is selected. 87 func FromFloat64RoundToZero(value float64) fixed.Int26_6 { 88 a, b := FromFloat64(value) 89 if a >= 0 { 90 return a 91 } // both values are positive, a is smallest one 92 return b // both values are negative, b is the closest to zero 93 } 94 95 // Same as [FromFloat64](), but returning a single value. 96 // In case of ties, the result furthest away from zero is selected. 97 func FromFloat64RoundAwayZero(value float64) fixed.Int26_6 { 98 a, b := FromFloat64(value) 99 if a >= 0 { 100 return b 101 } // both values are positive, b is the biggest one 102 return a // both values are negative, a is the smallest one 103 } 104 105 // Handy method to convert int values to their exact fixed.Int26_6 106 // representation. [fixed.I]() also does this, but this has bound checks, 107 // in case that's important for you. 108 // 109 // [fixed.I]: https://pkg.go.dev/golang.org/x/image/math/fixed#I 110 func FromInt(value int) fixed.Int26_6 { 111 // bound checks 112 if value > 33554431 { 113 given := strconv.Itoa(value) 114 panic("can't convert " + given + " to fixed.Int26_6, the biggest representable int is 33554431") 115 } else if value < -33554432 { 116 given := strconv.Itoa(value) 117 panic("can't convert " + given + " to fixed.Int26_6, the smallest representable int is -33554432") 118 } 119 120 // actual conversion 121 return fixed.Int26_6(value << 6) 122 } 123 124 // Notice: the following methods can overflow, but it's by such a small 125 // margin with regards to actual overflows that it's not even being 126 // mentioned in the documentation. Usage of this package for etxt 127 // shouldn't get even closer to values that can overflow, and in 128 // fact etxt caches will impose lower limits on fixed.Int26_6 129 // magnitudes on their own already. 130 131 // Like [fixed.Floor](), but returning the fixed.Int26_6 value instead 132 // of an int. 133 // 134 // [fixed.Floor]: https://pkg.go.dev/golang.org/x/image/math/fixed#Int26_6.Floor 135 func Floor(value fixed.Int26_6) fixed.Int26_6 { 136 return (value & ^0x3F) 137 } 138 139 // Like [fixed.Round](), but returns the fixed.Int26_6 instead of an int 140 // and is clearly named. For the int result, see [ToIntHalfUp]() instead. 141 // 142 // [fixed.Round]: https://pkg.go.dev/golang.org/x/image/math/fixed#Int26_6.Round 143 func RoundHalfUp(value fixed.Int26_6) fixed.Int26_6 { 144 return (value + 32) & ^0x3F 145 } 146 147 // Like [RoundHalfUp](), but rounding down. For the int result, see 148 // [ToIntHalfDown]() instead. 149 func RoundHalfDown(value fixed.Int26_6) fixed.Int26_6 { 150 return (value + 31) & ^0x3F 151 } 152 153 // Like [RoundHalfUp](), but rounding away from zero. For the int result, see 154 // [ToIntHalfAwayZero]() instead. 155 func RoundHalfAwayZero(value fixed.Int26_6) fixed.Int26_6 { 156 if value >= 0 { 157 return RoundHalfUp(value) 158 } 159 return RoundHalfDown(value) 160 } 161 162 // Like [RoundHalfUp](), but directly converting to int. 163 func ToIntHalfUp(value fixed.Int26_6) int { return int(value+32) >> 6 } 164 165 // Like [RoundHalfDown](), but directly converting to int. 166 func ToIntHalfDown(value fixed.Int26_6) int { return int(value+31) >> 6 } 167 168 // Like [RoundHalfAwayZero](), but directly converting to int. 169 func ToIntHalfAwayZero(value fixed.Int26_6) int { 170 if value >= 0 { 171 return ToIntHalfUp(value) 172 } 173 return ToIntHalfDown(value) 174 } 175 176 // Quantizes the fractional part of the given value with the given step, 177 // rounding up. 178 func QuantizeFractUp(value fixed.Int26_6, step uint8) fixed.Int26_6 { 179 if step >= 64 { 180 return RoundHalfUp(value) 181 } 182 if step <= 1 { 183 return value 184 } 185 186 fstep := fixed.Int26_6(step) 187 if value >= 0 { // positive values 188 fract := value & 0x3F 189 mod := fract % fstep 190 if mod == 0 { 191 return value 192 } 193 prevFract := fract - mod 194 nextFract := prevFract + fstep 195 if (nextFract - fract) <= (fract - prevFract) { // round up 196 if nextFract >= 64 { 197 return Floor(value) + 64 198 } 199 return value + (nextFract - fract) 200 } else { 201 return value - (fract - prevFract) 202 } 203 } else { // negative values 204 fract := (value & 0x3F) | ^0x3F 205 mod := fract % fstep 206 if mod == 0 { 207 return value 208 } 209 prevFract := fract - mod - fstep 210 nextFract := prevFract + fstep 211 if (nextFract - fract) <= (fract - prevFract) { // round up 212 return value + (nextFract - fract) 213 } else { // round down 214 if prevFract <= -64 { 215 return Floor(value) 216 } 217 return value - (fract - prevFract) 218 } 219 } 220 } 221 222 // Quantizes the fractional part of the given value with the given step, 223 // rounding down. 224 func QuantizeFractDown(value fixed.Int26_6, step uint8) fixed.Int26_6 { 225 if step >= 64 { 226 return RoundHalfDown(value) 227 } 228 if step <= 1 { 229 return value 230 } 231 232 fstep := fixed.Int26_6(step) 233 if value >= 0 { // positive values 234 fract := value & 0x3F 235 mod := fract % fstep 236 if mod == 0 { 237 return value 238 } 239 prevFract := fract - mod 240 nextFract := prevFract + fstep 241 if (fract - prevFract) <= (nextFract - fract) { // round down 242 return value - (fract - prevFract) 243 } else { // round up 244 if nextFract >= 64 { 245 return Floor(value) + 64 246 } 247 return value + (nextFract - fract) 248 } 249 } else { // negative values 250 fract := (value & 0x3F) | ^0x3F 251 mod := fract % fstep 252 if mod == 0 { 253 return value 254 } 255 prevFract := fract - mod - fstep 256 nextFract := prevFract + fstep 257 if (fract - prevFract) <= (nextFract - fract) { // round down 258 if prevFract <= -64 { 259 return Floor(value) 260 } 261 return value - (fract - prevFract) 262 } else { // round up 263 return value + (nextFract - fract) 264 } 265 } 266 } 267 268 // Doesn't care about NaNs and general floating point quirkiness. 269 func abs64(value float64) float64 { 270 if value >= 0 { 271 return value 272 } 273 return -value 274 }