github.com/kintar/etxt@v0.0.9/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  }