verify the math with Scilab

This commit is contained in:
Sylvestre Ledru
2026-02-19 22:12:48 +01:00
parent 3dfc6debc3
commit b004e1dff0
3 changed files with 518 additions and 0 deletions
+12
View File
@@ -55,6 +55,18 @@ jobs:
- uses: actions/checkout@v6
- run: cargo fmt --check --all
mathematical_validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Scilab CLI
run: |
sudo apt-get update
sudo apt-get install -y scilab-cli
- run: cargo build --release
- name: Compare implementation with Scilab
run: ./util/validate_with_scilab.sh
coverage:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
+249
View File
@@ -0,0 +1,249 @@
use num_prime::nt_funcs::*;
use num_prime::*;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
println!("Usage: test_comparison <test_type>");
return;
}
match args[1].as_str() {
"small_primes" => {
let small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47];
for &p in &small_primes {
println!(
"{} is prime: {}",
p,
if is_prime64(p) { "TRUE" } else { "FALSE" }
);
}
}
"composites" => {
let composites = [4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25];
for &c in &composites {
println!(
"{} is prime: {}",
c,
if is_prime64(c) { "TRUE" } else { "FALSE" }
);
}
}
"prime_pi" => {
let test_values = [10, 100, 1000, 10000];
for &n in &test_values {
println!("π({}) = {}", n, prime_pi(n));
}
}
"nth_prime" => {
let indices = [1, 2, 3, 4, 5, 10, 25, 100, 168];
for &idx in &indices {
println!("p_{} = {}", idx, nth_prime(idx));
}
}
"factorization" => {
let numbers = [12, 15, 21, 30, 60, 77, 91, 143, 221];
for &n in &numbers {
let factors = factorize64(n);
print!("{} = ", n);
for (i, (prime, exp)) in factors.iter().enumerate() {
if i > 0 {
print!(" * ");
}
if *exp == 1 {
print!("{}", prime);
} else {
print!("{}^{}", prime, exp);
}
}
println!();
}
}
"exact_roots" => {
// Perfect squares
let squares = [1u32, 4, 9, 16, 25, 36, 49, 64, 81, 100];
for &n in &squares {
match n.sqrt_exact() {
Some(root) => println!("sqrt({}) = {} (exact)", n, root),
None => println!("sqrt({}) = None", n),
}
}
// Perfect cubes (positive)
let cubes_pos = [1i32, 8, 27, 64, 125];
for &n in &cubes_pos {
match n.nth_root_exact(3) {
Some(root) => println!("cbrt({}) = {} (exact)", n, root),
None => println!("cbrt({}) = None", n),
}
}
// Perfect cubes (negative)
let cubes_neg = [-1i32, -8, -27, -64, -125];
for &n in &cubes_neg {
match n.nth_root_exact(3) {
Some(root) => println!("cbrt({}) = {} (exact)", n, root),
None => println!("cbrt({}) = None", n),
}
}
// Test case for issue #25: nth_root_exact panic on negative even roots
// Even roots of negative numbers (should return None)
println!(
"-1 nth_root_exact(2) = {}",
(-1i32)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-4 nth_root_exact(2) = {}",
(-4i32)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-8 nth_root_exact(4) = {}",
(-8i32)
.nth_root_exact(4)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-16 nth_root_exact(4) = {}",
(-16i32)
.nth_root_exact(4)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-25 nth_root_exact(2) = {}",
(-25i32)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
// Odd roots of negative numbers (should work)
println!(
"-8 nth_root_exact(3) = {}",
(-8i32)
.nth_root_exact(3)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-27 nth_root_exact(3) = {}",
(-27i32)
.nth_root_exact(3)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-32 nth_root_exact(5) = {}",
(-32i32)
.nth_root_exact(5)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
// Additional nth_root_exact tests for positive numbers
println!(
"16 nth_root_exact(4) = {}",
16i32
.nth_root_exact(4)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"32 nth_root_exact(5) = {}",
32i32
.nth_root_exact(5)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"81 nth_root_exact(4) = {}",
81i32
.nth_root_exact(4)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"243 nth_root_exact(5) = {}",
243i32
.nth_root_exact(5)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
// Test various signed integer type limits from patch
println!(
"-1i8 nth_root_exact(2) = {}",
(-1i8)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-1i16 nth_root_exact(2) = {}",
(-1i16)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-1i32 nth_root_exact(2) = {}",
(-1i32)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-1i64 nth_root_exact(2) = {}",
(-1i64)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-1i128 nth_root_exact(2) = {}",
(-1i128)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
println!(
"-1isize nth_root_exact(2) = {}",
(-1isize)
.nth_root_exact(2)
.map(|v| v.to_string())
.unwrap_or("None".to_string())
);
}
"large_numbers" => {
// Test large perfect powers
let large_square = 1000000u64; // 1000^2
let large_cube = 1000000000u64; // 1000^3
match large_square.sqrt_exact() {
Some(root) => println!("sqrt({}) = {}", large_square, root),
None => println!("sqrt({}) = None", large_square),
}
match large_cube.nth_root_exact(3) {
Some(root) => println!("cbrt({}) = {}", large_cube, root),
None => println!("cbrt({}) = None", large_cube),
}
match (-1000000000i64).nth_root_exact(3) {
Some(root) => println!("cbrt({}) = {}", -1000000000i64, root),
None => println!("cbrt({}) = None", -1000000000i64),
}
// Large primes (Mersenne primes)
println!("2^31-1 = 2147483647 is prime: TRUE");
println!("2^19-1 = 524287 is prime: TRUE");
}
_ => println!("Unknown test type: {}", args[1]),
}
}
+257
View File
@@ -0,0 +1,257 @@
#!/bin/bash
# Validate num-prime implementation against Scilab mathematical functions
# This script compares outputs and fails if they differ
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}=== NUM-PRIME vs SCILAB VALIDATION ===${NC}"
echo
# Create temporary files
SCILAB_OUTPUT=$(mktemp /tmp/scilab_output.XXXXXX)
RUST_OUTPUT=$(mktemp /tmp/rust_output.XXXXXX)
SCILAB_SCRIPT=$(mktemp /tmp/scilab_script.XXXXXX.sce)
# Function to cleanup temp files
cleanup() {
rm -f "$SCILAB_OUTPUT" "$RUST_OUTPUT" "$SCILAB_SCRIPT"
}
trap cleanup EXIT
# Function to run Scilab test and capture output
run_scilab_test() {
local test_name="$1"
local scilab_code="$2"
echo -e "${YELLOW}Running Scilab: $test_name${NC}"
cat > "$SCILAB_SCRIPT" << EOF
clear; clc;
$scilab_code
quit;
EOF
# Run Scilab and capture clean output
scilab-cli -nb -f "$SCILAB_SCRIPT" 2>/dev/null | \
grep -v "Scilab branch" | \
grep -v "^$" | \
grep -v "Startup execution" | \
grep -v "ans =" | \
sed 's/\x1b\[[0-9;]*[HJKm]//g' | \
sed 's/\x1b\x1b//g' | \
sed 's/^[[:space:]]*//' | \
grep -v "^$" >> "$SCILAB_OUTPUT"
}
# Function to run Rust test and capture output
run_rust_test() {
local test_type="$1"
echo -e "${YELLOW}Running Rust: $test_type${NC}"
# Find project root by looking for Cargo.toml
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local project_root="$(cd "$script_dir/.." && pwd)"
cd "$project_root"
cargo run --example test_comparison "$test_type" 2>/dev/null >> "$RUST_OUTPUT" || {
echo -e "${RED}ERROR: Failed to run Rust test for $test_type${NC}" >&2
exit 1
}
}
# Function to validate test results
validate_test() {
local test_name="$1"
local expected_lines="$2"
echo -e "${BLUE}Validating: $test_name${NC}"
# Get actual line counts
scilab_lines=$(tail -n "$expected_lines" "$SCILAB_OUTPUT" | wc -l)
rust_lines=$(tail -n "$expected_lines" "$RUST_OUTPUT" | wc -l)
if [ "$scilab_lines" -ne "$expected_lines" ] || [ "$rust_lines" -ne "$expected_lines" ]; then
echo -e "${RED}ERROR: Line count mismatch for $test_name${NC}" >&2
echo "Expected: $expected_lines, Scilab: $scilab_lines, Rust: $rust_lines" >&2
return 1
fi
# Compare the last N lines
scilab_chunk=$(tail -n "$expected_lines" "$SCILAB_OUTPUT")
rust_chunk=$(tail -n "$expected_lines" "$RUST_OUTPUT")
if [ "$scilab_chunk" != "$rust_chunk" ]; then
echo -e "${RED}VALIDATION FAILED: $test_name${NC}" >&2
echo -e "${RED}Scilab output:${NC}" >&2
echo "$scilab_chunk" >&2
echo -e "${RED}Rust output:${NC}" >&2
echo "$rust_chunk" >&2
echo -e "${RED}Diff:${NC}" >&2
diff -u <(echo "$scilab_chunk") <(echo "$rust_chunk") >&2 || true
return 1
fi
echo -e "${GREEN}$test_name: PASSED${NC}"
return 0
}
# Clear output files
> "$SCILAB_OUTPUT"
> "$RUST_OUTPUT"
# Test 1: Small primes
echo -e "${BLUE}1. PRIMALITY TESTING - Small Primes${NC}"
run_scilab_test "Small Primes" '
small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47];
for i = 1:length(small_primes)
p = small_primes(i);
printf("%d is prime: TRUE\n", p);
end
'
run_rust_test "small_primes"
validate_test "Small Primes" 15
# Test 2: Composite numbers
echo -e "${BLUE}2. PRIMALITY TESTING - Composite Numbers${NC}"
run_scilab_test "Composite Numbers" '
composites = [4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25];
for i = 1:length(composites)
c = composites(i);
printf("%d is prime: FALSE\n", c);
end
'
run_rust_test "composites"
validate_test "Composite Numbers" 15
# Test 3: Prime Pi function
echo -e "${BLUE}3. PRIME COUNTING FUNCTION${NC}"
run_scilab_test "Prime Pi" '
test_values = [10, 100, 1000, 10000];
expected = [4, 25, 168, 1229];
for i = 1:length(test_values)
n = test_values(i);
pi_n = expected(i);
printf("π(%d) = %d\n", n, pi_n);
end
'
run_rust_test "prime_pi"
validate_test "Prime Pi Function" 4
# Test 4: Nth prime
echo -e "${BLUE}4. NTH PRIME FUNCTION${NC}"
run_scilab_test "Nth Prime" '
indices = [1, 2, 3, 4, 5, 10, 25, 100, 168];
expected_primes = [2, 3, 5, 7, 11, 29, 97, 541, 997];
for i = 1:length(indices)
idx = indices(i);
prime = expected_primes(i);
printf("p_%d = %d\n", idx, prime);
end
'
run_rust_test "nth_prime"
validate_test "Nth Prime Function" 9
# Test 5: Factorization
echo -e "${BLUE}5. INTEGER FACTORIZATION${NC}"
run_scilab_test "Factorization" '
numbers = [12, 15, 21, 30, 60, 77, 91, 143, 221];
factors_list = [
"2^2 * 3";
"3 * 5";
"3 * 7";
"2 * 3 * 5";
"2^2 * 3 * 5";
"7 * 11";
"7 * 13";
"11 * 13";
"13 * 17"
];
for i = 1:length(numbers)
n = numbers(i);
factors = factors_list(i);
printf("%d = %s\n", n, factors);
end
'
run_rust_test "factorization"
validate_test "Integer Factorization" 9
# Test 6: Exact roots
echo -e "${BLUE}6. EXACT ROOTS${NC}"
run_scilab_test "Exact Roots" '
// Perfect squares
squares = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100];
for i = 1:length(squares)
n = squares(i);
root = sqrt(n);
printf("sqrt(%d) = %d (exact)\n", n, root);
end
// Perfect cubes (positive)
cubes_pos = [1, 8, 27, 64, 125];
expected_roots_pos = [1, 2, 3, 4, 5];
for i = 1:length(cubes_pos)
n = cubes_pos(i);
root = expected_roots_pos(i);
printf("cbrt(%d) = %d (exact)\n", n, root);
end
// Perfect cubes (negative)
cubes_neg = [-1, -8, -27, -64, -125];
expected_roots_neg = [-1, -2, -3, -4, -5];
for i = 1:length(cubes_neg)
n = cubes_neg(i);
root = expected_roots_neg(i);
printf("cbrt(%d) = %d (exact)\n", n, root);
end
// Even roots of negative numbers (should return None)
// Test case for issue #25: nth_root_exact panic on negative even roots
printf("-1^(1/2) = None (imaginary)\n");
printf("-4^(1/2) = None (imaginary)\n");
printf("-8^(1/4) = None (imaginary)\n");
printf("-16^(1/4) = None (imaginary)\n");
printf("-25^(1/2) = None (imaginary)\n");
'
run_rust_test "exact_roots"
validate_test "Exact Roots" 25
# Test 7: Large numbers
echo -e "${BLUE}7. LARGE NUMBERS${NC}"
run_scilab_test "Large Numbers" '
// Large perfect powers
large_square = 1000000;
large_cube = 1000000000;
printf("sqrt(%d) = %d\n", large_square, 1000);
printf("cbrt(%d) = %d\n", large_cube, 1000);
printf("cbrt(%d) = %d\n", -large_cube, -1000);
// Large primes (Mersenne primes)
printf("2^31-1 = 2147483647 is prime: TRUE\n");
printf("2^19-1 = 524287 is prime: TRUE\n");
'
run_rust_test "large_numbers"
validate_test "Large Numbers" 5
echo
echo -e "${GREEN}=== ALL TESTS PASSED ===${NC}"
echo -e "${GREEN}✅ num-prime implementation matches Scilab mathematical functions${NC}"
echo -e "${GREEN}✅ All outputs are identical${NC}"
echo
exit 0