github.com/richardwilkes/toolbox@v1.121.0/xmath/fixed/f128/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 f128
    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  	var zero Int[T]
    41  	if f.Denominator == zero {
    42  		f.Numerator = Int[T]{}
    43  		f.Denominator = From[T, int](1)
    44  	} else if f.Denominator.LessThan(zero) {
    45  		negOne := From[T, int](-1)
    46  		f.Numerator = f.Numerator.Mul(negOne)
    47  		f.Denominator = f.Denominator.Mul(negOne)
    48  	}
    49  }
    50  
    51  // Value returns the computed value.
    52  func (f Fraction[T]) Value() Int[T] {
    53  	n := f
    54  	n.Normalize()
    55  	return n.Numerator.Div(n.Denominator)
    56  }
    57  
    58  // StringWithSign returns the same as String(), but prefixes the value with a '+' if it is positive.
    59  func (f Fraction[T]) StringWithSign() string {
    60  	n := f
    61  	n.Normalize()
    62  	s := n.Numerator.StringWithSign()
    63  	if n.Denominator == From[T, int](1) {
    64  		return s
    65  	}
    66  	return s + "/" + n.Denominator.String()
    67  }
    68  
    69  func (f Fraction[T]) String() string {
    70  	n := f
    71  	n.Normalize()
    72  	s := n.Numerator.String()
    73  	if n.Denominator == From[T, int](1) {
    74  		return s
    75  	}
    76  	return s + "/" + n.Denominator.String()
    77  }
    78  
    79  // MarshalJSON implements json.Marshaler.
    80  func (f Fraction[T]) MarshalJSON() ([]byte, error) {
    81  	return json.Marshal(f.String())
    82  }
    83  
    84  // UnmarshalJSON implements json.Unmarshaler.
    85  func (f *Fraction[T]) UnmarshalJSON(in []byte) error {
    86  	var s string
    87  	if err := json.Unmarshal(in, &s); err != nil {
    88  		return err
    89  	}
    90  	*f = NewFraction[T](s)
    91  	f.Normalize()
    92  	return nil
    93  }