Currently, that's the way I'm doing it.
fn main() {
let superscript_digits = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"];
let sample_num = 1234;
for i in sample_num.to_string().chars() {
print!("{}", superscript_digits[i.to_digit(10).unwrap() as usize])
}
println!();
}
This gets it done with a fairly minimal amount of code. But I worry that it's not an optimal solution and the conversions are a performance impairment.
In the final implementation the incoming numbers are floats, so even more conversion is happening. I would just need the rounded numbers and do something like:
fn main() {
let superscript_digits = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"];
let sample_nums: Vec<f64> = [12.7, 27.4, 32.8, 41.2].to_vec();
for num in sample_nums {
for c in num.round().to_string().chars() {
print!("{}", superscript_digits[c.to_digit(10).unwrap() as usize])
}
println!();
}
}
I hoped that someone with more experience than myself could share a better way of doing it and why it is so.
Since benchmarking println!()
was not very relevant in my opinion, I decided to use criterion on two variants returning a String
(see superscript_v1_collect()
and superscript_v2_collect()
below).
The v1
version is near to the original code; the v2
version tries to get rid of intermediate storage and conversions.
The result was disappointing (see my_benchmark.rs
below) because the performances were not so different and were varying a lot.
I guess this is due to the memory allocation in the returned String
, so I decided to produce two other variants, much less convenient but avoiding many dynamic allocations (see superscript_v1_ref_mut()
and superscript_v2_ref_mut()
below).
This time, the results are much more stable (especially for v2
) and show a substantial improvement in v2
over v1
.
Note that in v2
I chose a kind of hardcoded solution in which I know the maximal number of digits (10 for u32
).
src/lib.rs
pub fn superscript_v1_collect(value: u32) -> String {
const SUPERSCRIPT_DIGITS: [&str; 10] =
["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"];
value
.to_string()
.chars()
.map(|c| SUPERSCRIPT_DIGITS[c.to_digit(10).unwrap() as usize])
.collect()
}
pub fn superscript_v2_collect(mut value: u32) -> String {
const SUPERSCRIPT_DIGITS: [char; 10] =
['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
let mut started = false;
let mut power_of_ten = 1_000_000_000;
if value == 0 {
'⁰'.to_string()
} else {
(0..10)
.filter_map(|_| {
let digit = value / power_of_ten;
value -= digit * power_of_ten;
power_of_ten /= 10;
if digit != 0 || started {
started = true;
Some(SUPERSCRIPT_DIGITS[digit as usize])
} else {
None
}
})
.collect()
}
}
pub fn superscript_v1_ref_mut(
value: u32,
result: &mut String,
) {
const SUPERSCRIPT_DIGITS: [&str; 10] =
["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"];
result.clear();
for c in value.to_string().chars() {
result.push_str(SUPERSCRIPT_DIGITS[c.to_digit(10).unwrap() as usize]);
}
}
pub fn superscript_v2_ref_mut(
mut value: u32,
result: &mut String,
) {
const SUPERSCRIPT_DIGITS: [char; 10] =
['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
result.clear();
let mut started = false;
let mut power_of_ten = 1_000_000_000;
if value == 0 {
result.push('⁰')
} else {
for _ in 0..10 {
let digit = value / power_of_ten;
value -= digit * power_of_ten;
power_of_ten /= 10;
if digit != 0 || started {
started = true;
result.push(SUPERSCRIPT_DIGITS[digit as usize]);
}
}
}
}
src/main.rs
use prog::{
superscript_v1_collect, superscript_v1_ref_mut, superscript_v2_collect,
superscript_v2_ref_mut,
};
fn main() {
let sample_nums = [
0,
12,
345,
6_789,
12_345,
678_912,
3_456_789,
12_345_678,
912_345_678,
];
let mut s1 = String::new();
let mut s2 = String::new();
for num in sample_nums.iter() {
superscript_v1_ref_mut(*num, &mut s1);
superscript_v2_ref_mut(*num, &mut s2);
println!(
"{} {} {} {} {}",
*num,
superscript_v1_collect(*num),
superscript_v2_collect(*num),
s1,
s2
);
}
}
/*
0 ⁰ ⁰ ⁰ ⁰
12 ¹² ¹² ¹² ¹²
345 ³⁴⁵ ³⁴⁵ ³⁴⁵ ³⁴⁵
6789 ⁶⁷⁸⁹ ⁶⁷⁸⁹ ⁶⁷⁸⁹ ⁶⁷⁸⁹
12345 ¹²³⁴⁵ ¹²³⁴⁵ ¹²³⁴⁵ ¹²³⁴⁵
678912 ⁶⁷⁸⁹¹² ⁶⁷⁸⁹¹² ⁶⁷⁸⁹¹² ⁶⁷⁸⁹¹²
3456789 ³⁴⁵⁶⁷⁸⁹ ³⁴⁵⁶⁷⁸⁹ ³⁴⁵⁶⁷⁸⁹ ³⁴⁵⁶⁷⁸⁹
12345678 ¹²³⁴⁵⁶⁷⁸ ¹²³⁴⁵⁶⁷⁸ ¹²³⁴⁵⁶⁷⁸ ¹²³⁴⁵⁶⁷⁸
912345678 ⁹¹²³⁴⁵⁶⁷⁸ ⁹¹²³⁴⁵⁶⁷⁸ ⁹¹²³⁴⁵⁶⁷⁸ ⁹¹²³⁴⁵⁶⁷⁸
*/
benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use prog::{
superscript_v1_collect, superscript_v1_ref_mut, superscript_v2_collect,
superscript_v2_ref_mut,
};
fn benchmark(c: &mut Criterion) {
let mut c = c.benchmark_group("benches");
let sample_nums = [
0,
12,
345,
6_789,
12_345,
678_912,
3_456_789,
12_345_678,
912_345_678,
];
let mut s1 = String::new();
let mut s2 = String::new();
c.bench_function("superscript_v1_collect", |b| {
b.iter(|| {
for num in sample_nums.iter() {
black_box(superscript_v1_collect(black_box(*num)));
}
})
});
c.bench_function("superscript_v2_collect", |b| {
b.iter(|| {
for num in sample_nums.iter() {
black_box(superscript_v2_collect(black_box(*num)));
}
})
});
c.bench_function("superscript_v1_ref_mut", |b| {
b.iter(|| {
for num in sample_nums.iter() {
superscript_v1_ref_mut(black_box(*num), black_box(&mut s1));
}
})
});
c.bench_function("superscript_v2_ref_mut", |b| {
b.iter(|| {
for num in sample_nums.iter() {
superscript_v2_ref_mut(black_box(*num), black_box(&mut s2));
}
})
});
}
criterion_group!(benches, benchmark);
criterion_main!(benches);
/*
superscript_v1_collect ... [578.36 ns 580.18 ns 583.00 ns]
superscript_v2_collect ... [571.34 ns 572.70 ns 574.11 ns]
superscript_v1_ref_mut ... [321.37 ns 321.79 ns 322.27 ns]
superscript_v2_ref_mut ... [271.51 ns 272.00 ns 272.59 ns]
*/