From baf4d5d2bc6b1e2529b27957e1190f923d95056c Mon Sep 17 00:00:00 2001 From: LagoLunatic Date: Wed, 23 Jul 2025 20:23:00 -0400 Subject: [PATCH] Don't round partially-matched percentages to 0.00% or 100.00% (#8) * Don't round percentages to 0.00% or 100.00% * Remove dead code --- crates/core/src/util.rs | 9 +++++++++ crates/github/src/changes.rs | 25 ++++++++++--------------- crates/images/src/badge.rs | 4 +--- crates/images/src/treemap.rs | 4 +--- crates/web/src/handlers/common.rs | 23 +++++++++++++---------- crates/web/src/handlers/project.rs | 6 +++--- crates/web/src/handlers/report.rs | 10 +++++----- js/history.ts | 6 ++---- js/treemap.ts | 8 +++----- js/util.ts | 11 +++++++++++ 10 files changed, 58 insertions(+), 48 deletions(-) create mode 100644 js/util.ts diff --git a/crates/core/src/util.rs b/crates/core/src/util.rs index b8fb147..10245c8 100644 --- a/crates/core/src/util.rs +++ b/crates/core/src/util.rs @@ -71,3 +71,12 @@ pub fn size(value: u64) -> String { } format!("{:.2} {}", value, units[unit]) } + +/// Formats a progress percentage as a string, and prevents partial matches from being rounded to 0.00% or 100.00%. +pub fn format_percent(value: f32) -> String { + let mut value = value; + if value > 0.0 && value < 100.0 { + value = value.clamp(0.01, 99.99); + } + format!("{value:.2}%") +} diff --git a/crates/github/src/changes.rs b/crates/github/src/changes.rs index 06968b4..9085bc1 100644 --- a/crates/github/src/changes.rs +++ b/crates/github/src/changes.rs @@ -1,7 +1,7 @@ use std::{cmp::Ordering, collections::BTreeMap}; use anyhow::Result; -use decomp_dev_core::models::Commit; +use decomp_dev_core::{models::Commit, util::format_percent}; use objdiff_core::bindings::report::{ ChangeItem, ChangeItemInfo, ChangeUnit, Changes, Report, ReportItem, ReportUnit, }; @@ -130,8 +130,9 @@ fn measure_line_matched( to_percent: f32, ) -> String { let emoji = if to > from { "📈" } else { "📉" }; + let to_percent_str = format_percent(to_percent); let percent_diff = to_percent - from_percent; - let percent_str = if percent_diff < 0.0 { + let percent_diff_str = if percent_diff < 0.0 { format!("{percent_diff:.2}%") } else { format!("+{percent_diff:.2}%") @@ -141,7 +142,7 @@ fn measure_line_matched( Ordering::Less => bytes_diff.to_string(), Ordering::Equal | Ordering::Greater => format!("+{bytes_diff}"), }; - format!("{emoji} **{name}**: {to_percent:.2}% ({percent_str}, {bytes_str} bytes)\n") + format!("{emoji} **{name}**: {to_percent_str} ({percent_diff_str}, {bytes_str} bytes)\n") } fn measure_line_bytes(name: &str, from: u64, to: u64) -> String { @@ -189,19 +190,13 @@ fn output_line(line: &ChangeLine, out: &mut String) { Ordering::Greater => format!("+{}", line.bytes_diff), }; - // Avoid showing 100% for nearly-matched functions due to rounding. - let mut from_percent = line.from_fuzzy_match_percent; - if from_percent > 99.99 && from_percent < 100.00 { - from_percent = 99.99; - } - let mut to_percent = line.to_fuzzy_match_percent; - if to_percent > 99.99 && to_percent < 100.00 { - to_percent = 99.99; - } - out.push_str(&format!( - "| `{}` | `{}` | {} | {:.2}% | {:.2}% |\n", - line.unit_name, line.item_name, bytes_str, from_percent, to_percent, + "| `{}` | `{}` | {} | {} | {} |\n", + line.unit_name, + line.item_name, + bytes_str, + format_percent(line.from_fuzzy_match_percent), + format_percent(line.to_fuzzy_match_percent), )); } diff --git a/crates/images/src/badge.rs b/crates/images/src/badge.rs index 759cdf6..432e9de 100644 --- a/crates/images/src/badge.rs +++ b/crates/images/src/badge.rs @@ -1,5 +1,5 @@ use anyhow::{Result, anyhow}; -use decomp_dev_core::util::size; +use decomp_dev_core::util::{format_percent, size}; use image::ImageFormat; use objdiff_core::bindings::report::Measures; use serde::{Deserialize, Serialize}; @@ -30,8 +30,6 @@ pub struct ShieldResponse { label_color: Option, } -fn format_percent(value: f32) -> String { format!("{value:.2}%") } - fn format_bytes(a: u64, b: u64) -> String { let mut a_buf = num_format::Buffer::default(); let mut b_buf = num_format::Buffer::default(); diff --git a/crates/images/src/treemap.rs b/crates/images/src/treemap.rs index d544f30..f9ff0d0 100644 --- a/crates/images/src/treemap.rs +++ b/crates/images/src/treemap.rs @@ -28,9 +28,7 @@ pub fn hsl(h: u16, s: u8, l: u8) -> Srgb { Srgb::from_color(hsl) } -pub fn color_mix(c1: Srgb, c2: Srgb, percent: f32) -> Srgb { - c1.mix(c2, percent) -} +pub fn color_mix(c1: Srgb, c2: Srgb, percent: f32) -> Srgb { c1.mix(c2, percent) } pub fn unit_color(fuzzy_match_percent: f32) -> String { html_color(if fuzzy_match_percent == 100.0 { diff --git a/crates/web/src/handlers/common.rs b/crates/web/src/handlers/common.rs index c478f4f..b8f599c 100644 --- a/crates/web/src/handlers/common.rs +++ b/crates/web/src/handlers/common.rs @@ -13,7 +13,10 @@ use axum::{ response::{IntoResponseParts, ResponseParts}, }; use decomp_dev_auth::CurrentUser; -use decomp_dev_core::{AppError, util::size}; +use decomp_dev_core::{ + AppError, + util::{format_percent, size}, +}; use maud::{Markup, PreEscaped, Render, html}; use objdiff_core::bindings::report::Measures; use regex::Regex; @@ -305,8 +308,8 @@ impl TemplateContext { "progress-section", measures.complete_code_percent - current_percent, format!( - "{:.2}% fully linked ({})", - measures.complete_code_percent, + "{} fully linked ({})", + format_percent(measures.complete_code_percent), size(measures.complete_code) ), ); @@ -317,8 +320,8 @@ impl TemplateContext { if current_percent > 0.0 { "progress-section striped" } else { "progress-section" }, measures.matched_code_percent - current_percent, format!( - "{:.2}% perfect match ({})", - measures.matched_code_percent, + "{} perfect match ({})", + format_percent(measures.matched_code_percent), size(measures.matched_code) ), ); @@ -328,7 +331,7 @@ impl TemplateContext { out.push( "progress-section striped fuzzy", measures.fuzzy_match_percent - current_percent, - format!("{:.2}% fuzzy match", measures.fuzzy_match_percent), + format!("{} fuzzy match", format_percent(measures.fuzzy_match_percent)), ); } out @@ -345,8 +348,8 @@ impl TemplateContext { "progress-section", measures.complete_data_percent - current_percent, format!( - "{:.2}% fully linked ({})", - measures.complete_data_percent, + "{} fully linked ({})", + format_percent(measures.complete_data_percent), size(measures.complete_data) ), ); @@ -357,8 +360,8 @@ impl TemplateContext { if current_percent > 0.0 { "progress-section striped" } else { "progress-section" }, measures.matched_data_percent - current_percent, format!( - "{:.2}% perfect match ({})", - measures.matched_data_percent, + "{} perfect match ({})", + format_percent(measures.matched_data_percent), size(measures.matched_data) ), ); diff --git a/crates/web/src/handlers/project.rs b/crates/web/src/handlers/project.rs index 465d7ed..3cae20a 100644 --- a/crates/web/src/handlers/project.rs +++ b/crates/web/src/handlers/project.rs @@ -14,7 +14,7 @@ use decomp_dev_core::{ CachedReportFile, Commit, Platform, ProjectInfo, ProjectVisibility, ReportInner, project_visibility, }, - util::{UrlExt, size}, + util::{UrlExt, format_percent, size}, }; use maud::{DOCTYPE, Markup, html}; use objdiff_core::bindings::report::Measures; @@ -413,11 +413,11 @@ fn project_fragment( (size(ctx.measures.total_code)) " total code" } @else { - (format!("{:.2}%", ctx.measures.matched_code_percent)) + (format_percent(ctx.measures.matched_code_percent)) " decompiled" @if ctx.measures.complete_code_percent > 0.0 { " | " - (format!("{:.2}%", ctx.measures.complete_code_percent)) + (format_percent(ctx.measures.complete_code_percent)) " fully linked" } } diff --git a/crates/web/src/handlers/report.rs b/crates/web/src/handlers/report.rs index 237858c..406a7d2 100644 --- a/crates/web/src/handlers/report.rs +++ b/crates/web/src/handlers/report.rs @@ -11,7 +11,7 @@ use decomp_dev_auth::CurrentUser; use decomp_dev_core::{ AppError, FullUri, models::{FullReportFile, ProjectInfo, ProjectVisibility, project_visibility}, - util::{UrlExt, size}, + util::{UrlExt, format_percent, size}, }; use decomp_dev_images::{ badge, @@ -781,7 +781,7 @@ async fn render_report( link rel="next" href=(next_commit_path); } meta name="description" content=(format!("Decompilation progress report for {project_name}")); - meta property="og:title" content=(format!("{project_short_name_with_label} is {:.2}% decompiled", measures.matched_code_percent)); + meta property="og:title" content=(format!("{project_short_name_with_label} is {} decompiled", format_percent(measures.matched_code_percent))); meta property="og:description" content=(format!("Decompilation progress report for {project_name}")); meta property="og:image" content=(image_url); meta property="og:url" content=(canonical_url); @@ -861,9 +861,9 @@ async fn render_report( span.icon-github { " " } span.md { "Repository" } } - h3.report-title { (format!("{project_short_name_with_label} is {:.2}% decompiled", measures.matched_code_percent)) } + h3.report-title { (format!("{project_short_name_with_label} is {} decompiled", format_percent(measures.matched_code_percent))) } @if current_unit.is_none() && measures.complete_code_percent > 0.0 { - h4.muted { (format!("{:.2}% fully linked", measures.complete_code_percent)) } + h4.muted { (format!("{} fully linked", format_percent(measures.complete_code_percent))) } } @if let Some(source_file_url) = source_file_url { h4.muted { @@ -1079,7 +1079,7 @@ async fn render_history( (ctx.chunks("history", Load::Preload).await) link rel="canonical" href=(canonical_url); meta name="description" content=(format!("Decompilation progress history for {project_name}")); - meta property="og:title" content=(format!("{project_short_name_with_label} is {:.2}% decompiled", measures.matched_code_percent)); + meta property="og:title" content=(format!("{project_short_name_with_label} is {} decompiled", format_percent(measures.matched_code_percent))); meta property="og:description" content=(format!("Decompilation progress history for {project_name}")); meta property="og:image" content=(image_url); meta property="og:url" content=(canonical_url); diff --git a/js/history.ts b/js/history.ts index 0880cb1..76792a8 100644 --- a/js/history.ts +++ b/js/history.ts @@ -1,4 +1,5 @@ import uPlot from 'uplot'; +import { formatPercent } from './util'; const height = 400; const stroke = '#a9a9b3'; @@ -12,10 +13,7 @@ function percentValue( _seriesIdx: number, _idx: number | null, ) { - if (rawValue > 99.99 && rawValue < 100.0) { - rawValue = 99.99; - } - return rawValue == null ? '' : `${rawValue.toFixed(2)}%`; + return rawValue == null ? '' : formatPercent(rawValue); } function renderChart(id: string, data: ReportHistoryEntry[]) { diff --git a/js/treemap.ts b/js/treemap.ts index c0de719..10accfd 100644 --- a/js/treemap.ts +++ b/js/treemap.ts @@ -1,3 +1,5 @@ +import { formatPercent } from './util'; + const unitBounds = (unit: Unit, width: number, height: number) => { return { x: unit.x * width, @@ -64,13 +66,9 @@ const drawTooltip = ( ctx.textBaseline = 'middle'; const { x, y, w, h } = unitBounds(unit, width, height); - let percent = unit.fuzzy_match_percent; - if (percent > 99.99 && percent < 100.0) { - percent = 99.99; - } const text = ellipsize( ctx, - `${unit.name} • ${formatSize(unit.total_code)} • ${percent.toFixed(2)}%`, + `${unit.name} • ${formatSize(unit.total_code)} • ${formatPercent(unit.fuzzy_match_percent)}`, width, ); const m = ctx.measureText(text); diff --git a/js/util.ts b/js/util.ts new file mode 100644 index 0000000..9ad33bb --- /dev/null +++ b/js/util.ts @@ -0,0 +1,11 @@ +export function clamp(value: number, min: number, max: number) { + return Math.min(Math.max(value, min), max); +} + +// Formats a progress percentage as a string, and prevents partial matches from being rounded to 0.00% or 100.00%. +export function formatPercent(value: number) { + if (value !== 0.0 && value !== 100.0) { + value = clamp(value, 0.01, 99.99); + } + return `${value.toFixed(2)}%`; +}