uiua-lang / uiua

A stack-based array programming language
https://www.uiua.org
MIT License
1.59k stars 116 forks source link

In Pad, Matrix exponentiation doesn't behave as expected #134

Closed and-vil closed 1 year ago

and-vil commented 1 year ago

Using the Matrix exponentiation Uiuism on Chrome on Windows 10, both up-to-date, (this is the direct Pad link, but is located at the bottom of https://www.uiua.org/isms) on the 2-D basis matrix for the Fibonacci sequence, there are computation errors for exponents greater than 78.

For reference, the 79th term of the Fibonacci sequence should be 14472334024676221, but has a zero in the ones place if run with the Uiuism. For an example of this, here is a link to a Pad with the Fibonacci basis matrix and an n-value of 78, with n+1 in the first matrix cell of the result. As sequence terms increase past 79, this continues for successive terms, and there is a loss in precision as terms increase, but the initial digits of successive terms are correct until hitting their respective limits.

Since this error only occurs above the 32-bit unsigned integer limit of 4294967295 and for some values under the signed 64-bit limit of 9223372036854775807, this may be unintended behavior.

kaikalii commented 1 year ago

This is a floating-point rounding error, as most numbers in Uiua are represented as IEEE doubles. Consider the output of this Rust program.

Garmelon commented 1 year ago

That rust playground link contains no rust program

and-vil commented 1 year ago

@Garmelon Try this link. If that fails, here's some code and output:

Rust code:

fn main() {
    let err_num: f32 = 14472334024676221f32;
    println!("Errored number (f32): {}", err_num);
    let err_num: f64 = 14472334024676221f64; //shadowing `err_num`
    println!("Also errored number (f64): {}", err_num);
    let num: u64 = 14472334024676221u64;
    println!("Number (u64): {}", num);
    let num: u128 = 14472334024676221u128; //shadowing `num`
    println!("Also number (u128): {}", num);
}

Output:

Errored number (f32): 14472334000000000
Also errored number (f64): 14472334024676220
Number (u64): 14472334024676221
Also number (u128): 14472334024676221

In an ideal world, it might be nice to have strictly integral-type matrices that are fully integrally bounded to use integral types under the hood, but given that matrices can be float-valued, it makes sense to implement them in that domain alone, especially since one could use modulo or other algos to just remap into the appropriate range. Implementing some sort of type-switching that could do so would allow for some more flexibility, but I would imagine would be a nightmare to implement, even if possible. This isn't truly an issue at this point, especially since there are other ways of approaching this particular problem.

P.S. This same f64 (or equivalent-size IEEE double) numeric limitation applies in APL/J and BQN (when also testing using similar online interpreters/environments), so it seems fairly standard (linguistically speaking, anyhow) for operations structured like this to remain strictly float-valued.

kaikalii commented 1 year ago

The interpreter currently uses both an f64 array type and a u8 array type. The u8 type is mostly for space efficiency when reading files in and sometimes for booleans. While it would not be hard to add an integral type, it would be a lot of code both to write and for the Rust compiler to chew on, further increasing the compile times of the interpreter itself. It is a combinatorics problem. A new array type wouldn't necessarily be a lot to add. A new array type that has to behave like the other number types would be a lot, as most functions on arrays have to type check, and each new number type would double the number of number-type checks. Some of these could be eliminated by doing a catch-all convert-to-f64 for some operations, but this would also defeat the purpose of adding an integral type in the first place.