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 }