-
Notifications
You must be signed in to change notification settings - Fork 190
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
Decimal (NOT YET FOR MERGE) #225
Conversation
…l create was the choosen implementation, but this is liable to change. As it stands, the decimal type works, but only when 'no_float' is set, as the Decimal type needs to hijack the float parser since there isn't typed literals in the Rhai language.
Replaced the arithmetic operators defined in arithmetics.rs and logic.rs with efficient hard coded versions in fn_call.rs Added tests for Decimal array usage. Added support for use of decimals in arrays in array_basic.rs
Added 'no_float' as a dependency of 'decimal' as the decimal feature hijacks the float parser, since the Rhai language has no way to distinguish between float literals and decimal literals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great! Some comments in this review.
doc/src/language/json.md
Outdated
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if | ||
the [`no_float`] feature is not used. Most common generators of JSON data distinguish between | ||
JSON numbers are all floating-point while Rhai supports integers (`INT`) floating-point (`FLOAT`) if | ||
the [`no_float`] feature is not used and decimal is the [`decimal`] feature is used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and decimal if the [decimal
] feature is used
doc/src/language/numbers.md
Outdated
@@ -10,6 +10,9 @@ The default system integer type (also aliased to `INT`) is `i64`. It can be turn | |||
Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` | |||
(also aliased to `FLOAT`). | |||
|
|||
Decimal numbers are also supported if explicitly enabled with ['decimal']. | |||
Be aware that Decimal support replaces float support, the two features cannot be enabled at the same time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add: "... meaning that [
decimal
] implies [no_float
]." to be explicitly clear?
doc/src/language/values-and-types.md
Outdated
@@ -32,6 +33,8 @@ If only 32-bit integers are needed, enabling the [`only_i32`] feature will remov | |||
This is useful on some 32-bit targets where using 64-bit integers incur a performance penalty. | |||
|
|||
If no floating-point is needed or supported, use the [`no_float`] feature to remove it. | |||
If decimal support is necessary, use the [`decimal`] feature, but be aware that decimal and float support cannot cco-exist in the same build. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cco-exist
doc/src/start/builds/minimal.md
Outdated
@@ -31,7 +31,7 @@ Opt-Out of Features | |||
------------------ | |||
|
|||
Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default | |||
all code is compiled into the final binary since what a script requires cannot be predicted. | |||
all code except [`decimal`] is compiled into the final binary since what a script requires cannot be predicted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, actually there are a few opt-in features, such as serde
. So this statement is not technically correct.
doc/src/start/features.md
Outdated
@@ -17,6 +17,7 @@ more control over what a script can (or cannot) do. | |||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. | | |||
| `no_optimize` | Disable [script optimization]. | | |||
| `no_float` | Disable floating-point numbers and math. | | |||
| `decimal` | Enable decimal number support (through the 'rust_decimal' create). This will disable floating point support. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create
-> crate
src/any.rs
Outdated
|
||
#[cfg(feature = "decimal")] | ||
{ | ||
boxed = match unsafe_cast_box::<_, rust_decimal::Decimal>(boxed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose Decimal
is Copy
so it can use unsafe_try_cast
to save on one boxing operation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just copy the FLOAT
block:
#[cfg(feature = "decimal")]
if type_id == TypeId::of::<Decimal>() {
return <dyn Any>::downcast_ref::<Decimal>(&value)
.unwrap()
.clone()
.into();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if you have serde
enabled, it may attempt to serialize f32
or f64
into a Dynamic
via Dynamic::from
. Therefore, you'd want to also handle the case where 1) no_float
, 2) decimal
, 3) serde
(which I suppose must have floats...), 4) the type is f32
or f64
. In such a case, you should convert the floating-point number into Decimal
.:
#[cfg(feature = "decimal")]
#[cfg(feature = "serde")]
if type_id == TypeId::of::<f64>() {
return dec!(<dyn Any>::downcast_ref::<f64>(&value).unwrap().clone()).into();
} else if type_id == TypeId::of::<f32>() {
return dec!(<dyn Any>::downcast_ref::<f32>(&value).unwrap().clone()).into();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using unsafe_try_cast
is possible, but this breaks the transfer of ownership pattern at play, where the Result::Err<Box<...>
returned from unsafe_cast_box
is used to move the boxed value back from the unsafe_try_cast
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem being that unsafe_try_cast
returns an Option
meaning it consumes the boxed value. I'm unsure how to resolve that. Maybe by borrowing the contents of the Box to unsafe_try_cast
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, my mistake. You can't use unsafe_try_cast
here. Just downcast_ref
and then clone
as my example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They key points are: 1) Decimal
is Copy
so cloning is cheaper than boxing (supposedly), 2) You should handle the rate conversion cases to/from f32/f64.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
downcast_ref
version is pushed.
I have to look at the serde
case closer later on. I need to understand the flow there. rust_decimal
has serde
support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I think it probably defaults to serialize to/from JSON as f64 or f32. Thus you need to consider some of the plumbing in the serde
feature that serializes to/from Dynamic
.
However, it is not a big deal, as I can clean them up later on.
src/any.rs
Outdated
@@ -692,6 +716,14 @@ impl Dynamic { | |||
}; | |||
} | |||
|
|||
#[cfg(feature = "decimal")] | |||
if type_id == TypeId::of::<Decimal>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, consider unsafe_try_cast
if Decimal
is Copy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsafe_try_cast
here should be fine!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsafe_try_cast
version is pushed.
@@ -1203,6 +1206,27 @@ pub fn run_builtin_binary_op( | |||
} | |||
} | |||
|
|||
#[cfg(feature = "decimal")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if a Decimal
can overflow or underflow. You may want to actually copy the INT
code block instead of the FLOAT
code block if you need checked arithmetic... At least the division should trap divide-by-zero...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have to take a look at how this all fits together a bit closer. Decimal
can overflow, but it has checked versions for add, sub, etc that return an Option.
Rhai also has Checked*
traits that return an Option
, so all good there. I'll see later on today if I can use those traits for the Decimal
type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, look into the add
, sub
etc. functions. Those are checked versions. You can copy that template for your own add_decimal
etc. Essentially if it overflows, convert it into an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, actually I see that Decimal
seems to implement num_traits::CheckedAdd
etc. If so, maybe you can simply use the generic versions of add
, sub
etc. and no need to write your own versions.
All good 😉 this will be ready tomorrow as my fence just blew over in a storm, so that’s first priority 😆
Brian
… On 26 Aug 2020, at 08:45, Stephen Chung ***@***.***> wrote:
@schungx commented on this pull request.
In src/any.rs:
> @@ -578,6 +593,15 @@ impl Dynamic {
Ok(s) => return (*s).into(),
Err(val) => val,
};
+
+ #[cfg(feature = "decimal")]
+ {
+ boxed = match unsafe_cast_box::<_, rust_decimal::Decimal>(boxed) {
They key points are: 1) Decimal is Copy so cloning is cheaper than boxing (supposedly), 2) You should handle the rate conversion cases to/from f32/f64.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
@schungx Ok I'm gonna be busy with adulting for the rest of the day. I'll come back to the open issues tomorrow morning. have a good one 😉 |
Hey hey, after an unexpected break I'm back in business ;) |
Great to have you back! But a bit of a "doh!"... I just merged in a major revision that implements a plugins system with procedural macros. The files under Or you can leave it to me... I can apply your changes to the new code base when you're ready. Sorry! |
Nah don't worry about it, I'll merge in the changes ;) |
@ObsceneGiraffe let me know when this can be merged. |
@schungx well all the tests are passing. I merged the changes in upstream yesterday. But the Serde work remains to be done. I'm starting that now. |
I took a look at splitting the Decimal implemented trait Zero but all the integer types do not for some reason? But anyways, I'm going stash that work and get on with the serde / Decimal integration. |
No prob.
You can, but May not be worth it, as having a dedicated module like what you have is perhaps simpler.
This is why I'd hesitated to pull in |
Yea the increase in groups is not worth the trivial duplication in the
And good to know |
On the serde support. I don't think the Dynamic type should be modified to support converting floats to Decimal, as that's an inherently dangerous operation in terms of loss of precision.
From what I see, I would have to integrate the |
However, I think the serde serialization trait requires you to implement a serializer for |
Yep |
A reminder: Rhai serde support is essentially a new serialization format. That's why it is |
@schungx I'm having some trouble figuring out how to integrate the
My |
I'm not an expert in Deserialization means going from Let me think about this some more... |
Yea that's what I'm trying to avoid. I want to warn if someone is trying to deserialize from floats to Decimal. I explicitly want to only handle |
If there is built-in support for Just do: Union::Decimal(_) => self.deserialize_str(visitor),
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
if self.value.type_id() == TypeId::of::<Decimal>() {
// ... get the Decimal, then convert into a string
} else {
self.value.downcast_ref::<ImmutableString>().map_or_else(|| self.type_error(), |x| visit_borrowed_str(x.as_str()))
}
} That would convert a Or you may want to use |
Decimal is now built into Rhai. Retiring this PR. |
Thanks @schungx, apologies, I disappeared into the wilderness for a while. |
This is a draft implementation of integrating a
Decimal
type that depends on['no_float']
being set.The
['no_float']
dependency is required because theDecimal
type hijacks the float parser as the Rhai language has no support for typed literals. Without a typed literal, the parser has no way to decide whether to store a decimal literal as afloat
or aDecimal
.I use the
rust_decimal
library as the Decimal implementation as it is pure Rust and still being maintained.