github.com/nuvolaris/goja@v0.0.0-20230825100449-967811910c6d/ftoa/ftobasestr.go (about)

     1  package ftoa
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"math/big"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  const (
    12  	digits = "0123456789abcdefghijklmnopqrstuvwxyz"
    13  )
    14  
    15  func FToBaseStr(num float64, radix int) string {
    16  	var negative bool
    17  	if num < 0 {
    18  		num = -num
    19  		negative = true
    20  	}
    21  
    22  	dfloor := math.Floor(num)
    23  	ldfloor := int64(dfloor)
    24  	var intDigits string
    25  	if dfloor == float64(ldfloor) {
    26  		if negative {
    27  			ldfloor = -ldfloor
    28  		}
    29  		intDigits = strconv.FormatInt(ldfloor, radix)
    30  	} else {
    31  		floorBits := math.Float64bits(num)
    32  		exp := int(floorBits>>exp_shiftL) & exp_mask_shifted
    33  		var mantissa int64
    34  		if exp == 0 {
    35  			mantissa = int64((floorBits & frac_maskL) << 1)
    36  		} else {
    37  			mantissa = int64((floorBits & frac_maskL) | exp_msk1L)
    38  		}
    39  
    40  		if negative {
    41  			mantissa = -mantissa
    42  		}
    43  		exp -= 1075
    44  		x := big.NewInt(mantissa)
    45  		if exp > 0 {
    46  			x.Lsh(x, uint(exp))
    47  		} else if exp < 0 {
    48  			x.Rsh(x, uint(-exp))
    49  		}
    50  		intDigits = x.Text(radix)
    51  	}
    52  
    53  	if num == dfloor {
    54  		// No fraction part
    55  		return intDigits
    56  	} else {
    57  		/* We have a fraction. */
    58  		var buffer strings.Builder
    59  		buffer.WriteString(intDigits)
    60  		buffer.WriteByte('.')
    61  		df := num - dfloor
    62  
    63  		dBits := math.Float64bits(num)
    64  		word0 := uint32(dBits >> 32)
    65  		word1 := uint32(dBits)
    66  
    67  		dblBits := make([]byte, 0, 8)
    68  		e, _, dblBits := d2b(df, dblBits)
    69  		//            JS_ASSERT(e < 0);
    70  		/* At this point df = b * 2^e.  e must be less than zero because 0 < df < 1. */
    71  
    72  		s2 := -int((word0 >> exp_shift1) & (exp_mask >> exp_shift1))
    73  		if s2 == 0 {
    74  			s2 = -1
    75  		}
    76  		s2 += bias + p
    77  		/* 1/2^s2 = (nextDouble(d) - d)/2 */
    78  		//            JS_ASSERT(-s2 < e);
    79  		if -s2 >= e {
    80  			panic(fmt.Errorf("-s2 >= e: %d, %d", -s2, e))
    81  		}
    82  		mlo := big.NewInt(1)
    83  		mhi := mlo
    84  		if (word1 == 0) && ((word0 & bndry_mask) == 0) && ((word0 & (exp_mask & (exp_mask << 1))) != 0) {
    85  			/* The special case.  Here we want to be within a quarter of the last input
    86  			   significant digit instead of one half of it when the output string's value is less than d.  */
    87  			s2 += log2P
    88  			mhi = big.NewInt(1 << log2P)
    89  		}
    90  
    91  		b := new(big.Int).SetBytes(dblBits)
    92  		b.Lsh(b, uint(e+s2))
    93  		s := big.NewInt(1)
    94  		s.Lsh(s, uint(s2))
    95  		/* At this point we have the following:
    96  		 *   s = 2^s2;
    97  		 *   1 > df = b/2^s2 > 0;
    98  		 *   (d - prevDouble(d))/2 = mlo/2^s2;
    99  		 *   (nextDouble(d) - d)/2 = mhi/2^s2. */
   100  		bigBase := big.NewInt(int64(radix))
   101  
   102  		done := false
   103  		m := &big.Int{}
   104  		delta := &big.Int{}
   105  		for !done {
   106  			b.Mul(b, bigBase)
   107  			b.DivMod(b, s, m)
   108  			digit := byte(b.Int64())
   109  			b, m = m, b
   110  			mlo.Mul(mlo, bigBase)
   111  			if mlo != mhi {
   112  				mhi.Mul(mhi, bigBase)
   113  			}
   114  
   115  			/* Do we yet have the shortest string that will round to d? */
   116  			j := b.Cmp(mlo)
   117  			/* j is b/2^s2 compared with mlo/2^s2. */
   118  
   119  			delta.Sub(s, mhi)
   120  			var j1 int
   121  			if delta.Sign() <= 0 {
   122  				j1 = 1
   123  			} else {
   124  				j1 = b.Cmp(delta)
   125  			}
   126  			/* j1 is b/2^s2 compared with 1 - mhi/2^s2. */
   127  			if j1 == 0 && (word1&1) == 0 {
   128  				if j > 0 {
   129  					digit++
   130  				}
   131  				done = true
   132  			} else if j < 0 || (j == 0 && ((word1 & 1) == 0)) {
   133  				if j1 > 0 {
   134  					/* Either dig or dig+1 would work here as the least significant digit.
   135  					Use whichever would produce an output value closer to d. */
   136  					b.Lsh(b, 1)
   137  					j1 = b.Cmp(s)
   138  					if j1 > 0 { /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output such as 3.5 in base 3.  */
   139  						digit++
   140  					}
   141  				}
   142  				done = true
   143  			} else if j1 > 0 {
   144  				digit++
   145  				done = true
   146  			}
   147  			//                JS_ASSERT(digit < (uint32)base);
   148  			buffer.WriteByte(digits[digit])
   149  		}
   150  
   151  		return buffer.String()
   152  	}
   153  }