1use uqa_core::memory::{Produced, ProductionControl};
10
11use super::{division_by_zero, BinaryOp, Result, SQLError, Value};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum FloatWidth {
15 Real,
16 DoublePrecision,
17}
18
19pub(super) fn to_float_with_control(
20 value: &Value,
21 width: FloatWidth,
22 control: &ProductionControl<'_>,
23) -> Result<f64> {
24 control.check()?;
25 match value {
26 Value::Float(value) => match width {
27 FloatWidth::Real => narrow_real(*value).map(f64::from),
28 FloatWidth::DoublePrecision => Ok(*value),
29 },
30 Value::Int(value) => Ok(match width {
31 FloatWidth::Real => f64::from(*value as f32),
32 FloatWidth::DoublePrecision => *value as f64,
33 }),
34 Value::Bool(value) => Ok(f64::from(u8::from(*value))),
35 Value::Str(value) | Value::FixedChar(value) => parse_float(value, width, control),
36 Value::Decimal(value) => {
37 parse_float(&value.to_sql_string_with_control(control)?, width, control)
38 }
39 other => Err(SQLError::TypeMismatch(format!(
40 "expected number, got {other:?}"
41 ))),
42 }
43}
44
45fn narrow_real(value: f64) -> Result<f32> {
46 let narrowed = value as f32;
47 if narrowed.is_infinite() && !value.is_infinite() {
48 return Err(range_error("overflow"));
49 }
50 if narrowed == 0.0 && value != 0.0 {
51 return Err(range_error("underflow"));
52 }
53 Ok(narrowed)
54}
55
56fn parse_float(input: &str, width: FloatWidth, control: &ProductionControl<'_>) -> Result<f64> {
57 for _ in input.as_bytes().chunks(4096) {
58 control.check()?;
59 }
60 let text = input.trim_matches(|c: char| c.is_ascii_whitespace());
61 let value = match width {
62 FloatWidth::Real => text.parse::<f32>().map(f64::from),
63 FloatWidth::DoublePrecision => text.parse::<f64>(),
64 }
65 .map_err(|_| SQLError::Routine {
66 sqlstate: "22P02".into(),
67 message: format!(
68 "invalid input syntax for type {}: \"{input}\"",
69 type_name(width)
70 ),
71 })?;
72 let special = text
73 .trim_start_matches(['+', '-'])
74 .eq_ignore_ascii_case("inf")
75 || text
76 .trim_start_matches(['+', '-'])
77 .eq_ignore_ascii_case("infinity");
78 let mantissa = text.split(['e', 'E']).next().unwrap_or(text);
79 let nonzero = mantissa.bytes().any(|byte| matches!(byte, b'1'..=b'9'));
80 if (value.is_infinite() && !special) || (value == 0.0 && nonzero) {
81 return Err(SQLError::Routine {
82 sqlstate: "22003".into(),
83 message: format!("\"{text}\" is out of range for type {}", type_name(width)),
84 });
85 }
86 Ok(value)
87}
88
89fn type_name(width: FloatWidth) -> &'static str {
90 match width {
91 FloatWidth::Real => "real",
92 FloatWidth::DoublePrecision => "double precision",
93 }
94}
95
96fn range_error(kind: &str) -> SQLError {
97 SQLError::Routine {
98 sqlstate: "22003".into(),
99 message: format!("value out of range: {kind}"),
100 }
101}
102
103pub(super) fn square_root(value: f64) -> Result<f64> {
104 if value < 0.0 {
105 return Err(SQLError::Routine {
106 sqlstate: "2201F".into(),
107 message: "cannot take square root of a negative number".into(),
108 });
109 }
110 Ok(value.sqrt())
111}
112
113pub(super) fn power(base: f64, exponent: f64) -> Result<f64> {
114 let invalid = if base == 0.0 && exponent < 0.0 {
115 Some("zero raised to a negative power is undefined")
116 } else if base < 0.0 && !exponent.is_nan() && exponent.floor() != exponent {
117 Some("a negative number raised to a non-integer power yields a complex result")
118 } else {
119 None
120 };
121 if let Some(message) = invalid {
122 return Err(SQLError::Routine {
123 sqlstate: "2201F".into(),
124 message: message.into(),
125 });
126 }
127 let value = libm::pow(base, exponent);
128 if base.is_finite() && exponent.is_finite() {
129 if value.is_infinite() {
130 return Err(range_error("overflow"));
131 }
132 if value == 0.0 && base != 0.0 {
133 return Err(range_error("underflow"));
134 }
135 }
136 Ok(value)
137}
138
139pub fn eval_float_arithmetic(
141 op: BinaryOp,
142 left: &Value,
143 right: &Value,
144 width: FloatWidth,
145) -> Result<Value> {
146 eval_float_arithmetic_with_control(op, left, right, width, &ProductionControl::uncontrolled())
147}
148
149pub fn eval_float_arithmetic_with_control(
151 op: BinaryOp,
152 left: &Value,
153 right: &Value,
154 width: FloatWidth,
155 control: &ProductionControl<'_>,
156) -> Result<Value> {
157 control.check()?;
158 if matches!(left, Value::Null) || matches!(right, Value::Null) {
159 return Ok(Value::Null);
160 }
161 let left = to_float_with_control(left, width, control)?;
162 let right = to_float_with_control(right, width, control)?;
163 if matches!(op, BinaryOp::Divide) && right == 0.0 && !left.is_nan() {
164 return Err(division_by_zero());
165 }
166 let result = match width {
167 FloatWidth::Real => {
168 let left = left as f32;
169 let right = right as f32;
170 f64::from(match op {
171 BinaryOp::Add => left + right,
172 BinaryOp::Subtract => left - right,
173 BinaryOp::Multiply => left * right,
174 BinaryOp::Divide => left / right,
175 _ => return Err(non_arithmetic(op)),
176 })
177 }
178 FloatWidth::DoublePrecision => match op {
179 BinaryOp::Add => left + right,
180 BinaryOp::Subtract => left - right,
181 BinaryOp::Multiply => left * right,
182 BinaryOp::Divide => left / right,
183 _ => return Err(non_arithmetic(op)),
184 },
185 };
186 if result.is_infinite() && !left.is_infinite() && !right.is_infinite() {
187 return Err(range_error("overflow"));
188 }
189 if result == 0.0
190 && left != 0.0
191 && match op {
192 BinaryOp::Multiply => right != 0.0,
193 BinaryOp::Divide => !right.is_infinite(),
194 _ => false,
195 }
196 {
197 return Err(range_error("underflow"));
198 }
199 Ok(Value::Float(result))
200}
201
202fn non_arithmetic(op: BinaryOp) -> SQLError {
203 SQLError::Internal(format!(
204 "non-arithmetic operator {op:?} reached floating arithmetic"
205 ))
206}
207
208#[must_use]
210pub fn format_real(value: f32) -> String {
211 format_real_with_control(value, &ProductionControl::uncontrolled())
212 .expect("ordinary real formatting")
213 .into_uncontrolled()
214 .expect("ordinary real text")
215}
216
217pub(super) fn format_real_with_control(
218 value: f32,
219 control: &ProductionControl<'_>,
220) -> Result<Produced<String>> {
221 if value.is_nan() {
222 return Ok(control.copy_text("NaN")?);
223 }
224 if value.is_infinite() {
225 return Ok(control.copy_text(if value.is_sign_negative() {
226 "-Infinity"
227 } else {
228 "Infinity"
229 })?);
230 }
231 let scientific = control.format(format_args!("{value:e}"))?;
232 let (mantissa, exponent) = scientific.split_once('e').expect("scientific float output");
233 let exponent: i32 = exponent.parse().expect("scientific exponent");
234 if (-4..6).contains(&exponent) {
235 return Ok(control.format(format_args!("{value}"))?);
236 }
237 let sign = if exponent >= 0 { '+' } else { '-' };
238 Ok(control.format(format_args!("{mantissa}e{sign}{:02}", exponent.abs()))?)
239}