Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/fp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Understanding f32 and f64 in Rust

## Overview
Rust provides two floating-point types: `f32` (32-bit) and `f64` (64-bit). Both follow the IEEE 754 standard for floating-point arithmetic.

## Key Differences

### Precision
- `f32`: Single precision, ~6-7 decimal digits of precision
- `f64`: Double precision, ~15-17 decimal digits of precision

### Memory Usage
- `f32`: 4 bytes
- `f64`: 8 bytes

### Range
- `f32`: ±3.4E+38 to ±3.4E-38
- `f64`: ±1.8E+308 to ±2.2E-308

### Use Cases

#### f32 is preferred when:
- Working with graphics and game development
- Memory is constrained
- Performance is critical
- Full double precision isn't necessary
- Working with hardware that natively uses 32-bit floats

#### f64 is preferred when:
- Higher precision is required
- Working with scientific calculations
- Dealing with large numerical ranges
- Financial calculations (though decimals are often better)
- Default choice for general-purpose floating-point calculations

### Performance Considerations
- `f32` operations are generally faster
- Modern CPUs might handle `f64` just as efficiently
- `f32` arrays use less cache space
- SIMD operations can process more `f32` values simultaneously

### Common Pitfalls
1. Precision loss in `f32` during complex calculations
2. Comparison issues due to floating-point rounding
3. Accumulated errors in long calculation chains
4. Platform-dependent behavior

## Best Practices
1. Use `f64` by default unless you have specific reasons not to
2. Avoid direct equality comparisons
3. Consider using decimal types for financial calculations
4. Document precision requirements in your code
164 changes: 49 additions & 115 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,129 +1,63 @@
fn main() {
// intro_to_u();
string_handler();
}

// function to encapsulate all integers
fn intro_to_u(){
let sum_result: u8 = sum(5, 10);
let mult_result: u64 = multiply(5, 10);
let divide: f64 = divide(20.0, 10.2);
let subtr: isize = substract(20, 10);
let check: bool = check_func(5, 10);
println!("Sum: {}", sum_result);
println!("Multiplication: {}", mult_result);
println!("Division: {}", divide);
println!("Substraction: {}", subtr);
println!("Check: {}", check);

let sum_result: f64 = sumfp(5.0, 10.0);
let mult_result: f64 = multiply_fp(5.0, 10.0);
let divide: f64 = divide_fp(20.0, 10.2);
let subtr: f64 = substract_fp(20.0, 10.0);
println!("Sum: {}", sum_result);
println!("Multiplication: {}", mult_result);
println!("Division: {}", divide);
println!("Substraction: {}", subtr);

let full_name = string_formatting(convert_to_string_v1("Akinshola"), convert_to_string_v2("Akinniyi"));
println!("Full Name: {}", full_name);
}

fn sum(x: u8, y: u8) -> u8 {
x + y
}
fn multiply(x: u64, y: u64) -> u64 {
x * y
}
fn divide(x: f64, y: f64) -> f64 {
let res: f64 = x / y;
return res
}
fn substract(x: isize, y: isize) -> isize {
x - y
}

fn sumfp(x: f64, y: f64) -> f64 {
x + y
}

fn multiply_fp(x: f64, y: f64) -> f64 {
x * y
}
// Example usage
fn main() {
// Integer conversion examples
let small_num: u8 = 255;
let big_num = u8_to_u32(small_num);
println!("Converted u8 to u32: {}", big_num);

let large_num: u32 = 256;
match u32_to_u8(large_num) {
Some(num) => println!("Converted u32 to u8: {}", num),
None => println!("Value {} is too large for u8", large_num),
}

fn divide_fp(x: f64, y: f64) -> f64 {
x / y
// String conversion example
let owned_string = String::from("Hello, World!");
let str_ref = string_as_str(&owned_string);
println!("String as str: {}", str_ref);

// Arithmetic example
let a = 100;
let b = 20;
if let Some((sum, diff, prod, quot)) = checked_arithmetic(a, b) {
println!(
"Sum: {}, Difference: {}, Product: {}, Quotient: {}",
sum, diff, prod, quot
);
}
}

fn substract_fp(x: f64, y: f64) -> f64 {
x - y
// Convert from low integer to high integer (u8 -> u32)
fn u8_to_u32(value: u8) -> u32 {
value as u32
}

fn check_func(num1: u8, num2: u8) -> bool {
let sum_of_two_nums = sum(num1, num2);
if sum_of_two_nums % 2 == 0 {
println!("The sum of {} and {} is even", num1, num2);
return true;
// Convert from high bit to low bit with overflow checking
fn u32_to_u8(value: u32) -> Option<u8> {
if value <= u8::MAX as u32 {
Some(value as u8)
} else {
println!("The sum of {} and {} is odd", num1, num2);
return false;
None
}
}

// fn string_formatting(first_name: &str, last_name: &str) -> String {
// let full_name = format!("{} {}", first_name, last_name);
// return full_name;
// }
fn string_formatting(first_name: String, last_name: String) -> String {
let full_name = format!("{} {}", first_name, last_name);
return full_name;
}

// util fn version 1 to convert &str to String
fn convert_to_string_v1(x: &str) -> String {
x.to_string()
}

// util fn version 2 to convert &str to String
fn convert_to_string_v2(x: &str) -> String {
String::from(x)
}

=======
// function that encapsulate all integers
fn intro_to_u() {
// subtract
// multiplication
// division
let sum_result: u8 = sum(5, 10);
println!("the sum result is: {}", sum_result);
}

fn sum(x: u8, y: u8) -> u8 {
x + y // implicit return
// return x + y; // explicit return
}

// handle all string-related functions
fn string_handler() {
// intro_to_str_slice();
intro_to_ownable_string();
// Convert String to &str (Safe string to str conversion that doesn't leak memory)
fn string_as_str(s: &String) -> &str {
s.as_str()
}

// intro string slice
// for fixed-sized strings
fn intro_to_str_slice() {
let name: &str = "Sylvia";
println!("my name is {}", name)
}
// Arithmetic operations on signed integers with overflow checking
fn checked_arithmetic(a: i32, b: i32) -> Option<(i32, i32, i32, i32)> {
// Returns (sum, difference, product, quotient)
let sum = a.checked_add(b)?;
let difference = a.checked_sub(b)?;
let product = a.checked_mul(b)?;
let quotient = if b != 0 {
a.checked_div(b)?
} else {
return None;
};

fn intro_to_ownable_string() {
let mut name: String = String::from("Wisdom");
println!("first name: here: {}", name);
name.push_str(" John");
println!("final name: here: {}", name);
println!("ptr = address in heap memory: {:?}", name.as_ptr());
Some((sum, difference, product, quotient))
}



149 changes: 149 additions & 0 deletions src/pointer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Pointers in Rust Memory Management

## Overview
Rust's pointer system is fundamental to its memory safety guarantees. Unlike C/C++, Rust provides several pointer types with different ownership and borrowing semantics.

## Types of Pointers

### References (&T and &mut T)
- Most basic form of Rust pointers
- Non-nullable
- Always valid
- Follow borrowing rules
- Zero runtime cost
- Lifetime checked at compile time

Example:
```rust
fn process(data: &u32) {
println!("Value: {}", *data);
}
```

### Box<T>
- Heap allocation
- Single owner
- Fixed size known at compile time
- Automatically deallocated when Box goes out of scope

Example:
```rust
let boxed = Box::new(5);
```

### Raw Pointers (*const T and *mut T)
- Similar to C pointers
- No safety guarantees
- Used in unsafe code
- No automatic cleanup
- Can be null

Example:
```rust
let mut value = 10;
let raw = &mut value as *mut i32;
```

## Memory Management Features

### Ownership Rules
1. Each value has exactly one owner
2. When owner goes out of scope, value is dropped
3. Ownership can be transferred (moved)

### Borrowing Rules
1. One mutable reference OR any number of immutable references
2. References must not outlive their referent
3. No null references possible

### Smart Pointers
- Rc<T>: Reference counted, multiple owners
- Arc<T>: Atomic reference counting for thread-safety
- Weak<T>: Weak references that don't prevent cleanup

## Common Use Cases

### Stack vs Heap Allocation
```rust
// Stack allocation
let x = 5;
let y = &x; // Reference to stack data

// Heap allocation
let boxed = Box::new(5);
let reference = &*boxed; // Reference to heap data
```

### Dynamic Dispatch
```rust
trait Animal {
fn make_sound(&self);
}

// Box<dyn Animal> is a pointer to any type implementing Animal
fn process_animal(animal: Box<dyn Animal>) {
animal.make_sound();
}
```

### Recursive Data Structures
```rust
struct Node<T> {
value: T,
next: Option<Box<Node<T>>>
}
```

## Best Practices

1. Prefer references over raw pointers
2. Use Box<T> for heap allocation
3. Consider Rc<T>/Arc<T> for shared ownership
4. Document unsafe pointer usage
5. Implement Drop trait for custom cleanup
6. Use smart pointers appropriately
7. Understand lifetimes

## Safety Considerations

1. Memory Leaks Prevention
- Rust's ownership system prevents most memory leaks
- Resource cleanup is deterministic
- Drop trait ensures proper cleanup

2. Thread Safety
- Send and Sync traits
- Arc<T> for thread-safe reference counting
- Mutex and RwLock for synchronization

3. Null Safety
- Option<T> instead of null
- Never dereferencing null pointers
- Compile-time checks

## Common Patterns

### Interior Mutability
```rust
use std::cell::RefCell;

let data = RefCell::new(5);
*data.borrow_mut() += 1;
```

### Shared Ownership
```rust
use std::rc::Rc;

let shared = Rc::new(String::from("shared data"));
let clone = Rc::clone(&shared);
```

### Safe Raw Pointer Usage
```rust
unsafe fn dangerous_operation(ptr: *mut i32) {
if !ptr.is_null() {
*ptr += 1;
}
}
```