brep_kernel/solvers/sketch_solver/
jsnum.rs1use super::*;
2
3pub(super) fn js_round(x: f64) -> f64 {
9 if !x.is_finite() {
10 return x;
11 }
12 let floor = x.floor();
13 let diff = x - floor;
14 if diff >= 0.5 {
15 floor + 1.0
16 } else {
17 floor
18 }
19}
20
21pub(super) fn round_to_decimals(number: f64, decimals: i32) -> f64 {
23 let k = 10f64.powi(decimals);
24 js_round(number * k) / k
25}
26
27pub(super) fn js_sign(x: f64) -> f64 {
29 if x.is_nan() || x == 0.0 {
30 x
31 } else if x > 0.0 {
32 1.0
33 } else {
34 -1.0
35 }
36}
37
38pub(super) fn js_parse_float(value: Option<&Value>) -> f64 {
41 match value {
42 Some(Value::Number(number)) => number.as_f64().unwrap_or(f64::NAN),
43 Some(Value::String(text)) => parse_float_prefix(text),
44 _ => f64::NAN,
45 }
46}
47
48pub(super) fn parse_float_prefix(text: &str) -> f64 {
49 let trimmed = text.trim_start();
50 let bytes = trimmed.as_bytes();
51 let mut end = 0usize;
52 let mut seen_digit = false;
53 let mut seen_dot = false;
54 let mut seen_exp = false;
55 let mut index = 0usize;
56 if index < bytes.len() && (bytes[index] == b'+' || bytes[index] == b'-') {
57 index += 1;
58 }
59 if trimmed[index..].starts_with("Infinity") {
60 let sign = if bytes.first() == Some(&b'-') {
61 -1.0
62 } else {
63 1.0
64 };
65 return sign * f64::INFINITY;
66 }
67 while index < bytes.len() {
68 let b = bytes[index];
69 if b.is_ascii_digit() {
70 seen_digit = true;
71 end = index + 1;
72 } else if b == b'.' && !seen_dot && !seen_exp {
73 seen_dot = true;
74 } else if (b == b'e' || b == b'E') && seen_digit && !seen_exp {
75 let mut next = index + 1;
76 if next < bytes.len() && (bytes[next] == b'+' || bytes[next] == b'-') {
77 next += 1;
78 }
79 if next < bytes.len() && bytes[next].is_ascii_digit() {
80 seen_exp = true;
81 index = next;
82 end = index + 1;
83 continue;
84 } else {
85 break;
86 }
87 } else {
88 break;
89 }
90 index += 1;
91 }
92 if !seen_digit {
93 return f64::NAN;
94 }
95 trimmed[..end].parse::<f64>().unwrap_or(f64::NAN)
96}
97
98pub(super) fn js_number(value: Option<&Value>) -> f64 {
100 match value {
101 None => f64::NAN,
102 Some(Value::Null) => 0.0,
103 Some(Value::Bool(flag)) => {
104 if *flag {
105 1.0
106 } else {
107 0.0
108 }
109 }
110 Some(Value::Number(number)) => number.as_f64().unwrap_or(f64::NAN),
111 Some(Value::String(text)) => {
112 let trimmed = text.trim();
113 if trimmed.is_empty() {
114 0.0
115 } else if trimmed == "Infinity" || trimmed == "+Infinity" {
116 f64::INFINITY
117 } else if trimmed == "-Infinity" {
118 f64::NEG_INFINITY
119 } else {
120 trimmed.parse::<f64>().unwrap_or(f64::NAN)
121 }
122 }
123 Some(_) => f64::NAN,
124 }
125}
126
127pub(super) fn cv_from_raw(value: Option<&Value>) -> f64 {
131 match value {
132 None | Some(Value::Null) => f64::NAN,
133 other => js_number(other),
134 }
135}
136
137pub(super) fn js_parse_int(value: Option<&Value>) -> Option<i64> {
139 match value {
140 Some(Value::Number(number)) => {
141 let float = number.as_f64()?;
142 if float.is_finite() {
143 Some(float.trunc() as i64)
144 } else {
145 None
146 }
147 }
148 Some(Value::String(text)) => {
149 let trimmed = text.trim_start();
150 let bytes = trimmed.as_bytes();
151 let mut index = 0usize;
152 let mut sign = 1i64;
153 if index < bytes.len() && (bytes[index] == b'+' || bytes[index] == b'-') {
154 if bytes[index] == b'-' {
155 sign = -1;
156 }
157 index += 1;
158 }
159 let start = index;
160 while index < bytes.len() && bytes[index].is_ascii_digit() {
161 index += 1;
162 }
163 if index == start {
164 return None;
165 }
166 trimmed[start..index].parse::<i64>().ok().map(|v| sign * v)
167 }
168 _ => None,
169 }
170}
171
172pub(super) fn truthy(value: Option<&Value>) -> bool {
174 match value {
175 None | Some(Value::Null) => false,
176 Some(Value::Bool(flag)) => *flag,
177 Some(Value::Number(number)) => {
178 let float = number.as_f64().unwrap_or(f64::NAN);
179 float != 0.0 && !float.is_nan()
180 }
181 Some(Value::String(text)) => !text.is_empty(),
182 Some(_) => true,
183 }
184}
185
186pub(super) fn fmt_number(x: f64) -> String {
189 if x.is_nan() {
190 return "NaN".to_string();
191 }
192 if x == 0.0 {
193 return "0".to_string();
194 }
195 format!("{}", x)
196}
197
198pub fn fmt_id(value: &Value) -> String {
201 match value {
202 Value::String(text) => text.clone(),
203 Value::Number(number) => number
204 .as_f64()
205 .map(fmt_number)
206 .unwrap_or_else(|| number.to_string()),
207 Value::Bool(flag) => flag.to_string(),
208 Value::Null => "null".to_string(),
209 other => other.to_string(),
210 }
211}
212
213pub(super) fn json_num(x: f64) -> Value {
215 serde_json::Number::from_f64(x)
216 .map(Value::Number)
217 .unwrap_or(Value::Null)
218}
219
220pub(super) fn normalize_angle(angle: f64) -> f64 {
221 ((angle % 360.0) + 360.0) % 360.0
222}
223
224pub(super) fn shortest_angle_delta(target: f64, current: f64) -> f64 {
225 let delta = normalize_angle(target - current);
226 if delta > 180.0 {
227 delta - 360.0
228 } else {
229 delta
230 }
231}
232
233pub(super) fn relative_delta_ratio(a: f64, b: f64, floor: f64) -> f64 {
234 let denom = a.abs().max(b.abs()).max(floor);
235 (a - b).abs() / denom
236}
237
238pub(super) fn sig_bits(x: f64) -> u64 {
241 if x == 0.0 {
242 0u64
243 } else if x.is_nan() {
244 f64::NAN.to_bits()
245 } else {
246 x.to_bits()
247 }
248}