github.com/richardwilkes/toolbox@v1.121.0/xmath/fixed/f64/fraction.go (about)

     1  // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, version 2.0. If a copy of the MPL was not distributed with
     5  // this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  //
     7  // This Source Code Form is "Incompatible With Secondary Licenses", as
     8  // defined by the Mozilla Public License, version 2.0.
     9  
    10  package f64
    11  
    12  import (
    13  	"encoding/json"
    14  	"strings"
    15  
    16  	"github.com/richardwilkes/toolbox/xmath/fixed"
    17  )
    18  
    19  // Fraction holds a fractional value.
    20  type Fraction[T fixed.Dx] struct {
    21  	Numerator   Int[T]
    22  	Denominator Int[T]
    23  }
    24  
    25  // NewFraction creates a new fractional value from a string.
    26  func NewFraction[T fixed.Dx](s string) Fraction[T] {
    27  	parts := strings.SplitN(s, "/", 2)
    28  	f := Fraction[T]{
    29  		Numerator:   FromStringForced[T](strings.TrimSpace(parts[0])),
    30  		Denominator: From[T, int](1),
    31  	}
    32  	if len(parts) > 1 {
    33  		f.Denominator = FromStringForced[T](strings.TrimSpace(parts[1]))
    34  	}
    35  	return f
    36  }
    37  
    38  // Normalize the fraction, eliminating any division by zero and ensuring a positive denominator.
    39  func (f *Fraction[T]) Normalize() {
    40  	if f.Denominator == 0 {
    41  		f.Numerator = 0
    42  		f.Denominator = From[T, int](1)
    43  	} else if f.Denominator < 0 {
    44  		negOne := From[T, int](-1)
    45  		f.Numerator = f.Numerator.Mul(negOne)
    46  		f.Denominator = f.Denominator.Mul(negOne)
    47  	}
    48  }
    49  
    50  // Value returns the computed value.
    51  func (f Fraction[T]) Value() Int[T] {
    52  	n := f
    53  	n.Normalize()
    54  	return n.Numerator.Div(n.Denominator)
    55  }
    56  
    57  // StringWithSign returns the same as String(), but prefixes the value with a '+' if it is positive.
    58  func (f Fraction[T]) StringWithSign() string {
    59  	n := f
    60  	n.Normalize()
    61  	s := n.Numerator.StringWithSign()
    62  	if n.Denominator == From[T, int](1) {
    63  		return s
    64  	}
    65  	return s + "/" + n.Denominator.String()
    66  }
    67  
    68  func (f Fraction[T]) String() string {
    69  	n := f
    70  	n.Normalize()
    71  	s := n.Numerator.String()
    72  	if n.Denominator == From[T, int](1) {
    73  		return s
    74  	}
    75  	return s + "/" + n.Denominator.String()
    76  }
    77  
    78  // MarshalJSON implements json.Marshaler.
    79  func (f Fraction[T]) MarshalJSON() ([]byte, error) {
    80  	return json.Marshal(f.String())
    81  }
    82  
    83  // UnmarshalJSON implements json.Unmarshaler.
    84  func (f *Fraction[T]) UnmarshalJSON(in []byte) error {
    85  	var s string
    86  	if err := json.Unmarshal(in, &s); err != nil {
    87  		return err
    88  	}
    89  	*f = NewFraction[T](s)
    90  	f.Normalize()
    91  	return nil
    92  }