Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add float.to-fixed and float.to-precision #7831

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/rs/slint/private_unstable_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ pub mod re_exports {
};
pub use i_slint_core::slice::Slice;
pub use i_slint_core::string::shared_string_from_number;
pub use i_slint_core::string::shared_string_from_number_fixed;
pub use i_slint_core::string::shared_string_from_number_precision;
pub use i_slint_core::timers::{Timer, TimerMode};
pub use i_slint_core::translations::{
set_bundled_languages, translate_from_bundle, translate_from_bundle_with_plural,
Expand Down
4 changes: 4 additions & 0 deletions docs/astro/src/content/docs/reference/primitive-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ The following conversions are possible:
- Arrays generally don't convert between each other. Array literals can be converted if the element types are convertible.
- String can be converted to float by using the `to-float` function. That function returns 0 if the string isn't
a valid number. You can check with `is-float()` if the string contains a valid number
- `float` can be converted to a formatted `string` using `to-fixed` and `to-precision` which can be passed the
number of digits after the decimal point and and the number of significant digits respectively. They behave like their
JavaScript counterparts [`toFixed()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed)
and [`toPrecision()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision).

```slint
export component Example {
Expand Down
12 changes: 10 additions & 2 deletions internal/compiler/expression_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub enum BuiltinFunction {
ATan2,
Log,
Pow,
ToFixed,
ToPrecision,
SetFocusItem,
ClearFocusItem,
ShowPopupWindow,
Expand Down Expand Up @@ -171,6 +173,8 @@ declare_builtin_function_types!(
ATan2: (Type::Float32, Type::Float32) -> Type::Angle,
Log: (Type::Float32, Type::Float32) -> Type::Float32,
Pow: (Type::Float32, Type::Float32) -> Type::Float32,
ToFixed: (Type::Float32, Type::Int32) -> Type::String,
ToPrecision: (Type::Float32, Type::Int32) -> Type::String,
SetFocusItem: (Type::ElementReference) -> Type::Void,
ClearFocusItem: (Type::ElementReference) -> Type::Void,
ShowPopupWindow: (Type::ElementReference) -> Type::Void,
Expand Down Expand Up @@ -289,7 +293,9 @@ impl BuiltinFunction {
| BuiltinFunction::Log
| BuiltinFunction::Pow
| BuiltinFunction::ATan
| BuiltinFunction::ATan2 => true,
| BuiltinFunction::ATan2
| BuiltinFunction::ToFixed
| BuiltinFunction::ToPrecision => true,
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
BuiltinFunction::ShowPopupWindow
| BuiltinFunction::ClosePopupWindow
Expand Down Expand Up @@ -363,7 +369,9 @@ impl BuiltinFunction {
| BuiltinFunction::Log
| BuiltinFunction::Pow
| BuiltinFunction::ATan
| BuiltinFunction::ATan2 => true,
| BuiltinFunction::ATan2
| BuiltinFunction::ToFixed
| BuiltinFunction::ToPrecision => true,
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
BuiltinFunction::ShowPopupWindow
| BuiltinFunction::ClosePopupWindow
Expand Down
10 changes: 10 additions & 0 deletions internal/compiler/generator/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3541,6 +3541,16 @@ fn compile_builtin_function_call(
ctx.generator_state.conditional_includes.cmath.set(true);
format!("std::atan2({}, {}) / {}", a.next().unwrap(), a.next().unwrap(), pi_180)
}
BuiltinFunction::ToFixed => {
format!("[](float n, int d) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_fixed(&out, n, std::max(d, 0)); return out; }}({}, {})",
a.next().unwrap(), a.next().unwrap(),
)
}
BuiltinFunction::ToPrecision => {
format!("[](float n, int p) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_precision(&out, n, std::max(p, 0)); return out; }}({}, {})",
a.next().unwrap(), a.next().unwrap(),
)
}
BuiltinFunction::SetFocusItem => {
if let [llr::Expression::PropertyReference(pr)] = arguments {
let window = access_window_field(ctx);
Expand Down
8 changes: 8 additions & 0 deletions internal/compiler/generator/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3002,6 +3002,14 @@ fn compile_builtin_function_call(
let (a1, a2) = (a.next().unwrap(), a.next().unwrap());
quote!((#a1 as f64).powf(#a2 as f64))
}
BuiltinFunction::ToFixed => {
let (a1, a2) = (a.next().unwrap(), a.next().unwrap());
quote!(sp::shared_string_from_number_fixed(#a1 as f64, (#a2 as i32).max(0) as usize))
}
BuiltinFunction::ToPrecision => {
let (a1, a2) = (a.next().unwrap(), a.next().unwrap());
quote!(sp::shared_string_from_number_precision(#a1 as f64, (#a2 as i32).max(0) as usize))
}
BuiltinFunction::StringToFloat => {
quote!(#(#a)*.as_str().parse::<f64>().unwrap_or_default())
}
Expand Down
2 changes: 2 additions & 0 deletions internal/compiler/llr/optim_passes/inline_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
BuiltinFunction::ATan2 => 10,
BuiltinFunction::Log => 10,
BuiltinFunction::Pow => 10,
BuiltinFunction::ToFixed => ALLOC_COST,
BuiltinFunction::ToPrecision => ALLOC_COST,
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => isize::MAX,
BuiltinFunction::ShowPopupWindow
| BuiltinFunction::ClosePopupWindow
Expand Down
2 changes: 2 additions & 0 deletions internal/compiler/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,8 @@ impl LookupObject for NumberExpression<'_> {
.or_else(|| f2("atan", member_function(BuiltinFunction::ATan)))
.or_else(|| f2("log", member_function(BuiltinFunction::Log)))
.or_else(|| f2("pow", member_function(BuiltinFunction::Pow)))
.or_else(|| f2("to-fixed", member_function(BuiltinFunction::ToFixed)))
.or_else(|| f2("to-precision", member_function(BuiltinFunction::ToPrecision)))
.or_else(|| NumberWithUnitExpression(self.0).for_each_entry(ctx, f))
}
}
Expand Down
194 changes: 194 additions & 0 deletions internal/core/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,48 @@ pub fn shared_string_from_number(n: f64) -> SharedString {
}
}

/// Convert a f64 to a SharedString with a fixed number of digits after the decimal point
pub fn shared_string_from_number_fixed(n: f64, digits: usize) -> SharedString {
crate::format!("{number:.digits$}", number = n, digits = digits)
}

/// Convert a f64 to a SharedString following a similar logic as JavaScript's Number.toPrecision()
pub fn shared_string_from_number_precision(n: f64, precision: usize) -> SharedString {
let exponent: isize = if n.abs() < 1.0 {
let mut fract = n.abs();
let mut exponent = 0;

while fract < 1.0 {
fract *= 10.0;
exponent -= 1;
}

exponent
} else {
let mut int = n.abs();
let mut exponent = 0;

while int > 10.0 {
int /= 10.0;
exponent += 1;
}

exponent
};

if precision == 0 {
shared_string_from_number(n)
} else if exponent < -6 || (exponent >= 0 && exponent as usize >= precision) {
crate::format!(
"{number:.digits$e}",
number = n,
digits = precision.saturating_add_signed(-1)
)
} else {
shared_string_from_number_fixed(n, precision.saturating_add_signed(-(exponent + 1)))
}
}

#[test]
fn simple_test() {
use std::string::ToString;
Expand Down Expand Up @@ -459,6 +501,158 @@ pub(crate) mod ffi {
}
}

#[no_mangle]
pub unsafe extern "C" fn slint_shared_string_from_number_fixed(
out: *mut SharedString,
n: f64,
digits: usize,
) {
let str = shared_string_from_number_fixed(n, digits);
core::ptr::write(out, str);
}

#[test]
fn test_slint_shared_string_from_number_fixed() {
unsafe {
let num = 12345.6789;

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), num, 0);
assert_eq!(s.assume_init(), "12346");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), num, 1);
assert_eq!(s.assume_init(), "12345.7");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), num, 6);
assert_eq!(s.assume_init(), "12345.678900");

let num = -12345.6789;

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), num, 0);
assert_eq!(s.assume_init(), "-12346");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), num, 1);
assert_eq!(s.assume_init(), "-12345.7");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), num, 6);
assert_eq!(s.assume_init(), "-12345.678900");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), 1.23E+20_f64, 2);
assert_eq!(s.assume_init(), "123000000000000000000.00");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), 1.23E-10_f64, 2);
assert_eq!(s.assume_init(), "0.00");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), 2.34, 1);
assert_eq!(s.assume_init(), "2.3");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), 2.35, 1);
assert_eq!(s.assume_init(), "2.4");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_fixed(s.as_mut_ptr(), 2.55, 1);
assert_eq!(s.assume_init(), "2.5");
}
}

#[no_mangle]
pub unsafe extern "C" fn slint_shared_string_from_number_precision(
out: *mut SharedString,
n: f64,
precision: usize,
) {
let str = shared_string_from_number_precision(n, precision);
core::ptr::write(out, str);
}

#[test]
fn test_slint_shared_string_from_number_precision() {
unsafe {
let num = 5.123456;

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 0);
assert_eq!(s.assume_init(), "5.123456");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 5);
assert_eq!(s.assume_init(), "5.1235");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 2);
assert_eq!(s.assume_init(), "5.1");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 1);
assert_eq!(s.assume_init(), "5");

let num = 0.000123;

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 0);
assert_eq!(s.assume_init(), "0.000123");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 5);
assert_eq!(s.assume_init(), "0.00012300");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 2);
assert_eq!(s.assume_init(), "0.00012");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 1);
assert_eq!(s.assume_init(), "0.0001");

let num = 1234.5;

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 1);
assert_eq!(s.assume_init(), "1e3");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 2);
assert_eq!(s.assume_init(), "1.2e3");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 6);
assert_eq!(s.assume_init(), "1234.50");

let num = -1234.5;

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 1);
assert_eq!(s.assume_init(), "-1e3");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 2);
assert_eq!(s.assume_init(), "-1.2e3");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 6);
assert_eq!(s.assume_init(), "-1234.50");

let num = 0.00000012345;

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 1);
assert_eq!(s.assume_init(), "1e-7");

let mut s = core::mem::MaybeUninit::uninit();
slint_shared_string_from_number_precision(s.as_mut_ptr(), num, 10);
assert_eq!(s.assume_init(), "1.234500000e-7");
}
}

/// Append some bytes to an existing shared string
///
/// bytes must be a valid utf8 array of size `len`, without null bytes inside
Expand Down
12 changes: 12 additions & 0 deletions internal/interpreter/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,18 @@ fn call_builtin_function(
let y: f64 = eval_expression(&arguments[1], local_context).try_into().unwrap();
Value::Number(x.powf(y))
}
BuiltinFunction::ToFixed => {
let n: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let digits: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
let digits: usize = digits.max(0) as usize;
Value::String(i_slint_core::string::shared_string_from_number_fixed(n, digits))
}
BuiltinFunction::ToPrecision => {
let n: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let precision: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
let precision: usize = precision.max(0) as usize;
Value::String(i_slint_core::string::shared_string_from_number_precision(n, precision))
}
BuiltinFunction::SetFocusItem => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to SetFocusItem")
Expand Down
59 changes: 59 additions & 0 deletions tests/cases/expr/to-fixed.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

TestCase := Rectangle {
property<string> t1: 12345.6789.to-fixed(0);
property<string> t2: 12345.6789.to-fixed(1.0);
property<string> t3: 12345.6789.to-fixed(6);
property<string> t4: (-12345.6789).to-fixed(0);
property<string> t5: (-12345.6789).to-fixed(1);
property<string> t6: (-12345.6789).to-fixed(6.0);
property<string> t7: 2.34.to-fixed(1);
property<string> t8: 2.35.to-fixed(1.0);
property<string> t9: 2.55.to-fixed(1);
property<string> t10: 12345.6789.to-fixed(-1);
}
/*
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
assert_eq(instance.get_t1(), "12346");
assert_eq(instance.get_t2(), "12345.7");
assert_eq(instance.get_t3(), "12345.678900");
assert_eq(instance.get_t4(), "-12346");
assert_eq(instance.get_t5(), "-12345.7");
assert_eq(instance.get_t6(), "-12345.678900");
assert_eq(instance.get_t7(), "2.3");
assert_eq(instance.get_t8(), "2.4");
assert_eq(instance.get_t9(), "2.5");
assert_eq(instance.get_t10(), "12346");
```

```rust
let instance = TestCase::new().unwrap();
assert_eq!(instance.get_t1(), "12346");
assert_eq!(instance.get_t2(), "12345.7");
assert_eq!(instance.get_t3(), "12345.678900");
assert_eq!(instance.get_t4(), "-12346");
assert_eq!(instance.get_t5(), "-12345.7");
assert_eq!(instance.get_t6(), "-12345.678900");
assert_eq!(instance.get_t7(), "2.3");
assert_eq!(instance.get_t8(), "2.4");
assert_eq!(instance.get_t9(), "2.5");
assert_eq!(instance.get_t10(), "12346");
```

```js
var instance = new slint.TestCase({});
assert.equal(instance.t1, "12346");
assert.equal(instance.t2, "12345.7");
assert.equal(instance.t3, "12345.678900");
assert.equal(instance.t4, "-12346");
assert.equal(instance.t5, "-12345.7");
assert.equal(instance.t6, "-12345.678900");
assert.equal(instance.t7, "2.3");
assert.equal(instance.t8, "2.4");
assert.equal(instance.t9, "2.5");
assert.equal(instance.t10, "12346");
```
*/
Loading
Loading