a question about big number
Currently rhai does not support big number calculation, for example:
print(9223372036854775801 + 42 == 9223372036854775843)
# got EXCEPTION: Addition overflow: 9223372036854775801 + 42
I'm trying to integrate big number calculating via num_bigint.
Is it possible to write this code via register_fn and operator overloading?
print(big_number(9223372036854775801) + big_number(42) == big_number(9223372036854775843))
From the source code, it looks like rhai is parsing numbers to i64 by default, and I'm worried that this is not possible and would like to know what the recommended practice is?
Actually I have been toying with the idea of adding bigint to Rhai for a while, but just that nobody has actually called out for it.
What you have is essentially a "custom type" which you can register operators and methods on.
For example, you can do this to register the + operator for bigint:
// If you think it is OK to copy BitInt's
engine.register_fn("+", |x: BigInt, y: BigInt| x + y);
// If you'd rather not copy BitInt's
engine.register_fn("+", |x: &mut BitInt, y: BitInt| x + &y);
For Rhai, a number literal automatically gets parsed into i64. It would need a syntax addition to create a bigint syntax.
For constructors, you can probably parse it from a string, like the treatment for decimal:
// Assume to_bigint is defined via register_fn
let x = "9223372036854775801".to_bigint();
BTW, why is it necessary to work with such big integers? Since you can already go up to i128 for 128 bits... which is probably much more than the total number of atoms in the universe...
For such large numbers, shouldn't it be reasonable to expect losing a bit of precision to be acceptable and use floats instead?
For constructors, you can probably parse it from a string, like the treatment for decimal:
looks good to me, I will try it, thanks.
BTW, why is it necessary to work with such big integers?
I'm using rhai to define customize rule which processes blockchain data, big integers are widely used in blockchain.
I'm using rhai to define customize rule which processes blockchain data, big integers are widely used in blockchain.
Ah. Understood.
Remember, when you define operators, you may want to interoperate with integers, such that bigint + int and int + bigint also work. That means you'd need to register many versions of the same function. Usually we do that by having one function which does bigint + bigint and then other versions just call into that.
Another thing to consider is whether a BigInt is expensive to clone. If it is just a Vec of u32 (as I see it), then it involves allocation and so it isn't cheap to clone. Therefore, you'd like to use &mut BigInt as the first parameter as much as possible to at least avoid cloning one operand. You can't avoid cloning the second one.
I tried to implement your suggestion and found it very easy to integrate, thanks.
use num_bigint::BigUint;
use num_traits::Num;
use rhai::{Engine, EvalAltResult};
use std::str::FromStr;
fn to_uint(s: &str) -> Result<BigUint, Box<EvalAltResult>> {
if let Some(striped) = s.strip_prefix("0x") {
BigUint::from_str_radix(striped, 16).map_err(|e| e.to_string().into())
} else {
BigUint::from_str(s).map_err(|e| e.to_string().into())
}
}
macro_rules! register_ops {
($engine:ident $(, $op:tt)+ $(,)?) => {
$(
$engine.register_fn(stringify!($op), |a: BigUint, b: BigUint| a $op b);
)+
};
}
pub fn main() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
register_ops!(engine, +, -, *, /, %, >=, <=, ==, !=, >, <);
engine.register_result_fn("to_int", to_uint);
let script = r#"
let x = "9223372036854775801".to_int();
let y = "42".to_int();
let z = "9223372036854775843".to_int();
print(x + y == z);
let a = "0xff".to_int();
let b = "0x1".to_int();
let c = "0x100".to_int();
print(a + b == c);
"#;
engine.run(script)?;
Ok(())
}
You might actually want to do it as a "plugin module", so you can contribute it back to Rhai as an external package!
:-)
Beware, you're copying BigInt's at every call. I'd suspect that since they are Vec's, they would be expensive to clone as they'd require allocations.
Therefore, I'd strongly suggest making the first parameter &mut BigInt.
Usually, the operator is defined on references, such as &BigInt + &BigInt, so you can just do &*x + &y which would probably avoid at least one allocation for each call.
You'd also want to define to_string, to_debug to be able to print your numbers... and also register versions that interop with INT because you'll want to add one to a BigInt eventually...
Also don't forget the compound assignments, e.g. +=, -= etc. Usually, they are very efficient with &mut BigInt as first parameter. If you omit them, Rhai compiles them as x = x + y which means copying x, just to set it again.
Just checking up on how this is going...
Closing this for now. Feel free to reopen later.
Is there any way to do this without parsing the integer literals from strings? I'm not dealing with bignums, but I do need to deal with u64, and it's kind of annoying having u8 -> u32 and i8 -> i64 literals, but then have to stringify u64 values.
Well, Rhai by default uses i64 as the integer data type and has extensive built-in support for it. Therefore, it makes sense to use it as much as possible.
Usually, you don't really need u64 numbers as you can always represent it as an equivalent i64 integer which makes things much simpler. For example, many systems use u64 as unique ID values... which you don't really do extensive math on, but only use to represent a generic 64-bit number. In such cases, it makes sense to keep that ID value as the standard Rhai i64, and provide a parse_id function to construct it.
Also, Rhai also doesn't have any support for converting u8 to i64 etc. All of those are treated as opaque custom types, which are expensive to use.
But of course there is no easy way for you to use an unsigned representation of u64 to specify an i64 to Rhai... that's a limitation you'd need to live with. The standard way is a parsing function.