termdash/private/numbers/numbers.go

223 lines
4.6 KiB
Go

// Copyright 2019 Google Inc.
//
// 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
//
// http://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 numbers implements various numerical functions.
package numbers
import (
"image"
"math"
)
// RoundToNonZeroPlaces rounds the float up, so that it has at least the provided
// number of non-zero decimal places.
// Returns the rounded float and the number of leading decimal places that
// are zero. Returns the original float when places is zero. Negative places
// are treated as positive, so that -2 == 2.
func RoundToNonZeroPlaces(f float64, places int) (float64, int) {
if f == 0 {
return 0, 0
}
decOnly := zeroBeforeDecimal(f)
if decOnly == 0 {
return f, 0
}
nzMult := multToNonZero(decOnly)
if places == 0 {
return f, multToPlaces(nzMult)
}
plMult := placesToMult(places)
m := float64(nzMult * plMult)
return math.Ceil(f*m) / m, multToPlaces(nzMult)
}
// multToNonZero returns multiplier for the float, so that the first decimal
// place is non-zero. The float must not be zero.
func multToNonZero(f float64) int {
v := f
if v < 0 {
v *= -1
}
mult := 1
for v < 0.1 {
v *= 10
mult *= 10
}
return mult
}
// placesToMult translates the number of decimal places to a multiple of 10.
func placesToMult(places int) int {
if places < 0 {
places *= -1
}
mult := 1
for i := 0; i < places; i++ {
mult *= 10
}
return mult
}
// multToPlaces translates the multiple of 10 to a number of decimal places.
func multToPlaces(mult int) int {
places := 0
for mult > 1 {
mult /= 10
places++
}
return places
}
// zeroBeforeDecimal modifies the float so that it only has zero value before
// the decimal point.
func zeroBeforeDecimal(f float64) float64 {
var sign float64 = 1
if f < 0 {
f *= -1
sign = -1
}
floor := math.Floor(f)
return (f - floor) * sign
}
// MinMax returns the smallest and the largest value among the provided values.
// Returns (0, 0) if there are no values.
// Ignores NaN values. Allowing NaN values could lead to a corner case where all
// values can be NaN, in this case the function will return NaN as min and max.
func MinMax(values []float64) (min, max float64) {
if len(values) == 0 {
return 0, 0
}
min = math.MaxFloat64
max = -1 * math.MaxFloat64
allNaN := true
for _, v := range values {
if math.IsNaN(v) {
continue
}
allNaN = false
if v < min {
min = v
}
if v > max {
max = v
}
}
if allNaN {
return math.NaN(), math.NaN()
}
return min, max
}
// MinMaxInts returns the smallest and the largest int value among the provided
// values. Returns (0, 0) if there are no values.
func MinMaxInts(values []int) (min, max int) {
if len(values) == 0 {
return 0, 0
}
min = math.MaxInt32
max = -1 * math.MaxInt32
for _, v := range values {
if v < min {
min = v
}
if v > max {
max = v
}
}
return min, max
}
// DegreesToRadians converts degrees to the equivalent in radians.
func DegreesToRadians(degrees int) float64 {
if degrees > 360 {
degrees %= 360
}
return (float64(degrees) / 180) * math.Pi
}
// RadiansToDegrees converts radians to the equivalent in degrees.
func RadiansToDegrees(radians float64) int {
d := int(math.Round(radians * 180 / math.Pi))
if d < 0 {
d += 360
}
return d
}
// Abs returns the absolute value of x.
func Abs(x int) int {
if x < 0 {
return -x
}
return x
}
// findGCF finds the greatest common factor of two integers.
func findGCF(a, b int) int {
if a == 0 || b == 0 {
return 0
}
a = Abs(a)
b = Abs(b)
// https://en.wikipedia.org/wiki/Euclidean_algorithm
for {
rem := a % b
a = b
b = rem
if b == 0 {
break
}
}
return a
}
// SimplifyRatio simplifies the given ratio.
func SimplifyRatio(ratio image.Point) image.Point {
gcf := findGCF(ratio.X, ratio.Y)
if gcf == 0 {
return image.ZP
}
return image.Point{
X: ratio.X / gcf,
Y: ratio.Y / gcf,
}
}
// SplitByRatio splits the provided number by the specified ratio.
func SplitByRatio(n int, ratio image.Point) image.Point {
sr := SimplifyRatio(ratio)
if sr.Eq(image.ZP) {
return image.ZP
}
fn := float64(n)
sum := float64(sr.X + sr.Y)
fact := fn / sum
return image.Point{
int(math.Round(fact * float64(sr.X))),
int(math.Round(fact * float64(sr.Y))),
}
}