Skip to content

Commit 99b77b5

Browse files
authored
Added approximate number field (#61)
* Added floats as field * added --approx parameter * removed code duplicates * some refactor * added unit tests for floats
1 parent 7f641f4 commit 99b77b5

File tree

4 files changed

+347
-19
lines changed

4 files changed

+347
-19
lines changed

src/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub const FRACTION_VMARGIN: f32 = 1.;
1414
pub const FRACTION_LINE_WIDTH: f32 = 1.;
1515
pub const VALUE_PADDING: f32 = 15.;
1616

17+
pub const FLOAT_STRING_PRECISION: usize = 3;
18+
1719
pub const ICON_PATH: &str = "assets/icon.png";
1820
#[cfg(feature = "fft")]
1921
pub const DFT_PATH: &str = "assets/dft_result.json";

src/environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl<T: MatrixNumber> ToString for Type<T> {
9696
impl<T: MatrixNumber> GuiDisplayable for Type<T> {
9797
fn display_string(&self, locale: &Locale) -> String {
9898
match self {
99-
Type::Scalar(s) => s.to_string(),
99+
Type::Scalar(s) => s.display_string(locale),
100100
Type::Matrix(m) => m.display_string(locale),
101101
}
102102
}

src/float.rs

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
use crate::constants::FLOAT_STRING_PRECISION;
2+
use crate::locale::Locale;
3+
use crate::traits::{GuiDisplayable, LaTeXable};
4+
use eframe::epaint::{Color32, FontId, Shape, TextShape};
5+
use egui::{pos2, Context};
6+
use num_traits::{
7+
CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, Num, One, Signed, ToPrimitive,
8+
Zero,
9+
};
10+
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
11+
use std::str::FromStr;
12+
13+
#[derive(Debug, Clone, Copy)]
14+
pub struct Float64 {
15+
value: f64,
16+
}
17+
18+
impl Num for Float64 {
19+
type FromStrRadixErr = <f64 as Num>::FromStrRadixErr;
20+
21+
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
22+
<f64 as Num>::from_str_radix(str, radix).map(|v| v.into())
23+
}
24+
}
25+
26+
impl PartialEq for Float64 {
27+
fn eq(&self, other: &Self) -> bool {
28+
<f64 as PartialEq>::eq(&self.value, &other.value)
29+
}
30+
}
31+
32+
impl From<Float64> for f64 {
33+
fn from(value: Float64) -> Self {
34+
value.value
35+
}
36+
}
37+
38+
impl From<f64> for Float64 {
39+
fn from(value: f64) -> Self {
40+
Float64 { value }
41+
}
42+
}
43+
44+
impl Zero for Float64 {
45+
fn zero() -> Self {
46+
<f64 as Zero>::zero().into()
47+
}
48+
49+
fn is_zero(&self) -> bool {
50+
<f64 as Zero>::is_zero(&self.value)
51+
}
52+
}
53+
54+
impl Add<Self> for Float64 {
55+
type Output = Self;
56+
57+
fn add(self, rhs: Self) -> Self::Output {
58+
self.value.add(rhs.value).into()
59+
}
60+
}
61+
62+
impl One for Float64 {
63+
fn one() -> Self {
64+
<f64 as One>::one().into()
65+
}
66+
}
67+
68+
impl Mul<Self> for Float64 {
69+
type Output = Self;
70+
71+
fn mul(self, rhs: Self) -> Self::Output {
72+
self.value.mul(rhs.value).into()
73+
}
74+
}
75+
76+
impl Sub<Self> for Float64 {
77+
type Output = Self;
78+
79+
fn sub(self, rhs: Self) -> Self::Output {
80+
self.value.sub(rhs.value).into()
81+
}
82+
}
83+
84+
impl Div<Self> for Float64 {
85+
type Output = Self;
86+
87+
fn div(self, rhs: Self) -> Self::Output {
88+
self.value.div(rhs.value).into()
89+
}
90+
}
91+
92+
impl Rem<Self> for Float64 {
93+
type Output = Self;
94+
95+
fn rem(self, rhs: Self) -> Self::Output {
96+
self.value.rem(rhs.value).into()
97+
}
98+
}
99+
100+
impl CheckedAdd for Float64 {
101+
fn checked_add(&self, v: &Self) -> Option<Self> {
102+
Some(self.value.add(v.value).into())
103+
}
104+
}
105+
106+
impl CheckedSub for Float64 {
107+
fn checked_sub(&self, v: &Self) -> Option<Self> {
108+
Some(self.value.sub(v.value).into())
109+
}
110+
}
111+
112+
impl CheckedMul for Float64 {
113+
fn checked_mul(&self, v: &Self) -> Option<Self> {
114+
Some(self.value.mul(v.value).into())
115+
}
116+
}
117+
118+
impl CheckedDiv for Float64 {
119+
fn checked_div(&self, v: &Self) -> Option<Self> {
120+
if v.is_zero() {
121+
return None;
122+
}
123+
Some(self.value.div(v.value).into())
124+
}
125+
}
126+
127+
impl FromPrimitive for Float64 {
128+
fn from_i64(n: i64) -> Option<Self> {
129+
Some((n as f64).into())
130+
}
131+
132+
fn from_u64(n: u64) -> Option<Self> {
133+
Some((n as f64).into())
134+
}
135+
}
136+
137+
impl ToPrimitive for Float64 {
138+
fn to_i64(&self) -> Option<i64> {
139+
Some(self.value as i64)
140+
}
141+
142+
fn to_u64(&self) -> Option<u64> {
143+
Some(self.value as u64)
144+
}
145+
}
146+
147+
impl Signed for Float64 {
148+
fn abs(&self) -> Self {
149+
self.value.abs().into()
150+
}
151+
152+
fn abs_sub(&self, other: &Self) -> Self {
153+
(self.value - other.value).abs().into()
154+
}
155+
156+
fn signum(&self) -> Self {
157+
self.value.signum().into()
158+
}
159+
160+
fn is_positive(&self) -> bool {
161+
self.value.is_sign_positive()
162+
}
163+
164+
fn is_negative(&self) -> bool {
165+
self.value.is_sign_negative()
166+
}
167+
}
168+
169+
impl Neg for Float64 {
170+
type Output = Self;
171+
172+
fn neg(self) -> Self::Output {
173+
self.value.neg().into()
174+
}
175+
}
176+
177+
impl FromStr for Float64 {
178+
type Err = ();
179+
180+
fn from_str(s: &str) -> Result<Self, Self::Err> {
181+
s.parse::<f64>().map(|v| v.into()).map_err(|_| ())
182+
}
183+
}
184+
185+
impl ToString for Float64 {
186+
fn to_string(&self) -> String {
187+
self.value.to_string()
188+
}
189+
}
190+
191+
impl LaTeXable for Float64 {
192+
fn to_latex(&self) -> String {
193+
self.value.to_latex()
194+
}
195+
}
196+
197+
impl GuiDisplayable for Float64 {
198+
fn display_string(&self, locale: &Locale) -> String {
199+
self.value.display_string(locale)
200+
}
201+
202+
fn to_shape(&self, ctx: &Context, font_id: FontId, color: Color32) -> Shape {
203+
self.value.to_shape(ctx, font_id, color)
204+
}
205+
}
206+
207+
impl LaTeXable for f64 {
208+
fn to_latex(&self) -> String {
209+
trim_trailing_zeros_float_str(&format!("{:.*}", FLOAT_STRING_PRECISION, self))
210+
}
211+
}
212+
213+
/// Trims trailing zeros from a float string.
214+
/// # Arguments
215+
/// * `s` - String to trim zeros from
216+
/// # Returns
217+
/// A string representing the same floating point number, but without trailing zeros
218+
/// # Examples
219+
/// ```rust
220+
/// # use crate::jp2gmd_lib::trim_trailing_zeros_float_str;
221+
/// assert_eq!(trim_trailing_zeros_float_str("10.0"), "10");
222+
/// assert_eq!(trim_trailing_zeros_float_str("123.450"), "123.45");
223+
/// assert_eq!(trim_trailing_zeros_float_str("1.0"), "1");
224+
/// ```
225+
pub fn trim_trailing_zeros_float_str(s: &str) -> String {
226+
let mut s = s.to_string();
227+
if s.contains('.') {
228+
s = s.trim_end_matches('0').to_string();
229+
if s.ends_with('.') {
230+
s.pop();
231+
}
232+
}
233+
s
234+
}
235+
236+
impl GuiDisplayable for f64 {
237+
fn display_string(&self, _locale: &Locale) -> String {
238+
self.to_latex()
239+
}
240+
241+
fn to_shape(&self, ctx: &Context, font_id: FontId, color: Color32) -> Shape {
242+
let text_shape = TextShape::new(
243+
pos2(0., 0.),
244+
ctx.fonts(|f| f.layout_no_wrap(self.to_latex(), font_id, color)),
245+
);
246+
Shape::Text(text_shape)
247+
}
248+
}
249+
250+
#[cfg(test)]
251+
mod test {
252+
use super::*;
253+
use crate::locale::Language;
254+
255+
#[test]
256+
fn test_zeros() {
257+
assert_eq!(Float64::from_str("0").unwrap(), 0.0.into());
258+
assert_eq!(Float64::from_str("0.0").unwrap(), 0.0.into());
259+
assert_eq!(Float64::from_str("0.00").unwrap(), 0.0.into());
260+
assert_eq!(Float64::from_str("0.000").unwrap(), 0.0.into());
261+
}
262+
263+
#[test]
264+
fn test_zeros_to_string() {
265+
assert_eq!(Float64::from_str("0").unwrap().to_string(), "0");
266+
assert_eq!(Float64::from_str("0.0").unwrap().to_string(), "0");
267+
assert_eq!(Float64::from_str("0.00").unwrap().to_string(), "0");
268+
assert_eq!(Float64::from_str("0.000").unwrap().to_string(), "0");
269+
}
270+
271+
#[test]
272+
fn test_trim_trailing_zeros_float_str() {
273+
assert_eq!(trim_trailing_zeros_float_str("10.0"), "10");
274+
assert_eq!(trim_trailing_zeros_float_str("123.450"), "123.45");
275+
assert_eq!(trim_trailing_zeros_float_str("1.0"), "1");
276+
}
277+
278+
#[test]
279+
fn test_float64_from_str() {
280+
assert_eq!(Float64::from_str("10.0").unwrap(), 10.0.into());
281+
assert_eq!(Float64::from_str("123.450").unwrap(), 123.45.into());
282+
assert_eq!(Float64::from_str("1.0").unwrap(), 1.0.into());
283+
}
284+
285+
#[test]
286+
fn test_float64_to_latex() {
287+
assert_eq!(Float64::from_str("10.0").unwrap().to_latex(), "10");
288+
assert_eq!(Float64::from_str("123.450").unwrap().to_latex(), "123.45");
289+
assert_eq!(Float64::from_str("1.0").unwrap().to_latex(), "1");
290+
}
291+
292+
#[test]
293+
fn test_float64_display_string() {
294+
let locale = Locale::new(Language::English);
295+
assert_eq!(
296+
Float64::from_str("10.0").unwrap().display_string(&locale),
297+
"10"
298+
);
299+
assert_eq!(
300+
Float64::from_str("123.450")
301+
.unwrap()
302+
.display_string(&locale),
303+
"123.45"
304+
);
305+
assert_eq!(
306+
Float64::from_str("1.0").unwrap().display_string(&locale),
307+
"1"
308+
);
309+
}
310+
}

0 commit comments

Comments
 (0)