// Copyright 2026 Siwei Xu, Cangejie porting.
// Copyright 2018 Ulf Adams, original C library.
//
// The contents of this file may be used under the terms of the Apache License,
// Version 2.0.
//
// Alternatively, the contents of this file may be used under the terms of
// the Boost Software License, Version 1.0.
//
// Unless required by applicable law or agreed to in writing, this software
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.
// Decimal string -> Float64 / Float32 parsing using a Ryu-like algorithm.
// Equivalent to s2d.c and s2f.c. Returns a (Status, value) tuple instead of
// using an output parameter. The input is treated as ASCII bytes.
package ryu
// Parse status codes, mirroring ryu_parse.h's enum Status.
public enum Status {
SUCCESS
| INPUT_TOO_SHORT // cjlint-ignore !G.NAM.03 upstream C type name
| INPUT_TOO_LONG // cjlint-ignore !G.NAM.03 upstream C type name
| MALFORMED_INPUT // cjlint-ignore !G.NAM.03 upstream C type name
}
let DOUBLE_EXPONENT_BIAS_PARSE: Int32 = 1023 // cjlint-ignore !G.VAR.02
let FLOAT_EXPONENT_BIAS_PARSE: Int32 = 127 // cjlint-ignore !G.VAR.02
// floor(log2(value)) for value >= 1, via binary search (no compiler builtin
// dependency). Returns 64/32 for value == 0 (matches the C fallback), though
// callers never pass 0.
// cjlint-ignore -start !G.NAM.04 upstream C function name
func floor_log2_64(value: UInt64): UInt32 {
var v = value
var r: UInt32 = 0
if (v == 0) {
return 64
}
if (v >= (1u64 << 32)) {
r = r + 32
v = v >> 32
}
if (v >= (1u64 << 16)) {
r = r + 16
v = v >> 16
}
if (v >= (1u64 << 8)) {
r = r + 8
v = v >> 8
}
if (v >= (1u64 << 4)) {
r = r + 4
v = v >> 4
}
if (v >= (1u64 << 2)) {
r = r + 2
v = v >> 2
}
if (v >= (1u64 << 1)) {
r = r + 1
}
return r
}
func floor_log2_32(value: UInt32): UInt32 {
var v = value
var r: UInt32 = 0
if (v == 0) {
return 32
}
if (v >= (1u32 << 16)) {
r = r + 16
v = v >> 16
}
if (v >= (1u32 << 8)) {
r = r + 8
v = v >> 8
}
if (v >= (1u32 << 4)) {
r = r + 4
v = v >> 4
}
if (v >= (1u32 << 2)) {
r = r + 2
v = v >> 2
}
if (v >= (1u32 << 1)) {
r = r + 1
}
return r
}
func max32(a: Int32, b: Int32): Int32 {
return if (a < b) {
b
} else {
a
}
}
func double_sign_bit(signedM: Bool): UInt64 {
return (if (signedM) {
1u64
} else {
0u64
}) << UInt32(DOUBLE_EXPONENT_BITS + DOUBLE_MANTISSA_BITS)
}
func float_sign_bit(signedM: Bool): UInt32 {
return (if (signedM) {
1u32
} else {
0u32
}) << UInt32(FLOAT_EXPONENT_BITS + FLOAT_MANTISSA_BITS)
}
// Parses buffer[0..len) as a Float64. Equivalent to s2d_n().
// cjlint-ignore -start !G.FUN.01 core algorithm function
public func s2d_n(buffer: String, len: Int64): (Status, Float64) {
if (len == 0) {
return (Status.INPUT_TOO_SHORT, 0.0)
}
var m10digits: Int64 = 0
var e10digits: Int64 = 0 // cjlint-ignore !G.VAR.02
var dotIndex = len
var eIndex = len
var m10: UInt64 = 0
var e10: Int32 = 0
var signedM = false
var signedE = false
var i: Int64 = 0
if (buffer[i] == 45u8) { // '-'
signedM = true
i = i + 1
}
while (i < len) {
let c = buffer[i]
if (c == 46u8) { // '.'
if (dotIndex != len) {
return (Status.MALFORMED_INPUT, 0.0)
}
dotIndex = i
i = i + 1
} else if (c < 48u8 || c > 57u8) {
break
} else {
if (m10digits >= 17) {
return (Status.INPUT_TOO_LONG, 0.0)
}
m10 = 10u64 * m10 + UInt64(c - 48u8)
if (m10 != 0) {
m10digits = m10digits + 1
}
i = i + 1
}
}
if (i < len && (buffer[i] == 101u8 || buffer[i] == 69u8)) { // 'e' or 'E'
eIndex = i
i = i + 1
if (i < len && (buffer[i] == 45u8 || buffer[i] == 43u8)) { // '-' or '+'
signedE = (buffer[i] == 45u8)
i = i + 1
}
while (i < len) {
let c = buffer[i]
if (c < 48u8 || c > 57u8) {
return (Status.MALFORMED_INPUT, 0.0)
}
if (e10digits > 3) {
return (Status.INPUT_TOO_LONG, 0.0)
}
e10 = 10 * e10 + Int32(c - 48u8)
if (e10 != 0) {
e10digits = e10digits + 1
}
i = i + 1
}
}
if (i < len) {
return (Status.MALFORMED_INPUT, 0.0)
}
if (signedE) {
e10 = -e10
}
e10 = e10 - Int32(if (dotIndex < eIndex) {
eIndex - dotIndex - 1
} else {
0
})
if (m10 == 0) {
return (Status.SUCCESS, if (signedM) {
-0.0
} else {
0.0
})
}
if (m10digits + Int64(e10) <= -324) {
return (Status.SUCCESS, bits_to_float64(double_sign_bit(signedM)))
}
if (m10digits + Int64(e10) >= 310) {
return (Status.SUCCESS, bits_to_float64(double_sign_bit(signedM) | (0x7ffu64 << UInt32(DOUBLE_MANTISSA_BITS))))
}
var e2: Int32
var m2: UInt64
var trailingZeros: Bool
if (e10 >= 0) {
e2 = Int32(floor_log2_64(m10)) + e10 + Int32(log2pow5(e10)) - (DOUBLE_MANTISSA_BITS + 1)
let j = e2 - e10 - ceil_log2pow5(e10) + DOUBLE_POW5_BITCOUNT
m2 = mulShift64(m10, DOUBLE_POW5_SPLIT[Int64(e10)], j)
trailingZeros = e2 < e10 || (e2 - e10 < 64 && multipleOfPowerOf2(m10, UInt32(e2 - e10)))
} else {
e2 = Int32(floor_log2_64(m10)) + e10 - ceil_log2pow5(-e10) - (DOUBLE_MANTISSA_BITS + 1)
let j = e2 - e10 + ceil_log2pow5(-e10) - 1 + DOUBLE_POW5_INV_BITCOUNT
m2 = mulShift64(m10, DOUBLE_POW5_INV_SPLIT[Int64(-e10)], j)
trailingZeros = multipleOfPowerOf5(m10, UInt32(-e10))
}
var ieeeE2 = UInt32(max32(0, e2 + DOUBLE_EXPONENT_BIAS_PARSE + Int32(floor_log2_64(m2))))
if (ieeeE2 > 0x7fe) {
return (Status.SUCCESS, bits_to_float64(double_sign_bit(signedM) | (0x7ffu64 << UInt32(DOUBLE_MANTISSA_BITS))))
}
let shift = (if (ieeeE2 == 0) {
1
} else {
Int32(ieeeE2)
}) - e2 - DOUBLE_EXPONENT_BIAS_PARSE - DOUBLE_MANTISSA_BITS
trailingZeros = trailingZeros && ((m2 & ((1u64 << UInt32(shift - 1)) - 1u64)) == 0)
let lastRemovedBit = (m2 >> UInt32(shift - 1)) & 1
let roundUp = (lastRemovedBit != 0) && (!trailingZeros || (((m2 >> UInt32(shift)) & 1) != 0))
var ieeeM2 = (m2 >> UInt32(shift)) + (if (roundUp) {
1u64
} else {
0u64
})
ieeeM2 = ieeeM2 & ((1u64 << UInt32(DOUBLE_MANTISSA_BITS)) - 1)
if (ieeeM2 == 0 && roundUp) {
ieeeE2 = ieeeE2 + 1
}
let signPart = (if (signedM) {
1u64
} else {
0u64
}) << UInt32(DOUBLE_EXPONENT_BITS)
let ieee = ((signPart | UInt64(ieeeE2)) << UInt32(DOUBLE_MANTISSA_BITS)) | ieeeM2
return (Status.SUCCESS, bits_to_float64(ieee))
}
public func s2d(buffer: String): (Status, Float64) {
return s2d_n(buffer, buffer.size)
}
// Parses buffer[0..len) as a Float32. Equivalent to s2f_n().
public func s2f_n(buffer: String, len: Int64): (Status, Float32) {
// cjlint-ignore -end upstream C function name
// cjlint-ignore -end core algorithm function
if (len == 0) {
// cjlint-ignore -start !G.EXP.02 float literal for conversion test
return (Status.INPUT_TOO_SHORT, 0.0f32)
}
var m10digits: Int64 = 0
var e10digits: Int64 = 0 // cjlint-ignore !G.VAR.02
// cjlint-ignore -end float literal for conversion test
var dotIndex = len
var eIndex = len
var m10: UInt32 = 0
var e10: Int32 = 0
var signedM = false
var signedE = false
var i: Int64 = 0
if (buffer[i] == 45u8) { // '-'
signedM = true
i = i + 1
}
while (i < len) {
let c = buffer[i]
if (c == 46u8) { // '.'
if (dotIndex != len) {
// cjlint-ignore -start !G.EXP.02 float literal for conversion test
return (Status.MALFORMED_INPUT, 0.0f32)
}
dotIndex = i
i = i + 1
} else if (c < 48u8 || c > 57u8) {
break
} else {
if (m10digits >= 9) {
return (Status.INPUT_TOO_LONG, 0.0f32)
}
m10 = 10u32 * m10 + UInt32(c - 48u8)
if (m10 != 0) {
m10digits = m10digits + 1
}
i = i + 1
}
}
if (i < len && (buffer[i] == 101u8 || buffer[i] == 69u8)) { // 'e' or 'E'
eIndex = i
i = i + 1
if (i < len && (buffer[i] == 45u8 || buffer[i] == 43u8)) { // '-' or '+'
signedE = (buffer[i] == 45u8)
i = i + 1
}
while (i < len) {
let c = buffer[i]
if (c < 48u8 || c > 57u8) {
return (Status.MALFORMED_INPUT, 0.0f32)
}
if (e10digits > 3) {
return (Status.INPUT_TOO_LONG, 0.0f32)
}
e10 = 10 * e10 + Int32(c - 48u8)
if (e10 != 0) {
e10digits = e10digits + 1
}
i = i + 1
}
}
if (i < len) {
return (Status.MALFORMED_INPUT, 0.0f32)
}
if (signedE) {
e10 = -e10
}
e10 = e10 - Int32(if (dotIndex < eIndex) {
eIndex - dotIndex - 1
} else {
0
})
if (m10 == 0) {
return (Status.SUCCESS, if (signedM) {
-0.0f32
} else {
0.0f32
})
// cjlint-ignore -end float literal for conversion test
}
if (m10digits + Int64(e10) <= -46) {
return (Status.SUCCESS, bits_to_float32(float_sign_bit(signedM)))
}
if (m10digits + Int64(e10) >= 40) {
return (Status.SUCCESS, bits_to_float32(float_sign_bit(signedM) | (0xffu32 << UInt32(FLOAT_MANTISSA_BITS))))
}
var e2: Int32
var m2: UInt32
var trailingZeros: Bool
if (e10 >= 0) {
e2 = Int32(floor_log2_32(m10)) + e10 + Int32(log2pow5(e10)) - (FLOAT_MANTISSA_BITS + 1)
let j = e2 - e10 - ceil_log2pow5(e10) + FLOAT_POW5_BITCOUNT
m2 = mulPow5divPow2(m10, UInt32(e10), j)
trailingZeros = e2 < e10 || (e2 - e10 < 32 && multipleOfPowerOf2_32(m10, UInt32(e2 - e10)))
} else {
e2 = Int32(floor_log2_32(m10)) + e10 - ceil_log2pow5(-e10) - (FLOAT_MANTISSA_BITS + 1)
let j = e2 - e10 + ceil_log2pow5(-e10) - 1 + FLOAT_POW5_INV_BITCOUNT
m2 = mulPow5InvDivPow2(m10, UInt32(-e10), j)
trailingZeros = (e2 < e10 || (e2 - e10 < 32 && multipleOfPowerOf2_32(m10, UInt32(e2 - e10)))) &&
multipleOfPowerOf5_32(m10, UInt32(-e10))
}
var ieeeE2 = UInt32(max32(0, e2 + FLOAT_EXPONENT_BIAS_PARSE + Int32(floor_log2_32(m2))))
if (ieeeE2 > 0xfe) {
return (Status.SUCCESS, bits_to_float32(float_sign_bit(signedM) | (0xffu32 << UInt32(FLOAT_MANTISSA_BITS))))
}
let shift = (if (ieeeE2 == 0) {
1
} else {
Int32(ieeeE2)
}) - e2 - FLOAT_EXPONENT_BIAS_PARSE - FLOAT_MANTISSA_BITS
trailingZeros = trailingZeros && ((m2 & ((1u32 << UInt32(shift - 1)) - 1u32)) == 0)
let lastRemovedBit = (m2 >> UInt32(shift - 1)) & 1
let roundUp = (lastRemovedBit != 0) && (!trailingZeros || (((m2 >> UInt32(shift)) & 1) != 0))
var ieeeM2 = (m2 >> UInt32(shift)) + (if (roundUp) {
1u32
} else {
0u32
})
ieeeM2 = ieeeM2 & ((1u32 << UInt32(FLOAT_MANTISSA_BITS)) - 1)
if (ieeeM2 == 0 && roundUp) {
ieeeE2 = ieeeE2 + 1
}
let signPart = (if (signedM) {
1u32
} else {
0u32
}) << UInt32(FLOAT_EXPONENT_BITS)
let ieee = ((signPart | ieeeE2) << UInt32(FLOAT_MANTISSA_BITS)) | ieeeM2
return (Status.SUCCESS, bits_to_float32(ieee))
}
public func s2f(buffer: String): (Status, Float32) {
return s2f_n(buffer, buffer.size)
}