use std::any::type_name;
// Function to create a formatted banner
pub fn banner(sep: &str, nchar: usize, message: &str) {
let sep = sep.repeat(nchar);
let message = format!("{:^width$}", message, width = nchar);
println!("\n{}\n{}\n{}", sep, message, sep);
}
fn type_of<T>(_: T) -> &'static str {
type_name::<T>()
}
Floating point data types in Rust are used to represent numbers that have fractional parts. They are especially useful for scientific computations, graphics programming, and any application where you need to perform calculations with real numbers. Rust provides two primary floating point types:
- f32: A 32-bit floating point number.
- f64: A 64-bit floating point number (double precision).
In this lesson, we will cover the basics of floating point types in Rust, how to declare them, their ranges, and some common operations.
The f32: 32-bit Floating Point Number
The f32 type represents a single-precision floating-point number. It occupies 32 bits (4 bytes) of memory and provides a balance between range and precision, suitable for many applications where performance and memory usage are considerations.
Range and Precision:
- The approximate range is from 1.2E-38 to 3.4E+38.
- It has about 7 decimal digits of precision.
Recommendation
- Use
f32
only if you want to save some space - Use
f64
for maximum precision generally. (Most APIs in Rust usef64
)
/// This program demonstrates the use of the 32-bit floating-point type (`f32`) in Rust.
/// It showcases how to define and print the value, type, and size in memory.
fn main() {
banner("=", 62, "32-bit floating-point variable with 4 Decimal Points");
// Define a 32-bit floating-point variable with a precise value for Pi
let float32: f32 = 3.1415927;
println!(
"Value: {:.4}, Type: {}, Size: {} bytes",
float32,
type_of(float32),
std::mem::size_of::<f32>()
);
}
main();
==============================================================
32-bit floating-point variable with 4 Decimal Points
==============================================================
Value: 3.1416, Type: f32, Size: 4 bytes
The f64: 64-bit Floating Point Number
The f64 type represents a double-precision floating-point number. It occupies 64 bits (8 bytes) of memory and offers higher precision and range compared to f32, making it suitable for more precise scientific computations.
Range and Precision:
- The approximate range is from 2.2E-308 to 1.8E+308.
- It has about 15 decimal digits of precision.
/// This program demonstrates the use of the 64-bit floating-point type (`f64`) in Rust.
/// It showcases how to define and print the value, type, and size in memory.
fn main() {
banner("=", 62, "64-bit floating-point variable with 7 Decimal Points");
// Define a 64-bit floating-point variable with a precise value for Pi
let float64: f64 = 3.141592653589793;
println!(
"Value: {:.7}, Type: {}, Size: {} bytes",
float64,
type_of(float64),
std::mem::size_of::<f64>()
);
}
main();
==============================================================
64-bit floating-point variable with 7 Decimal Points
==============================================================
Value: 3.1415927, Type: f64, Size: 8 bytes
Floats in Details
The following example will show some details about the floating point number, such as the memory size, and the precision. The f64
is really a big number, you'll see!
// program demonstrating the float data types in rust
fn main() {
banner("=", 62, "The 32-bit floating point type");
// f32: The 32-bit floating point type
let float32: f32 = 3.14;
println!("Value: {:<15} \nType: {:<10} \nSize: {} bytes \nMin: {},\nMax: {}",
float32,
type_of(float32),
std::mem::size_of::<f32>(),
f32::MIN,
f32::MAX
);
banner("=", 62, "The 64-bit floating point t");
// f64: The 64-bit floating point type
let float64: f64 = 3.14;
println!("Value: {:<15} \nType: {:<10}\nSize: {} bytes \nMin: {} \nMax: {}",
float64,
type_of(float64),
std::mem::size_of::<f64>(),
f64::MIN,
f64::MAX
);
}
main();
==============================================================
The 32-bit floating point type
==============================================================
Value: 3.14
Type: f32
Size: 4 bytes
Min: -340282350000000000000000000000000000000,
Max: 340282350000000000000000000000000000000
==============================================================
The 64-bit floating point t
==============================================================
Value: 3.14
Type: f64
Size: 8 bytes
Min: -179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Max: 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Special Values
Floating point numbers can represent several special values, including:
- Infinity: Positive and negative infinity.
- NaN: Not a Number, used to represent undefined or unrepresentable values.
Examples:
fn main() {
banner("=", 62, "Special Values");
let positive_infinity = f32::INFINITY;
let negative_infinity = f32::NEG_INFINITY;
let nan = f32::NAN;
println!("Positive Infinity: {}", positive_infinity);
println!("Negative Infinity: {}", negative_infinity);
println!("NaN: {}", nan);
}
main();
==============================================================
Special Values
==============================================================
Positive Infinity: inf
Negative Infinity: -inf
NaN: NaN
Formatting Float Variable
The {..}
of println!
macro can used to format float variables along with other data types.
Specifying the Field Width
The println!
macro can be used to format strings in various ways. One common formatting need is to specify the field width for printed values. This can be done using the format specifications within the placeholder {}
. This section explains how to specify the field width and how to use fill characters in formatted output.
Specifying the Field Width
To specify the field width in the println!
macro, you can use the format specifications inside the curly braces {}
. The general syntax is {:width}
, where width
is the minimum number of characters to be printed. If the value to be printed is shorter than the specified width, it will be padded with spaces by default.
The println!
formatting Behavior
By default, the println!
macro right-aligns integers and left-aligns strings when a field width is specified. Here
- Right Alignment for Integers: When formatting integers with a specified field width, Rust pads the output with spaces to the left, effectively right-aligning the integer within the specified width.
- Left Alignment for Strings: When formatting strings with a specified field width, Rust pads the output with spaces to the right, effectively left-aligning the string within the specified width.
fn main() {
// Right-aligned integer with a field width of 5
println!("{:5}", 42); // Prints " 42"
// Left-aligned string with a field width of 5
println!("{:5}", "Hi"); // Prints "Hi "
// Additional examples for clarity
println!("{:5}", 7); // Prints " 7"
println!("{:5}", "Rust"); // Prints "Rust "
}
main();
42
Hi
7
Rust
In this example, the number 42 and 7 are right-aligned with spaces added to the left, while the string "Hi" and "Rust" are left-aligned within a field of width 5, padded with spaces to the right.
Specifying Alignment
- By default, values are right-aligned. You can change the alignment using:
<
for left alignment>
for right alignment (the default)^
for center alignment.
fn main() {
banner("=", 62, "Formatting Alignment");
// Right-aligned integer with a field width of 5 (default)
println!("{:>7}", 42); // Prints " 42"
// Left-aligned integer with a field width of 7
println!("{:<7}", 42); // Prints "42 "
// Center-aligned integer with a field width of 7
println!("{:^7}", 42); // Prints " 42 "
// Right-aligned string with a field width of 7
println!("{:>7}", "Rust"); // Prints " Rust"
// Left-aligned string with a field width of 7 (default)
println!("{:<7}", "Rust"); // Prints "Rust "
// Center-aligned string with a field width of 7
println!("{:^7}", "Rust"); // Prints " Rust "
}
main();
==============================================================
Formatting Alignment
==============================================================
42
42
42
Rust
Rust
Rust
Specifying the Fill Character
- You can specify a fill character to pad the output instead of spaces. This is done by placing the fill character and the alignment specifier before the width:
fn main() {
banner("=", 62, "Example of right-alignment with zero padding");
// Prints "0000042" with zeros as the fill character to make the total width 7
println!("{:0>7}", 42);
// Example of left-alignment with asterisks as the fill character
// Prints "Hi*****" with asterisks added to the right to make the total width 7
println!("{:*<7}", "Hi");
// Example of center-alignment within a field width of 10
// Prints " Rust " with spaces added to both sides to center the text
println!("{:^10}", "Rust");
}
// Call the main function to execute the program
main();
==============================================================
Example of right-alignment with zero padding
==============================================================
0000042
Hi*****
Rust
Specifying Precision for Floating-Point Numbers
You can control the number of decimal places displayed for floating-point numbers by specifying the precision after a period (.) within the formatting string. This feature is particularly useful when you need to format numbers for display purposes, such as in scientific calculations, financial applications, or any context where the precision of floating-point representation matters.
Example: Formatting Floating-Point Numbers with Precision
The following example demonstrates how to specify the number of decimal places for a floating-point number:
fn main() {
banner("=", 62, "Formatting Floating-Point Numbers with Precision");
let pi = 3.141592653589793;
// Print with 2 decimal places
println!("{:.2}", pi); // Prints "3.14"
// Print with 4 decimal places
println!("{:.4}", pi); // Prints "3.1416"
}
main();
==============================================================
Formatting Floating-Point Numbers with Precision
==============================================================
3.14
3.1416
Here is another example showing how to format floats:
fn main() {
banner("=", 62, "Extended example of formatting floats");
let e = 2.718281828459045;
// Print with no decimal places
println!("{:.0}", e); // Prints "3"
// Print with 1 decimal place
println!("{:.1}", e); // Prints "2.7"
// Print with 5 decimal places
println!("{:.5}", e); // Prints "2.71828"
// Print with 10 decimal places
println!("{:.10}", e); // Prints "2.7182818285"
}
main();
==============================================================
Extended example of formatting floats
==============================================================
3
2.7
2.71828
2.7182818285
Combining Width, Alignment, and Precision
- You can combine field width, alignment, fill character, and precision for more complex formatting:
fn main() {
banner("=", 62, "Combining different alignment operators");
// Right-align with custom width and padding character
println!("{:*>10}", 123); // Prints "*******123"
// Left-align with custom width and padding character
println!("{:-<10}", "Hello"); // Prints "Hello-----"
// Center-align with custom width and padding character
println!("{:=^12}", "World"); // Prints "===World==="
// Default right-align for integers
println!("{:>8}", 45); // Prints " 45"
// Default left-align for strings
println!("{:<8}", "Hi"); // Prints "Hi "
let pi = 3.141592653589793;
println!("{:>10.2}", pi); // Prints " 3.14" with right alignment, width 10, and 2 decimal places
println!("{:*^10.4}", pi); /*
Prints "**3.1416**" with center alignment, width 10, 4 decimal places,
and asterisks as the fill character
*/
let float64: f64 = 3.141592653589793;
println!("L-aligned: {:~<10.4}", float64);
println!("R-aligned: {:~>10.4}", float64);
}
main();
==============================================================
Combining different alignment operators
==============================================================
*******123
Hello-----
===World====
45
Hi
3.14
**3.1416**
L-aligned: 3.1416~~~~
R-aligned: ~~~~3.1416
Formatting with Named Arguments
You can use named arguments in the println!
macro for clearer formatting:
fn main() {
banner("=", 62, "Formatting Named Arguments");
let name = "Alice";
let age = 30;
// Use named arguments in the println! macro to print a formatted string
println!("{name} is {age} years old", name = name, age = age);
}
main();
==============================================================
Formatting Named Arguments
==============================================================
Alice is 30 years old
Formatting Numbers with Thousands Separator
To include a thousands separator in numeric values, you can use the num.to_formatted_string(&Locale::en) from the num-format
crate.
:dep num-format
use num_format::{Locale, ToFormattedString};
fn main() {
let num = 1000000;
println!("{}", num.to_formatted_string(&Locale::en));
}
main();
Here is a customized function to do that;
fn format_with_commas(num: u64) -> String {
let num_str = num.to_string();
let mut result = String::new();
let mut count = 0;
for (i, c) in num_str.chars().rev().enumerate() {
if i != 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
result.chars().rev().collect()
}
fn main() {
let num = 1_000_000; // Using underscores for readability in the code
println!("{}", format_with_commas(num)); // Prints "1,000,000"
}
main();
1,000,000
Scientific Notation
Scientific notation is a way of representing numbers that are too large or too small to be conveniently written in decimal form. Rust's println!
macro allows you to format numbers in scientific notation using either lowercase e
or uppercase E
.
The Lowercase e
Using the lowercase e
in the format specifier will output the number in scientific notation with a lowercase e
to denote the exponent.
Example:
fn main() {
let num = 12345.6789;
println!("{:e}", num); // Prints "1.234568e4"
}
main();
1.23456789e4
The Uppercase E
- Using the uppercase E in the format specifier will output the number in scientific notation with an uppercase E to denote the exponent.
Example:
fn main() {
let num = 12345.6789;
println!("{:E}", num); // Prints "1.234568E4"
}
main();
1.23456789E4
fn main() {
let num = 0.00012345;
println!("{:e}", num); // Prints "1.234500e-4"
println!("{:E}", num); // Prints "1.234500e-4"
}
main();
1.2345e-4
1.2345E-4
Here is a practical example where to format float in scientific notation:
fn main() {
let pi = 3.141592653589793;
let euler_number = 2.718281828459045;
let golden_ratio = 1.618033988749895;
let avogadro_number = 6.02214076e23;
let speed_of_light = 299792458.0;
let gravitational_constant = 6.67430e-11;
println!("Pi (π) in scientific notation (e): {:e}", pi);
println!("Pi (π) in scientific notation (E): {:E}", pi);
println!("Euler's Number (e) in scientific notation (e): {:e}", euler_number);
println!("Euler's Number (e) in scientific notation (E): {:E}", euler_number);
println!("Golden Ratio (φ) in scientific notation (e): {:e}", golden_ratio);
println!("Golden Ratio (φ) in scientific notation (E): {:E}", golden_ratio);
println!("Avogadro's Number in scientific notation (e): {:e}", avogadro_number);
println!("Avogadro's Number in scientific notation (E): {:E}", avogadro_number);
println!("Speed of Light (c) in scientific notation (e): {:e}", speed_of_light);
println!("Speed of Light (c) in scientific notation (E): {:E}", speed_of_light);
println!("Gravitational Constant (G) in scientific notation (e): {:e}", gravitational_constant);
println!("Gravitational Constant (G) in scientific notation (E): {:E}", gravitational_constant);
}
main();
Pi (π) in scientific notation (e): 3.141592653589793e0
Pi (π) in scientific notation (E): 3.141592653589793E0
Euler's Number (e) in scientific notation (e): 2.718281828459045e0
Euler's Number (e) in scientific notation (E): 2.718281828459045E0
Golden Ratio (φ) in scientific notation (e): 1.618033988749895e0
Golden Ratio (φ) in scientific notation (E): 1.618033988749895E0
Avogadro's Number in scientific notation (e): 6.02214076e23
Avogadro's Number in scientific notation (E): 6.02214076E23
Speed of Light (c) in scientific notation (e): 2.99792458e8
Speed of Light (c) in scientific notation (E): 2.99792458E8
Gravitational Constant (G) in scientific notation (e): 6.6743e-11
Gravitational Constant (G) in scientific notation (E): 6.6743E-11
Common Operations
- Floating point numbers support a variety of operations, including addition, subtraction, multiplication, division, and more.
Example of Basic Operations:
fn main() {
banner("=", 62, "Basic Arithmetic operations with Floats");
let a: f32 = 5.5;
let b: f32 = 2.2;
// Perform basic arithmetic operations
let sum = a + b;
let difference = a - b;
let product = a * b;
let quotient = a / b;
// Print the results of the arithmetic operations
println!("Sum: {}", sum);
println!("Difference: {}", difference);
println!("Product: {}", product);
println!("Quotient: {}", quotient);
}
// Call the main function to execute the program
main();
==============================================================
Basic Arithmetic operations with Floats
==============================================================
Sum: 7.7
Difference: 3.3
Product: 12.1
Quotient: 2.5
Methods on Floating Point Types
-
Rust provides several useful methods for floating point types. Here are some common ones:
abs()
: Returns the absolute value.sqrt()
: Returns the square root.powi()
: Raises the number to an integer power.sin()
,cos()
,tan()
: Trigonometric functions.
Examples:
fn main() {
banner("=", 62, "Few Methods of float types");
let x: f64 = -3.14;
let y: f64 = 2.0;
println!("Absolute value of {}: {}", x, x.abs());
println!("Square root of {}: {}", y, y.sqrt());
println!("{} raised to the power 3: {}", y, y.powi(3));
println!("Sine of {}: {}", x, x.sin());
}
main();
==============================================================
Few Methods of float types
==============================================================
Absolute value of -3.14: 3.14
Square root of 2: 1.4142135623730951
2 raised to the power 3: 8
Sine of -3.14: -0.0015926529164868282
Summary
- Rust provides two floating point types:
f32
(32-bit) andf64
(64-bit). f32
is single-precision, suitable for applications where memory is constrained.f64
is double-precision, suitable for applications requiring higher precision.- Floating point types support a wide range of mathematical operations and special values.
- Rust’s standard library provides many useful methods for working with floating point numbers.