Replace the hand-written parser in calc.c with a tree-sitter grammar (subprojects/rizin-math-parser) and a typed evaluator. The old parser could only ever produce a ut64 and folded anything it failed to read to 0, which left callers unable to tell a failed expression from one that evaluated to zero. Expressions now evaluate to an RzNumValue, a tagged union over ut64, double, RzBitVector, arbitrary-precision integer and arbitrary-precision decimal, carrying an RzNumError rather than signalling failure as 0. Literals keep the width they were written with (5u8, 0xffu128, any width from 1 to 65536), results that outgrow 64 bits promote to a big number on their own, and a parse error, division by zero or unresolved identifier reaches the caller. rz_num_math() is deprecated. rz_num_math_ut64() keeps its exact behaviour for callers that want a ut64, and rz_num_math_value() exposes the typed result. rz_core_math() adds the RzCore-backed form used by the % command, with rz_core_math_ut64() deprecated alongside it. rz-ax routes through the typed API, so it prints values at full precision, reports errors on stderr and exits non-zero. rz_il_lift_num() converts an expression to an RzILOpPure, so a numeric argument can be lifted instead of pre-evaluated. Legacy input still works: trailing base suffixes (101b, 35o, 212t), the trailing-'h' hex form and the k/m/g scale suffixes are all accepted and warn once, pointing at the 0b/0o/0t prefixes. doc/math.md documents the language and doc/math-il-lift.md the lift; the grammar, the evaluator, rz-ax and the % command are covered by unit and db tests.
30 KiB
RzNum Expression Language
The RzNum evaluator parses and computes mathematical expressions
used throughout Rizin. Every place that accepts a numeric argument
(%, rz-ax, the ?v command, flag offsets, seek targets, ...)
funnels through the same grammar and the same evaluator.
This document describes the surface language as users see it.
For consumers of the C API see the doxygen comments on
rz_num_math_value(), rz_num_math_value_ex(), rz_core_math(),
and the helpers rz_num_value_to_ut64() / rz_num_value_print().
Numeric literals
| Form | Meaning | Example |
|---|---|---|
123 |
decimal | 42 |
0x1a / 0X1A |
hexadecimal | 0xdeadbeef |
0b1010 / 0B1010 |
binary | 0b101010 |
0o17 / 0O17 |
octal | 0o755 |
0t102 / 0T102 |
ternary (base 3) | 0t1102 |
3.14 |
decimal float | 1.5 |
1.5e3 |
decimal float with exponent | 1.0e-3 |
0x1p10 |
hex float (binary exponent) | 0x1p-4 |
A literal in any base may be followed by an IEC byte unit suffix to multiply by the matching power of two:
| Suffix | Multiplier |
|---|---|
KiB |
1024 |
KB |
1000 |
MiB / MB |
1024^2 / 1000^2 |
GiB / GB |
1024^3 / 1000^3 |
TiB / TB |
1024^4 / 1000^4 |
PiB / PB |
1024^5 / 1000^5 |
EiB / EB |
1024^6 / 1000^6 |
Examples: 4KiB, 2MiB, 1.5GB. Float values accept units too.
A literal that does not fit in a 64-bit unsigned integer is
automatically promoted to an arbitrary-precision big number (kind
RZ_NUM_KIND_BIG). This works for every supported base, not just
hexadecimal:
% 0x10000000000000000 -> bignum 0x10000000000000000
% 99999999999999999999999999 -> bignum 0x52b7d2dcc80cd2e3ffffffff
% 0b100000000000000000000000000000000000000000000000000000000000000000
-> bignum 0x20000000000000000
When arithmetic on a big number produces a result that fits back
inside ut64, the value is demoted automatically.
A literal followed by a u plus a bit-width - u8, u16, u32,
u64, u128 - is a fixed-width bit-vector (kind
RZ_NUM_KIND_BITVECTOR). Bit-vectors do modular arithmetic at their
width, so they overflow by wrapping rather than promoting:
% 5u8 -> width 8, 0x05
% 200u8 + 100u8 -> width 8, 0x2c (300 mod 256)
% 0xffu8 + 1u8 -> width 8, 0x00 (wraps)
% 10u16 * 10u16 -> width 16, 0x0064
A bare letter run with no width (5u, 10ul, 3.0f) is
informational only and does not change the kind. See the
"Bit-vectors" section below for the operations defined on them.
Identifiers
A bare identifier is looked up as a variable through the host's
RzNum callback. In the shell this is the flag table: typing
% entry0 evaluates to the address of entry0. Unknown
identifiers resolve to zero (matching the legacy behaviour).
Identifier characters are everything that is not a control character, whitespace, operator, bracket, quote, comma, semicolon, backslash, dollar sign, or a digit (as the first character). This includes any non-ASCII Unicode letter; identifiers may be written in any script.
Reserved words
These seven words are operator keywords and cannot be assigned to. Each is meaningful only in its operator position:
| Word(s) | Operator position |
|---|---|
mod, log |
binary operator: x mod y, x log y |
sdiv, smod, sar |
signed operator: x sdiv y, x sar y |
le, be |
endianness in a typed read: 0x1000:le32 |
Every other identifier, in any script, is a free variable name.
Special variables
$-prefixed names are a closed set drawn from the canonical list
used by the %v (legacy ?v) command:
$$ current offset $$$ alias for $$
$b block size $B block size in bytes
$F function start at $$ $Ff function end at $$
$Fj function jump at $$ $Fb function basic block at $$
$FB function basic block end $Fi next instruction
$FS function size $fl flag size at $$
$S section start $SS section size
$D debug map start $DB debug map end
$DD debug map size $DS debug map permissions
$c column / cursor $Cn comment by index
$alias alias map $e end of execution
$f fail-jump address $j true-jump address
$Ja analysis jump address
$l last instruction length $M map count
$MM address space size $m memory size
$O current opcode address $o opcode size
$p PID $P parent PID
$r return value register $s stack pointer
$v value $w word size
An unknown $-name is a parse error (since the rule is a closed
list), not an evaluator error.
Variable bindings
x = y and let x = y bind the identifier x to the value of y
and evaluate to that value. The two forms are identical; let is an
optional keyword with no separate scoping. The binding is visible to
the rest of the current expression, so a later part can read it back:
(x = 7) + x -> 14
(a = 2) * (a + 1) -> 6
(c = 1) + (c = 9) + c -> 19 (reassignment updates the binding)
A local binding shadows a host variable (flag / special variable) of the same name. Bound values keep their kind, so a bound big number stays exact. The reserved words (see the Reserved words table above) cannot be assigned.
With the statement separator ; (see below) bindings can be
introduced and reused without the parenthesis trick:
let radius = 5; let area = radius * radius; area -> 25
bla = crc(0, 4); 0:be32 + bla
Persistence across evaluations is opt-in. By default a binding lives
for a single evaluation. If the caller supplies a persistent variable
store (RzNumMathOptions.vars, created with rz_num_value_store_new()),
bindings written by one evaluation remain visible to later ones that
share the store. The % shell command uses such a store, so a
variable bound in one % invocation is visible to the next:
[0x0]> % bla = crc(0, 4)
[0x0]> % 0:be32 + bla <- 'bla' still bound here
Statement sequencing
Several expressions can be chained with ;. Each is evaluated in
order (so bindings made by earlier statements are visible to later
ones) and the value of the whole is the value of the last statement.
A trailing ; is allowed.
1 + 2; 3 + 4 -> 7
x = 10; y = 20; x + y -> 30
a = 3; b = a * a; a + b -> 12
a = 3; b = a * a; a + b; -> 12 (a trailing ; is allowed)
Operators
In order from highest precedence to lowest. All operators are left-associative except exponentiation, which is right-associative.
Unary prefix: +x -x !x ~x ++x --x
Exponent: x ** y (right-associative)
Mul-class: x * y x / y x % y x mod y
x sdiv y x smod y (signed division / remainder)
Add-class: x + y x - y
Shifts: x << y x >> y x <<< y x >>> y x sar y
(<<< and >>> are bitwise rotates on the low 64 bits;
sar is the sign-propagating arithmetic shift right)
Bitwise AND: x & y
Bitwise XOR: x ^ y
Bitwise OR: x | y
Comparison: x < y x <= y x > y x >= y
Equality: x == y x != y
Conditional: c ? a : b (ternary, right-associative)
Logarithm: x log y (computes log_x(y))
Assignment: x = y let x = y (binds x, evaluates to y)
Notes:
- Division and modulo with a zero right-hand side raise
RZ_NUM_ERR_DIV_ZERO(for both the unsigned/%and the signedsdivsmod). sdiv,smodandsarinterpret their operands as two's-complement signed 64-bit values./,%and>>are unsigned. For example-5 sdiv 2is-2while0xfffffffffffffffb / 2is a large positive number;0xffffffffffffff00 sar 4sign-extends to0xfffffffffffffff0whereas>> 4does not.- Rotations
<<<and>>>on aut64or big number rotate a 64-bit projection of the operand (a big number has no width-independent rotate). On a bit-vector they rotate within that value's own width instead - see the Bit-vectors section. **andlogare computed indouble. An integer**with a small exponent is still exact (it promotes to a big number); otherwise the result is afloat. For an exact bignum power use repeated multiplication.~(bitwise NOT) on a big number projects tout64first, since the complement of an arbitrary-precision integer is not defined without a fixed width.- The ternary
c ? a : bis right-associative, soa ? b : c ? d : eparses asa ? b : (c ? d : e). It short-circuits: only the taken branch is evaluated, sob != 0 ? a / b : 0is safe even whenbis zero. The condition's truthiness is per-kind - non-zero ut64/big/bit-vector is true; a float is tested as a float, so0.5 ? a : bisabut0.0 ? a : bisb. The selected branch keeps its own kind, including bit-vectors and big numbers. The ternary is also the one control-flow construct that lifts structurally to RzIL, mapping directly ontoite.
Comparisons and equality return 1 (true) or 0 (false) as a
ut64.
Typed address syntax
A literal address can be followed by a colon and a typed suffix describing the read width, signedness, endianness, and whether the bytes are an integer or a float:
0x1000:le32 read a 32-bit little-endian unsigned word
0x1000:be64 read a 64-bit big-endian unsigned word
0x1000:8 read 8 bits (1 byte), native endianness
0x1000:s32 read a 32-bit SIGNED word (sign-extended)
0x1000:lef32 read a 32-bit little-endian IEEE-754 float
0x1000:f16 read a native-endian half-precision float
0x1000:f64 read a double-precision float
0x1000:128 read a 128-bit value, returns a bit-vector
0x1000:f128 read a 128-bit IEEE-754 quad, raw bits as a bit-vector
The suffix grammar is [le|be]? [s]? width for integers (width
one of 8/16/32/64/128) or [le|be]? f (16|32|64|128) for floats.
An s marks a signed integer read (the value is sign-extended to
the result); an f marks an IEEE-754 float read.
The result kind follows the requested width:
- widths up to 64 bits return a
ut64(integer) ordouble(float); the float decoder handles f16 / f32 / f64 directly; - 128-bit reads (
:128and:f128) return a width-128 bit-vector. Two underlying 64-bit reads are stitched into the bit-vector in the requested endianness, so:128and:f128both preserve every bit. RzNum's float kind itself is double-precision, so:f128does NOT decode to a native quad-precision float; the bit-vector carries the raw 128-bit IEEE-754 pattern and the host can inspect it, compare two quad values bit-for-bit, or feed it to a host function that knows how to print quad-precision.
When evaluated through rz_core_math() (which backs the % shell
command) the suffix dereferences against the active IO layer:
[0x0]> % 0:le32 (on an ELF)
hex 0x464c457f <- little-endian read of 7f 45 4c 46
[0x0]> % 0:s32 <- same bytes, interpreted signed
[0x0]> % 0:lef32 <- same bytes, interpreted as a float
[0x0]> % 0:128 <- 16 bytes as a width-128 bit-vector
When evaluated through the bare rz_num_math_value() API with no
IO-read callback wired in, the suffix is parsed but the literal
address is returned unchanged (parity across every width).
String-as-bytes literal
A double-quoted string is interpreted as a sequence of bytes packed
little-endian (first byte is the least-significant) into a bit-vector
whose width is 8 * byte-count. Supported escapes: \\, \", \n,
\t, \r, \0, \xHH.
"AB" -> 0x4241 bit-vector, width 16 ('A' in the low byte)
"ABCD" -> 0x44434241 bit-vector, width 32
"\x01\xff" -> 0xff01 bit-vector, width 16
The bytes are taken verbatim, so a string written in any script is
packed as its raw UTF-8 source bytes. Because the result is a
bit-vector that carries its width, the byte count is recoverable with
len (see below):
len("ABCD") -> 32 (bits)
len("ABCD") / 8 -> 4 (bytes)
len("ABCDEFGHIJ") -> 80 (a 10-byte string is fine)
len("Ω") -> 16 ("Ω" is two UTF-8 bytes)
Arithmetic on a string value follows bit-vector rules at that width,
so "AB" + 1 is 0x4242 (it wraps within 16 bits). An empty string
"" is the zero-width edge case and evaluates to a ut64 0.
Built-in functions
| Function | Arity | Description |
|---|---|---|
min(a, b) |
2 | smaller operand, kind-preserving |
max(a, b) |
2 | larger operand, kind-preserving |
abs(x) |
1 | absolute value |
popcount(x) |
1 | number of set bits (works on bignums) |
len(x) |
1 | length in BITS of a bit-vector / big / ut64 (a string literal is a bit-vector, so len(s)/8 is its byte count) |
минимум(a, b) |
2 | alias for min (Russian) |
максимум(a, b) |
2 | alias for max (Russian) |
最小(a, b) / 最大(a, b) |
2 | aliases for min / max (Chinese/Japanese) |
دالة(x) |
1 | alias for abs (Arabic) |
Σ(x) |
1 | alias for popcount (Greek sigma) |
len gives a single, bit-keyed notion of length across the
integer-ish kinds: a bit-vector reports its declared width, a big
number its exact significant bit width, a ut64 its significant
bit width (len(0) is 0). Since a string-as-bytes literal is a
bit-vector of 8 * byte-count bits, len("ABCD") is 32 and
len("ABCD") / 8 is the byte count 4. len on a float is a type
error.
The Russian aliases exist as a runnable demonstration that
identifier handling is Unicode-clean end to end; new natural-
language aliases land in librz/util/num/evaluator.c
without grammar changes.
** computes an exact integer power when both operands are
integers and the exponent is a non-negative integer no larger than
4096; the result promotes to a big number when it exceeds ut64.
Otherwise (float operand, negative or oversized exponent) it falls
through to a double computation.
% 2 ** 64 -> decimal 18446744073709551616 (exact)
% 2 ** 0.5 -> float 1.41421 (double path)
Host-registered functions
A host can register additional functions through the C API without modifying the grammar or the evaluator. The function appears in the expression language exactly like a built-in; registered names take precedence over the built-in table, so a host may also shadow one.
Through rz_core_math() (which backs the % shell command) Rizin
registers a set of functions that read bytes through the active IO
layer (bounded to 64 KiB per call) and compute over them:
| Function | Arity | Result | Description |
|---|---|---|---|
md5(addr, n) |
2 | bignum | MD5 digest |
sha1(addr, n) |
2 | bignum | SHA-1 digest |
sha256(addr, n) |
2 | bignum | SHA-256 digest |
sha384(addr, n) |
2 | bignum | SHA-384 digest |
sha512(addr, n) |
2 | bignum | SHA-512 digest |
adler32(addr, n) |
2 | ut64 | Adler-32 checksum |
xxhash(addr, n) |
2 | ut64 | xxHash32 |
hash(addr, n) |
2 | bignum | alias for sha256 |
crc(addr, n [, width]) |
2-3 | ut64 | CRC; width 8/16/32/64, default 32 |
crc8/crc16/crc32/crc64 |
2 | ut64 | explicit CRC-width aliases |
entropy(addr, n) |
2 | float | Shannon entropy, 0.0 - 8.0 |
temperature(addr, n) |
2 | float | information temperature |
The CRC family deliberately lives behind one crc() name with an
optional width parameter rather than separate per-width entry
points:
% crc(0, 16) # CRC-32 (default)
% crc(0, 16, 16) # CRC-16
% crc(0, 16, 64) # CRC-64
Digest results are returned as big numbers laid out big-endian so
the hex form matches the ph <algo> command:
[0x0]> % sha256(0, 16)
decimal 72468553576780861412383346668577895435178167241169020037740589910276139146126
hex 0xa037bf6e958bd6b2fdcc4a95c7dc6f7735730ae33d20819a056a5da050d05b8e
width 256 bits (approx)
To register your own function, create a registry, add entries, and
pass it through RzNumMathOptions.funcs:
RzNumFuncRegistry *reg = rz_num_func_registry_new();
rz_num_func_registry_add(reg, "myfn", 1, my_callback, my_userdata);
RzNumMathOptions opt = { .funcs = reg };
rz_num_math_value_ex(num, expr, &opt, &out, &err);
rz_num_func_registry_free(reg);
rz_num_func_registry_add(reg, name, arity, fn, user):
-
nameis copied as UTF-8, so a function may be registered under any identifier the grammar accepts - including a non-ASCII (Unicode) name. For example a function registered asминимум,合计, orδis then callable by that exact name:rz_num_func_registry_add(reg, "сумма", -1, sum_cb, NULL); // expression: сумма(1, 2, 3) -> 6 -
arityis the exact number of arguments the function requires, or-1for a variadic function that accepts any number of arguments. A call whose argument count does not match a non-negativearityfails withRZ_NUM_ERR_NOT_IMPLEMENTEDbefore the callback runs. There is no upper bound on the number of arguments a variadic function may receive:rz_num_func_registry_add(reg, "сумма", -1, sum_cb, NULL); // сумма(1,2,3,4,5,6,7,8,9,10) -> 55 -
registered names are consulted before the built-in table, so a registration shadows a built-in of the same name. This is how a host can, for instance, replace the 2-argument built-in
minwith a variadic one.
The callback has the signature:
void my_callback(void *user, const RzNumValue *args, int argc,
RzNumCallbackResult *out);
It receives the opaque user pointer from registration, the
already-evaluated args (each carrying its own kind) with their
count argc, and fills an RzNumCallbackResult with a kind, a
payload, and an ok flag. Setting ok to false makes the
expression fail with RZ_NUM_ERR_UNCOMPUTABLE. A big-number or
bit-vector payload transfers ownership to the evaluator.
Result kinds
Every evaluation produces an RzNumValue carrying a kind and a
payload:
| Kind | Payload | Produced by |
|---|---|---|
RZ_NUM_KIND_UT64 |
ut64 n |
integer literals, comparisons, ... |
RZ_NUM_KIND_FLOAT |
double d |
float literals, **, log |
RZ_NUM_KIND_BIG |
RzNumBig* |
overflowing integer literals |
RZ_NUM_KIND_BITVECTOR |
RzBitVector* |
width-suffixed literals (5u8) |
When a sub-expression mixes kinds, the more general kind wins:
ut64 + ut64 -> ut64
ut64 + big -> big (or demoted ut64 if the result fits)
ut64 + float -> float
big + float -> float (big projected to double)
There is no cast operator and no implicit C-style cast, so a mixed
expression never fails for want of a cast - it promotes by the rule
above. Mixing bit-vector widths is the one case that does not
promote: the operand width is kept and the other side is taken modulo
that width (see Bit-vectors). To pin a value to a specific width or
kind, write the literal you want (5u32 for a 32-bit bit-vector) or
read it through a typed address.
The % shell command and rz-ax use the result kind to choose an
output layout (multi-base table for ut64, decimal scientific +
hex bit pattern for float, exact decimal + hex + bit-width for
big, and width + hex + binary for a bit-vector).
Bit-vectors
A width-suffixed literal (5u8, 0xffu16, 0x123456789abcu64)
evaluates to a bit-vector of exactly that width. Unlike ut64 and
big, a bit-vector never changes width during arithmetic: every
operation is computed modulo 2^width, so results wrap instead of
promoting.
Operations defined on bit-vectors:
| Operators | Result | Notes |
|---|---|---|
+ - * / % |
bit-vector | modular at the operand width; / or % by zero is RZ_NUM_ERR_DIV_ZERO |
sdiv smod sar |
bit-vector | two's-complement signed at the operand width; zero divisor is RZ_NUM_ERR_DIV_ZERO |
& | ^ |
bit-vector | bitwise |
<< >> |
bit-vector | shift by the low bits of the right operand |
<<< >>> |
bit-vector | rotate within the operand width (bits that fall off one end wrap to the other); the rotate amount is taken modulo W |
** |
bit-vector | exponentiation by squaring, modular at the operand width (so 2u8 ** 8u8 wraps to 0); the exponent must fit a ut64 |
log |
bit-vector | log_a(b), computed in double internally but returned as a bit-vector at the operand width, not as a float |
== != < <= > >= |
ut64 0/1 |
unsigned comparison |
The signed family (sdiv/smod/sar) lifts structurally to RzIL's
signed operators (/⁺, %⁺, ≫⁺); rotates, **, and log have no
direct RzIL pure-op for bit-vectors, so the lift grounds them to a
constant - the evaluator's result is correct, only the symbolic form
is collapsed.
When a bit-vector is combined with a plain ut64, the bit-vector's
width wins and the integer is taken modulo that width:
% 5u8 + 3 -> width 8, 0x08
% 0xffu8 + 1u8 -> width 8, 0x00 (wraps)
% 1u8 << 4u8 -> width 8, 0x10
% 10u8 / 3u8 -> width 8, 0x03
A bit-vector projects to a ut64 (via rz_num_value_to_ut64() or
the legacy rz_num_math_ut64()) by taking its low 64 bits.
Unicode rendering
When the shell is in UTF-8 mode (scr.utf8=true), the % command
folds the bit-width into the value rows of a bit-vector as a Unicode
subscript - the same notation RzIL uses for bit-vector constants in
its Unicode export - and drops the 0b prefix from the binary row.
With scr.utf8=false the plain ASCII layout is used instead, the
same way plf shows the ASCII layout that pLf renders in Unicode.
[0x0]> e scr.utf8=false
[0x0]> % 200u8 + 100u8
hex 0x2c
binary 0b00101100
width 8 bits
[0x0]> e scr.utf8=true
[0x0]> % 200u8 + 100u8
hex 0x2c₈
binary 00101100₈
width 8 bits
The value rows come first and the width row last in both modes. The
renderer is reachable from C as rz_num_value_print_ex() with
RzNumPrintOptions.utf8 set; rz_num_value_print() is the ASCII
default.
Relationship to RzIL
RzNum's bit-vector kind and RzIL both build on the same RzBitVector
primitive, so the two systems already share a value representation.
The Unicode bit-vector form above is deliberately the same notation
RzIL emits (see il_export_string_unicode.c). Keeping the output
visually consistent means a bit-vector printed by % reads the same
as one inside a pLf listing.
The operator-to-glyph mapping - which RzNum operator becomes which
RzIL pure-op and Unicode symbol - lives in doc/math-il-lift.md, so it
is maintained in one place. Two things tie the systems together:
-
A shared bit-vector formatter. The subscript-width rendering used here and the bit-vector case in RzIL's Unicode exporter are backed by the same RzUtil helpers,
rz_bv_width_subscript()andrz_bv_as_unicode_string(), so the two renderings cannot drift apart. -
Lifting to RzIL.
rz_num_il_lift()(core wrapperrz_core_il_lift()) turns an expression into the matchingRzILOpPure, rendered in the formpLfprints. Operators with a direct pure-op survive as structure; the rest are evaluated to a constant first:1 + 2 * 3 -> (0x1₆₄ + (0x2₆₄ * 0x3₆₄)) 5u8 + 3u8 -> (0x5₈ + 0x3₈) min(5, 3) + 1 -> (0x3₆₄ + 0x1₆₄) (min evaluated first) 2 ** 8 -> 0x100₆₄ (** evaluated)Arithmetic, bitwise, the shifts, the signed
sdiv/smod/sar,==/<=, the ternary (ontoite), and float-pure arithmetic lift structurally - the ternary is the one control-flow construct that survives into symbolic execution. Functions, the typed-address dereference, host variables,**/log, the rotates, mixed integer-and-float arithmetic, and;-sequences are the cases evaluated to a constant.
For an exhaustive set of worked examples - simple operators, deeply
nested expressions, the ternary lifting onto RzIL ite, host-
registered functions, and every case where the lift has to ground a
sub-expression - see doc/math-il-lift.md.
Error categories
Each evaluation either succeeds (RZ_NUM_ERR_OK) or fails with
exactly one category:
| Category | When |
|---|---|
RZ_NUM_ERR_PARSE |
unbalanced parens, unknown special var, ... |
RZ_NUM_ERR_EMPTY |
input is empty or whitespace only |
RZ_NUM_ERR_RESERVED_WORD |
bare mod / log / le / be |
RZ_NUM_ERR_UNDEFINED_VAR |
host callback rejected a known $-variable |
RZ_NUM_ERR_DIV_ZERO |
/ or % by zero |
RZ_NUM_ERR_OVERFLOW |
reserved for future fixed-width literals |
RZ_NUM_ERR_TYPE_MISMATCH |
reserved |
RZ_NUM_ERR_TIMEOUT |
wall-clock budget exceeded |
RZ_NUM_ERR_UNCOMPUTABLE |
log of non-positive, failed function or IO read |
RZ_NUM_ERR_NOT_IMPLEMENTED |
unknown function, unhandled node |
RZ_NUM_ERR_OUT_OF_MEMORY |
allocation failed |
Human-readable names for each category are returned by
rz_num_error_name().
Timeouts
rz_num_math_value_ex() accepts an RzNumMathOptions struct with
a timeout_ms field. The evaluator checks the monotonic clock
every 256 AST nodes; if the budget is exceeded the result carries
RZ_NUM_ERR_TIMEOUT. The default (and the value used by
rz_num_math_value, rz_num_math_ut64, rz-ax, and %) is
zero = unlimited. Set a positive value when feeding untrusted
input or when an interactive prompt must remain responsive.
Examples at the shell
> % 1 + 2 * 3
int32 7
uint32 7
hex 0x7
octal 07
unit 7
segment 0000:0007
binary 0b00000111
trits 0t21
> % 0xff & 0x0f
int32 15
uint32 15
hex 0xf
...
> % 0xffffffffffffffff + 1
int32 0
...
(ut64 + ut64 wraps; write an explicit bignum literal for
arbitrary precision)
> % 0x10000000000000000
decimal 18446744073709551616
hex 0x10000000000000000
width 68 bits (approx)
> % 2 ** 100
decimal 1267650600228229401496703205376
hex 0x100000000000000000000000000
width 104 bits (approx)
> % 3.14159 ** 2
float 9.8696
scifmt 9.8696044010893580
hex 0x4023bd3b7c6aaa1c
> % min(0x100, max(0x50, 0x80))
int32 128
...
> % минимум(0x100, 0x200)
int32 256
...
> % 0:le32 (on a loaded ELF)
hex 0x464c457f <- dereferences 7f 45 4c 46
> % sha256(0, 16)
decimal 72468553576780861412383346668577895435178167241169020037740589910276139146126
hex 0xa037bf6e958bd6b2fdcc4a95c7dc6f7735730ae33d20819a056a5da050d05b8e
width 256 bits (approx)
> %j 0x10000000000000000
{"value":"0x10000000000000000","kind":"big"}
> % 200u8 + 100u8 (8-bit modular arithmetic)
hex 0x2c
binary 0b00101100
width 8 bits