1use super::{division_by_zero, BinaryOp, Result, SQLError, Value};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FloatWidth {
13 Real,
14 DoublePrecision,
15}
16
17pub(super) fn to_float(value: &Value, width: FloatWidth) -> Result<f64> {
18 match value {
19 Value::Float(value) => match width {
20 FloatWidth::Real => narrow_real(*value).map(f64::from),
21 FloatWidth::DoublePrecision => Ok(*value),
22 },
23 Value::Int(value) => Ok(match width {
24 FloatWidth::Real => f64::from(*value as f32),
25 FloatWidth::DoublePrecision => *value as f64,
26 }),
27 Value::Bool(value) => Ok(f64::from(u8::from(*value))),
28 Value::Str(value) | Value::FixedChar(value) => parse_float(value, width),
29 Value::Decimal(value) => parse_float(&value.to_sql_string(), width),
30 other => Err(SQLError::TypeMismatch(format!(
31 "expected number, got {other:?}"
32 ))),
33 }
34}
35
36fn narrow_real(value: f64) -> Result<f32> {
37 let narrowed = value as f32;
38 if narrowed.is_infinite() && !value.is_infinite() {
39 return Err(range_error("overflow"));
40 }
41 if narrowed == 0.0 && value != 0.0 {
42 return Err(range_error("underflow"));
43 }
44 Ok(narrowed)
45}
46
47fn parse_float(input: &str, width: FloatWidth) -> Result<f64> {
48 let text = input.trim_matches(|c: char| c.is_ascii_whitespace());
49 let value = match width {
50 FloatWidth::Real => text.parse::<f32>().map(f64::from),
51 FloatWidth::DoublePrecision => text.parse::<f64>(),
52 }
53 .map_err(|_| SQLError::Routine {
54 sqlstate: "22P02".into(),
55 message: format!(
56 "invalid input syntax for type {}: \"{input}\"",
57 type_name(width)
58 ),
59 })?;
60 let special = text
61 .trim_start_matches(['+', '-'])
62 .eq_ignore_ascii_case("inf")
63 || text
64 .trim_start_matches(['+', '-'])
65 .eq_ignore_ascii_case("infinity");
66 let mantissa = text.split(['e', 'E']).next().unwrap_or(text);
67 let nonzero = mantissa.bytes().any(|byte| matches!(byte, b'1'..=b'9'));
68 if (value.is_infinite() && !special) || (value == 0.0 && nonzero) {
69 return Err(SQLError::Routine {
70 sqlstate: "22003".into(),
71 message: format!("\"{text}\" is out of range for type {}", type_name(width)),
72 });
73 }
74 Ok(value)
75}
76
77fn type_name(width: FloatWidth) -> &'static str {
78 match width {
79 FloatWidth::Real => "real",
80 FloatWidth::DoublePrecision => "double precision",
81 }
82}
83
84fn range_error(kind: &str) -> SQLError {
85 SQLError::Routine {
86 sqlstate: "22003".into(),
87 message: format!("value out of range: {kind}"),
88 }
89}
90
91pub fn eval_float_arithmetic(
93 op: BinaryOp,
94 left: &Value,
95 right: &Value,
96 width: FloatWidth,
97) -> Result<Value> {
98 if matches!(left, Value::Null) || matches!(right, Value::Null) {
99 return Ok(Value::Null);
100 }
101 let left = to_float(left, width)?;
102 let right = to_float(right, width)?;
103 if matches!(op, BinaryOp::Divide) && right == 0.0 && !left.is_nan() {
104 return Err(division_by_zero());
105 }
106 let result = match width {
107 FloatWidth::Real => {
108 let left = left as f32;
109 let right = right as f32;
110 f64::from(match op {
111 BinaryOp::Add => left + right,
112 BinaryOp::Subtract => left - right,
113 BinaryOp::Multiply => left * right,
114 BinaryOp::Divide => left / right,
115 _ => return Err(non_arithmetic(op)),
116 })
117 }
118 FloatWidth::DoublePrecision => match op {
119 BinaryOp::Add => left + right,
120 BinaryOp::Subtract => left - right,
121 BinaryOp::Multiply => left * right,
122 BinaryOp::Divide => left / right,
123 _ => return Err(non_arithmetic(op)),
124 },
125 };
126 if result.is_infinite() && !left.is_infinite() && !right.is_infinite() {
127 return Err(range_error("overflow"));
128 }
129 if result == 0.0
130 && left != 0.0
131 && match op {
132 BinaryOp::Multiply => right != 0.0,
133 BinaryOp::Divide => !right.is_infinite(),
134 _ => false,
135 }
136 {
137 return Err(range_error("underflow"));
138 }
139 Ok(Value::Float(result))
140}
141
142fn non_arithmetic(op: BinaryOp) -> SQLError {
143 SQLError::Internal(format!(
144 "non-arithmetic operator {op:?} reached floating arithmetic"
145 ))
146}
147
148#[must_use]
150pub fn format_real(value: f32) -> String {
151 if value.is_nan() {
152 return "NaN".into();
153 }
154 if value.is_infinite() {
155 return if value.is_sign_negative() {
156 "-Infinity"
157 } else {
158 "Infinity"
159 }
160 .into();
161 }
162 let scientific = format!("{value:e}");
163 let (mantissa, exponent) = scientific.split_once('e').expect("scientific float output");
164 let exponent: i32 = exponent.parse().expect("scientific exponent");
165 if (-4..6).contains(&exponent) {
166 return value.to_string();
167 }
168 let sign = if exponent >= 0 { '+' } else { '-' };
169 format!("{mantissa}e{sign}{:02}", exponent.abs())
170}