<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Software Development &amp; Programming | RENOR &amp; Partners S.r.l.</title>
	<atom:link href="https://renor.it/en/blog/software-development-programming/feed/" rel="self" type="application/rss+xml" />
	<link>https://renor.it/en/blog/software-development-programming/</link>
	<description></description>
	<lastBuildDate>Mon, 29 Dec 2025 01:45:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>AI per la scrittura di articoli: scrivere senza delegare il pensiero</title>
		<link>https://renor.it/en/blog/software-development-programming/ai-per-la-scrittura-di-articoli/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Mon, 29 Dec 2025 01:10:36 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[ai writing]]></category>
		<category><![CDATA[content marketing]]></category>
		<category><![CDATA[artificial intelligence]]></category>
		<category><![CDATA[scrittura assistita]]></category>
		<category><![CDATA[seo editoriale]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[writerflow]]></category>
		<guid isPermaLink="false">https://renor.it/?p=2852</guid>

					<description><![CDATA[<p>Con la creazione dell&#8217;infrastruttura di RunAI in RENOR &#38; Partners, abbiamo sviluppato un plugin WordPress che aiuta gli autori ad aumentare la visibilità online pubblicando contenuti di qualità: WriterFlow.WriterFlow nasce proprio per un uso consapevole dell’AI per la scrittura di articoli, come supporto al pensiero umano e non come sua sostituzione. Chi si affida a [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/ai-per-la-scrittura-di-articoli/">AI per la scrittura di articoli: scrivere senza delegare il pensiero</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Con la creazione dell&#8217;infrastruttura di <strong><a href="https://runai.it">RunAI</a> </strong>in RENOR &amp; Partners, abbiamo sviluppato un plugin WordPress che aiuta gli autori ad aumentare la visibilità online pubblicando contenuti di qualità: <strong><a href="https://runai.it/writerflow">WriterFlow</a></strong>.<br>WriterFlow nasce proprio per un uso consapevole dell’AI per la scrittura di articoli, come supporto al pensiero umano e non come sua sostituzione.</p>



<p>Chi si affida a ChatGPT per la creazione automatica di contenuti rischia di rimanere indietro. La SEO richiede strategie complesse e non basta fare un copia e incolla, necessita di creatività e di contenuti pensati e scritti per il lettore. Gli articoli scritti interamente da ChatGPT presentano sempre queste caratteristiche:</p>



<ul class="wp-block-list">
<li>Sono in linea con la media</li>



<li>Non introducono esperienza reale</li>



<li>Non risolvono un problema meglio di altri</li>



<li>Sono facilmente riconoscibili anche senza l&#8217;utilizzo di un detector AI</li>



<li>Non tengono conto delle esigenze dell&#8217;utente.</li>
</ul>



<p>Proprio sull&#8217;ultimo punto vale la pena soffermarci per un secondo. </p>



<h2 class="wp-block-heading" id="h-ai-per-la-scrittura-di-articoli-scrivere-pensando-alle-persone-non-alla-seo">AI per la scrittura di articoli: scrivere pensando alle persone, non alla SEO</h2>



<p>Ti sembrerà assurdo ma gli algoritmi dei motori di ricerca tendono a preferire contenuti originali e di valore che risultino utili e interessanti per l&#8217;utente finale. Questo è ciò che posiziona meglio un sito web o un articolo nei risultati di ricerca. Abbiamo affrontato questo argomento anche in questo articolo: <a href="https://renor.it/blog/marketing-business-digitale/come-scrivere-un-articolo-di-blog/">come scrivere un articolo di blog</a>. </p>



<h2 class="wp-block-heading" id="h-writerflow-e-l-ai-per-la-scrittura-di-articoli-non-si-sostituiscono-all-umano">WriterFlow e l’AI per la scrittura di articoli non si sostituiscono all’umano</h2>



<p>Il plugin si avvale di un’intelligenza artificiale dedicata alla SEO, pensata come AI per la scrittura di articoli, capace di generare contenuti ottimizzati in base al ragionamento dell&#8217;utente, fornendo opzioni diverse, sinonimi e suggerimenti per migliorare la qualità della scrittura, rimanendo un semplice supporto alla stesura dell&#8217;idea e non sostituendosi all&#8217;autore.</p>



<p>Ad esempio potrei scrivere:<br><em>&#8220;WriterFlow è un plugin AI per WordPress che ti dà una mano a scrivere gli articoli&#8221;</em></p>



<p>Richiedendo un&#8217;alternativa, quello che ottengo grazie a WriterFlow è questo:</p>



<p>&#8220;<em>WriterFlow è un plugin per WordPress che utilizza l&#8217;intelligenza artificiale per supportare gli autori nella scrittura di articoli.<em>&#8220;</em></em></p>



<h2 class="wp-block-heading" id="h-ai-per-la-scrittura-di-articoli-e-selezione-di-titoli-efficaci">AI per la scrittura di articoli e selezione di titoli efficaci</h2>



<figure class="wp-block-image size-large"><img decoding="async" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.12.53.png" alt="AI per la scrittura di articoli con WriterFlow: suggerimenti automatici di titoli in WordPress" class="wp-image-2855"/><figcaption class="wp-element-caption">WriterFlow propone automaticamente idee per titoli in linea con categoria e argomento dell’articolo.</figcaption></figure>



<p>Per far crescere il proprio sito è indispensabile nutrirlo continuamente di nuovi contenuti. Google non fornisce un numero esatto di articoli da pubblicare ogni giorno, non c&#8217;è un vademecum ma è possibile affermare con buona probabilità che la frequenza di pubblicazione dipenda fortemente dal tipo di sito web. </p>



<p>Un piccolo/medio blog dovrebbe pubblicare non più di 2/3 articoli a settimana, un sito di News è chiaro che ne debba pubblicare molti di più. Ma per un blog aziendale o per un blog personale le idee si esauriscono velocemente. </p>



<p>Con WriterFlow trovare il titolo perfetto è facile. Basta specificare categoria e tag, poi lasciare che WriterFlow generi 10 idee per titoli in linea con l&#8217;argomento selezionato.</p>



<p>Tanto tempo risparmiato e contenuti sempre di qualità.</p>



<h2 class="wp-block-heading" id="h-dalla-scelta-all-azione">Dalla scelta all&#8217;azione</h2>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1416" height="780" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.32.57.png" alt="AI per la scrittura di articoli con WriterFlow: inserimento automatico del titolo in WordPress" class="wp-image-2858" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.32.57.png 1416w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.32.57-300x165.png 300w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.32.57-768x423.png 768w" sizes="(max-width: 1416px) 100vw, 1416px" /><figcaption class="wp-element-caption">Con un solo click, WriterFlow inserisce il titolo scelto direttamente nell’editor WordPress.</figcaption></figure>



<p>Basta un click sul titolo prescelto e viene automaticamente inserito. WriterFlow si integra perfettamente con il tuo workflow WordPress, semplificando la creazione di contenuti ottimizzati per la SEO.</p>



<p>Basta perdere tempo con la ricerca del titolo perfetto. È sufficiente fornire l&#8217;argomento e WriterFlow proporrà diverse opzioni. Se non trovate quello che cercate, basta cliccare un tasto per rigenerare nuove idee.</p>



<h2 class="wp-block-heading" id="h-scrittura-assistita-un-uso-consapevole-dell-ai-per-la-scrittura-di-articoli">Scrittura assistita: un uso consapevole dell’AI per la scrittura di articoli</h2>



<figure class="wp-block-image size-large"><img decoding="async" width="1414" height="632" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.37.11.png" alt="AI per la scrittura di articoli con WriterFlow: suggerimenti di testo durante la scrittura" class="wp-image-2859" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.37.11.png 1414w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.37.11-300x134.png 300w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.37.11-768x343.png 768w" sizes="(max-width: 1414px) 100vw, 1414px" /><figcaption class="wp-element-caption">WriterFlow propone suggerimenti di testo mentre scrivi, lasciando all’autore la libertà di accettarli o modificarli.</figcaption></figure>



<p>Nei suggerimenti di testo hai due modalità. Questo approccio rende l’AI per la scrittura di articoli uno strumento discreto e realmente utile. Accettare il testo proposto, oppure iniziare a scrivere per poi variare il contenuto con nuove idee che ti vengono in mente. </p>



<h3 class="wp-block-heading" id="h-accettazione-del-testo-proposto">Accettazione del testo proposto</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="1388" height="676" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.42.23.png" alt="AI per la scrittura di articoli con WriterFlow: inserimento del testo suggerito nei blocchi Gutenberg" class="wp-image-2861" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.42.23.png 1388w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.42.23-300x146.png 300w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.42.23-768x374.png 768w" sizes="(max-width: 1388px) 100vw, 1388px" /><figcaption class="wp-element-caption">Nei blocchi di paragrafo è possibile inserire il suggerimento di testo con un click o premendo TAB.</figcaption></figure>



<p>Nei blocchi di paragrafo di Gutenberg ti basta cliccare sul suggerimento per inserire tutta la frase suggerita. Oppure premere semplicemente il tasto &#8220;TAB&#8221;. </p>



<h2 class="wp-block-heading" id="h-frasi-alternative">Frasi alternative</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1396" height="766" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.44.49.png" alt="AI per la scrittura di articoli con WriterFlow: riformulazione di una frase selezionata" class="wp-image-2862" style="aspect-ratio:1.8224964404366397" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.44.49.png 1396w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.44.49-300x165.png 300w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.44.49-768x421.png 768w" sizes="auto, (max-width: 1396px) 100vw, 1396px" /><figcaption class="wp-element-caption">Selezionando una frase, WriterFlow genera alternative più coerenti e professionali.</figcaption></figure>



<p>Non sei soddisfatto di come hai scritto una frase e la vorresti riformulata in modo più professionale e aderente al resto dell&#8217;articolo? Nulla di più semplice. <br>Con WriterFlow attivo è sufficiente selezionare la frase che vuoi riformulare e verranno generate immediatamente delle alternative. Ti basta premere l&#8217;alternativa che preferisci oppure generare nuove alternative se quelle proposte non ti soddisfano. </p>



<h2 class="wp-block-heading" id="h-scegli-la-parola-perfetta">Scegli la parola perfetta</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1418" height="800" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.47.20.png" alt="AI per la scrittura di articoli con WriterFlow: suggerimento di sinonimi durante la revisione del testo" class="wp-image-2863" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.47.20.png 1418w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.47.20-300x169.png 300w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.47.20-768x433.png 768w" sizes="auto, (max-width: 1418px) 100vw, 1418px" /><figcaption class="wp-element-caption">Selezionando una parola, WriterFlow propone sinonimi alternativi per migliorare precisione e stile del testo.</figcaption></figure>



<p>Stai cercando un sinonimo che non ti viene? WriterFlow ti velocizza anche in questo, mostrando il lato più pratico dell’AI per la scrittura di articoli. Fai doppio click sulla parola di cui vuoi cercare un sinonimo in modo che sia evidenziata e ti verranno consigliati 5 sinonimi. Se i sinonimi proposti non ti convincono, puoi cliccare su &#8220;rigenera&#8221; per ottenere 5 nuove opzioni.  Scegli il sinonimo che preferisci e verrà inserito al posto della parola originale.</p>



<h2 class="wp-block-heading" id="h-modalita-automatica-o-manuale">Modalità automatica o manuale?</h2>



<h3 class="wp-block-heading" id="h-in-modalita-automatica-puoi-impostare-un-tempo-di-inattivita-per-ricevere-suggerimenti">In modalità automatica puoi impostare un tempo di inattività per ricevere suggerimenti</h3>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1424" height="786" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.50.28.png" alt="AI per la scrittura di articoli con WriterFlow: impostazioni della modalità automatica dei suggerimenti" class="wp-image-2864" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.50.28.png 1424w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.50.28-300x166.png 300w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.50.28-768x424.png 768w" sizes="auto, (max-width: 1424px) 100vw, 1424px" /><figcaption class="wp-element-caption">La modalità automatica consente di ricevere suggerimenti dopo un periodo di inattività configurabile.</figcaption></figure>



<p>La modalità automatica è molto comoda quando si vuole ricevere il suggerimento dopo un certo periodo di inattività. La durata del tempo di inattività è configurabile tramite le impostazioni di WriterFlow.</p>



<p>Con un tempo di 5 secondi, come illustrato nell&#8217;esempio, il suggerimento si attiverà dopo 5 secondi di stop alla scrittura.</p>



<h3 class="wp-block-heading" id="h-in-modalita-manuale-sei-tu-a-scegliere-quando-vuoi-ricevere-un-suggerimento">In modalità manuale sei tu a scegliere quando vuoi ricevere un suggerimento</h3>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1254" height="270" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.54.52.png" alt="Pulsante Suggerisci nella modalità manuale" class="wp-image-2865" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.54.52.png 1254w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.54.52-300x65.png 300w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.54.52-768x165.png 768w" sizes="auto, (max-width: 1254px) 100vw, 1254px" /><figcaption class="wp-element-caption">In modalità manuale, il pulsante “Suggerisci” compare accanto al cursore dopo alcuni secondi di inattività.</figcaption></figure>



<p>Nella modalità manuale, dopo tre secondi di stop alla scrittura comparirà il tasto suggerisci a destra del cursore. Ti basterà cliccare sul pulsante per ricevere i suggerimenti di scrittura. Nella modalità manuale, le funzioni di generazione alternative e di suggerimento sinonimico saranno ancora disponibili. Come nel caso della modalità automatica dovrai solamente selezionare la frase o la parola per ottenere rispettivamente il testo alternativo o il sinonimo desiderato.</p>



<h1 class="wp-block-heading" id="h-con-writerflow-paghi-solo-quello-che-usi">Con WriterFlow paghi solo quello che usi</h1>



<div class="wp-block-media-text is-stacked-on-mobile"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="478" height="422" src="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.58.40.png" alt="Visualizzazione del credito residuo nell’editor WordPress" class="wp-image-2866 size-full" srcset="https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.58.40.png 478w, https://renor.it/wp-content/uploads/2025/12/Screenshot-2025-12-29-alle-01.58.40-300x265.png 300w" sizes="auto, (max-width: 478px) 100vw, 478px" /></figure><div class="wp-block-media-text__content">
<p>Nel menu a destra dell&#8217;editor dell&#8217;articolo hai sempre modo di controllare il credito residuo. Ad ogni interazione il credito verrà aggiornato in modo automatico. Niente sorprese. </p>



<p>Inoltre i nuovi iscritti possono godere di un credito gratuito di 5 euro per testare le funzionalità di WriterFlow. I 5 euro di credito ti basteranno per almeno 15 articoli di media lunghezza. </p>
</div></div>



<p>Iscrizione e ricarica sono operazioni semplicissime. La registrazione e la creazione di una chiave API sono operazioni che avvengono in modo automatico direttamente dalla tua area amministrativa di WordPress. La ricarica richiede invece l&#8217;accesso al portale di RunAI e potrai ricaricare il tuo credito con un minimo di 5 euro. </p>



<p>RunAI utilizza transazioni sicure tramite l&#8217;introduzione dei gateway di pagamento PayPal e Stripe. È necessaria una carta di credito. Non ci sono costi ricorrenti, non ci sono piani in abbonamento. Ricarichi il tuo credito e utilizzi WriterFlow. Nulla di più semplice. </p>



<p>Puoi usare lo stesso account WriterFlow anche su più siti WordPress e il credito non ha scadenza. Se un mese sei in ferie e non stai lavorando non dovrai continuare a pagare per un servizio che non utilizzi. I costi sono puramente a consumo. </p>



<h2 class="wp-block-heading" id="h-privacy-e-data-retention">Privacy e data retention</h2>



<p>La privacy è garantita da <a href="https://runai.it">RunAI</a>, la nostra piattaforma di intelligenza artificiale con nodi di inferenza Apple Silicon criptati end-to-end. Nessun contenuto viene salvato, i dati vengono elaborati solo durante l&#8217;utilizzo del servizio e cancellati un microsecondo dopo averti fornito il suggerimento nel rispetto delle normative GDPR in modo trasparente e sicuro per gli utenti. </p>



<h2 class="wp-block-heading" id="h-conclusione">Conclusione</h2>



<p>WriterFlow è lo strumento innovativo che rivoluziona il modo di scrivere online, offrendo un uso consapevole dell’AI per la scrittura di articoli.</p>



<p>Scarica WriterFlow e inizia ad usarlo con il credito gratuito. Diventerà il tuo compagno di scrittura e ti aiuterà a creare contenuti di qualità.</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/ai-per-la-scrittura-di-articoli/">AI per la scrittura di articoli: scrivere senza delegare il pensiero</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>From Zero to Packagist: Publishing a PHP Library on Composer</title>
		<link>https://renor.it/en/blog/software-development-programming/from-zero-to-packagist-publishing-a-php-library-on-composer/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Mon, 12 May 2025 20:06:01 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[Composer]]></category>
		<category><![CDATA[dependency management]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[open-source]]></category>
		<category><![CDATA[package publishing]]></category>
		<category><![CDATA[Packagist]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[PHP library]]></category>
		<category><![CDATA[PHPUnit]]></category>
		<category><![CDATA[PSR-4]]></category>
		<category><![CDATA[release workflow]]></category>
		<category><![CDATA[semantic versioning]]></category>
		<guid isPermaLink="false">https://renor.it/from-zero-to-packagist-publishing-a-php-library-on-composer/</guid>

					<description><![CDATA[<p>Publishing a Composer package means making your code installable with a simple CLI command. composer require vendor/package and keeping updates, versions, and dependencies under control.To follow the guide, we need: Referring back to the article where we explored how to develop a descriptive statistics library, the repository structure was as follows: In the terminal, inside [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/from-zero-to-packagist-publishing-a-php-library-on-composer/">From Zero to Packagist: Publishing a PHP Library on Composer</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Publishing a Composer package means making your code installable with a simple CLI command.</p>

<p><code>composer require vendor/package</code></p>

<p>and keeping updates, versions, and dependencies under control.<br/>To follow the guide, we need:  </p>

<ul class="wp-block-list">
<li>Git version 2.0 or higher installed and configured</li>



<li>A GitHub account and a token with repo permission</li>



<li>Composer version 2.5 or higher installed <code>$PATH</code></li>



<li>A free account on Packagist.org (registered via GitHub using OAuth)</li>
</ul>

<p>Referring back to the article where we explored how to develop a <a href="https://renor.it/a-descriptive-statistics-class-in-php/?lang=en" data-type="link" data-id="https://renor.it/una-classe-di-statistica-descrittiva-in-php/">descriptive statistics library</a>, the repository structure was as follows:</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="descriptive-statistics-php/ ├─ composer.json ├─ composer.lock ├─ phpunit.xml ├─ examples/ │ └─ geometric_mean_demo.php │ └─ harmonic_mean_demo.php │ └─ iqr_demo.php │ └─ mad_demo.php │ └─ mean_demo.php │ └─ median_demo.php │ └─ min_max_demo.php │ └─ mode_demo.php │ └─ percentile_demo.php │ └─ range_demo.php │ └─ standard_deviation_demo.php │ └─ trimmed_mean_demo.php │ └─ variance_demo.php ├─ src/ │ └─ DescriptiveStats.php ├─ tests/ │ └─ DescriptiveStatsTest.php └─ vendor/ └─ autoload.php" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">descriptive-statistics-php/</span></span>
<span class="line"><span style="color: #D4D4D4">├─ composer</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">json</span></span>
<span class="line"><span style="color: #D4D4D4">├─ composer</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">lock</span></span>
<span class="line"><span style="color: #D4D4D4">├─ phpunit</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">xml</span></span>
<span class="line"><span style="color: #D4D4D4">├─ examples/</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ geometric_mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ harmonic_mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ iqr_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ mad_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ median_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ min_max_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ mode_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ percentile_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ range_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ standard_deviation_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ trimmed_mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ variance_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">├─ src/</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ DescriptiveStats</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">├─ tests/</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ DescriptiveStatsTest</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">└─ vendor/</span></span>
<span class="line"><span style="color: #D4D4D4">    └─ autoload</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span></code></pre></div>

<p>In the terminal, inside the project folder, we type the command: </p>

<p><code>composer init</code></p>

<p>Composer will ask us for some information, starting with the package name, which is expressed as vendor/package-name. In my case, since my account is named “thesimon82”, the package name will be <code>thesimon82/descriptive-statistics</code>. We confirm by pressing Enter.  </p>

<p>Now we are asked for the description—<br/>for this project, we’ll enter: <code>Lightweight PHP class for descriptive statistics</code>.</p>

<p>Next is the author field: if we’ve already configured it previously, Composer will show a default author; otherwise, we enter our name and surname manually. </p>

<p>Minimum stability: if it’s a stable release, we enter “stable”.</p>

<p>For Package Type, we enter: “library”.</p>

<p>In license: MIT</p>

<p>We are then asked whether we want to add, one by one, the production packages (require) that the library needs in order to work. In this case, the class is stand-alone (it only uses native PHP functions), so we don’t have any dependencies other than the minimum PHP version—therefore, we can safely answer “no”.<br/><br/><br/>If you’re thinking about PHPUnit for testing the class methods, remember that PHPUnit is only used during development and testing, so it should be placed in require-dev, not in require.<br/><br/><br/>This way, anyone installing the package in a production environment won’t have PHPUnit among their runtime dependencies.    </p>

<p>The next prompt, in fact, asks whether we intend to define our development dependencies interactively (require-dev). Here, we type “yes”, and Composer will then prompt us to enter the name of the package.<br/><br/><br/>We now type:<br/><br/><br/><code>phpunit/phpunit</code><br/><br/><br/>Composer will return a list of matching packages—select phpunit/phpunit by entering the corresponding number.<br/><br/><br/>Then it will ask which version to include.<br/>We simply press Enter to install the latest available version.    </p>

<p>It will now ask if we want to install any additional dependency packages, but since we don’t have any others, we simply press Enter. <br/>It will now ask if we want to add a PSR-4 autoload mapping for the classes, suggesting to map the namespace Thesimon82\DescriptiveStatistics.<br/>However, in our project we used Renor\\Statistics, so we type n to skip the proposal.</p>

<p>Finally, we obtain a summary of the entered data:</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{ "name": "thesimon82/descriptive-statistics", "description": "Lightweight PHP class for descriptive statistics", "type": "library", "require-dev": { "phpunit/phpunit": "^12.1" }, "license": "MIT", "authors": [ { "name": "Simone Renzi", "email": "info@simonerenzi.com" } ], "minimum-stability": "stable", "require": {}, "autoload": { "psr-4": { "Renor\\Statistics\\": "src/" } } } Do you confirm generation [yes]?  " style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    "name": "thesimon82/descriptive-statistics",</span></span>
<span class="line"><span style="color: #D4D4D4">    "description": "Lightweight PHP class for descriptive statistics",</span></span>
<span class="line"><span style="color: #D4D4D4">    "type": "library",</span></span>
<span class="line"><span style="color: #D4D4D4">    "require-dev": {</span></span>
<span class="line"><span style="color: #D4D4D4">        "phpunit/phpunit": "^12.1"</span></span>
<span class="line"><span style="color: #D4D4D4">    },</span></span>
<span class="line"><span style="color: #D4D4D4">    "license": "MIT",</span></span>
<span class="line"><span style="color: #D4D4D4">    "authors": [</span></span>
<span class="line"><span style="color: #D4D4D4">        {</span></span>
<span class="line"><span style="color: #D4D4D4">            "name": "Simone Renzi",</span></span>
<span class="line"><span style="color: #D4D4D4">            "email": "info@simonerenzi.com"</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    ],</span></span>
<span class="line"><span style="color: #D4D4D4">    "minimum-stability": "stable",</span></span>
<span class="line"><span style="color: #D4D4D4">    "require": {},</span></span>
<span class="line"><span style="color: #D4D4D4">    "autoload": {</span></span>
<span class="line"><span style="color: #D4D4D4">        "psr-4": {</span></span>
<span class="line"><span style="color: #D4D4D4">            "Renor\\Statistics\\": "src/"</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">Do you confirm generation [yes]? </span></span></code></pre></div>

<p>Here we review the data, and if everything is correct, we confirm with “yes” and proceed to install the dependencies.</p>

<p>At the end, we type the command:</p>

<p><code>composer dump-autoload</code> to regenerate the class autoloader.</p>

<p>It’s time to make the commit using the following series of commands: </p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="git add . git commit -m "feat: first stable release" git tag v1.0.0 git push origin main v1.0.0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">git add .</span></span>
<span class="line"><span style="color: #D4D4D4">git commit -m "feat: first stable release"</span></span>
<span class="line"><span style="color: #D4D4D4">git tag v1.0.0</span></span>
<span class="line"><span style="color: #D4D4D4">git push origin main v1.0.0</span></span></code></pre></div>

<p>Now we move to GitHub, and we should see our repository with the latest commit—<br/>in my case: thesimon82 feat: first stable release.</p>

<p>We copy the URL from the address bar at the top and head over to Packagist.</p>

<p>Click on “Submit” at the top, then paste the GitHub link you copied into the text field. Press the Check button.<br/>At this point, Packagist will analyze the composer.json file and immediately display the package details.<br/>Clicking the “Submit” button at the bottom will create the package.    </p>

<p>We’re done—now it’s time to test if everything works correctly.</p>

<p>Create a new folder named “Stats Test”, then open the terminal and navigate into that folder using: <br/>Here we type:</p>

<p><code>composer require thesimon82/descriptive-statistics</code></p>

<p>Now import this folder into your development environment and create a new file in the root directory named: test.php.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;encoded_tag_open /&gt;?php require_once 'vendor/autoload.php'; use Renor\Statistics\DescriptiveStats; $stats = new DescriptiveStats([1, 2, 3, 4, 5,6.22]); echo $stats-&lt;encoded_tag_closed /&gt;mean();" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">&lt;?php</span></span>
<span class="line"><span style="color: #C586C0">require_once</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">'vendor/autoload.php'</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #569CD6">use</span><span style="color: #D4D4D4"> Renor\Statistics\</span><span style="color: #4EC9B0">DescriptiveStats</span><span style="color: #D4D4D4">;</span></span>
<span class="line"/>
<span class="line"><span style="color: #9CDCFE">$stats</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">DescriptiveStats</span><span style="color: #D4D4D4">([</span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">3</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">4</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">5</span><span style="color: #D4D4D4">,</span><span style="color: #B5CEA8">6.22</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"><span style="color: #DCDCAA">echo</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$stats</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">mean</span><span style="color: #D4D4D4">();</span></span></code></pre></div>

<p>As we can see, we’re including the Composer-generated autoloader, which makes all classes mapped via PSR-4 available automatically—without the need for additional require statements. </p>

<p>We define the namespace used in the DescriptiveStats class and instantiate a new object with a numeric dataset.<br/>You’ll recall that the class constructor automatically filters out non-numeric values, reindexes the array keys, and throws an exception if the resulting dataset is empty.<br/><br/><br/>Then, the method <code>mean()</code>is called, which computes the mean and displays it on the screen.   </p>

<p>If you now run the command: <code>php test.php</code> from the terminal, and if everything was done correctly, you should see the result: <br/>3.5366666666667,<br/>which is precisely the approximate mean of the values we passed to the class constructor. </p>

<h2 class="wp-block-heading">Conclusions</h2>

<p>We’ve seen how to transform a simple PHP class into a Composer package distributed on Packagist.<br/>Now the library can be installed with a single command, benefits from semantic versioning, continuous integration, and automation that updates the Packagist listing automatically with every new tag.  </p>

<p>From this point on, the lifecycle is straightforward:<br/>you implement a feature, run the tests, increment the version, create the tag, and push it to GitHub.<br/>Packagist and Composer will take care of the rest! </p>

<p>[starbox]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/from-zero-to-packagist-publishing-a-php-library-on-composer/">From Zero to Packagist: Publishing a PHP Library on Composer</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>A Descriptive Statistics Class in PHP</title>
		<link>https://renor.it/en/blog/software-development-programming/a-descriptive-statistics-class-in-php/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Mon, 12 May 2025 19:06:41 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[applied mathematics]]></category>
		<category><![CDATA[arithmetic mean]]></category>
		<category><![CDATA[Composer]]></category>
		<category><![CDATA[data analysis]]></category>
		<category><![CDATA[descriptive statistics]]></category>
		<category><![CDATA[IQR]]></category>
		<category><![CDATA[mean absolute deviation]]></category>
		<category><![CDATA[median]]></category>
		<category><![CDATA[mode]]></category>
		<category><![CDATA[Packagist]]></category>
		<category><![CDATA[percentiles]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[PHP library]]></category>
		<category><![CDATA[PHP tutorial]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[standard deviation]]></category>
		<category><![CDATA[standard error]]></category>
		<category><![CDATA[variance]]></category>
		<guid isPermaLink="false">https://renor.it/a-descriptive-statistics-class-in-php/</guid>

					<description><![CDATA[<p>In the world of software development, data analysis is becoming increasingly central. With the advent of artificial intelligence in the consumer market, many now use it to analyze statistical data as well. However, in many cases, the use of this technology is not required and is actually less efficient than a solid algorithm designed to [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/a-descriptive-statistics-class-in-php/">A Descriptive Statistics Class in PHP</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In the world of software development, data analysis is becoming increasingly central. With the advent of artificial intelligence in the consumer market, many now use it to analyze statistical data as well. However, in many cases, the use of this technology is not required and is actually less efficient than a solid algorithm designed to take input data and return output data in a “black-box” manner.  </p>

<p>Artificial intelligence is now seen by many entrepreneurs as a trend, but it should only be used where it truly adds value. Speaking of efficiency, using AI to calculate a mean, a median, or a mode is not only like using a bazooka to kill a couple of flies, but it can also turn out to be slower, considering the inference times, compared to a finite state algorithm.  </p>

<p>Even in contexts that are not strictly scientific or academic, knowing the fundamentals of descriptive statistics can prove extremely useful: from generating automated reports, to monitoring an application’s performance, and even to building dashboards or analytical tools. </p>

<p>In this article, we will build a PHP class that calculates the main descriptive statistical indicators—such as mean, median, mode, variance, standard deviation, quartiles, and more—from scratch, without relying on external libraries. The code will be clean, reusable, and ready to be transformed into a Composer package, which will be the subject of a follow-up article. </p>

<h2 class="wp-block-heading">Designing a simple yet extensible class</h2>

<p>To build a useful and easily integrable descriptive statistics class, we will adopt an object-oriented structure aligned with SOLID principles, avoiding external dependencies and ensuring the possibility of future extension (e.g., support for associative datasets or reading from CSV files). </p>

<p>The class will be designed to operate on arrays of numerical values and efficiently compute the main statistical indicators. The goal is to provide a simple interface.  </p>

<p>First, let’s create a new repository on GitHub, which you can find at the following link:<br/><a href="https://github.com/thesimon82/descriptive-statistics-php">https://github.com/thesimon82/descriptive-statistics-php</a></p>

<p>Let’s start implementing the methods.</p>

<p>The project structure should be as follows: </p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="descriptive-statistics-php/ ├─ composer.json ├─ composer.lock ├─ phpunit.xml ├─ examples/ │ └─ geometric_mean_demo.php │ └─ harmonic_mean_demo.php │ └─ iqr_demo.php │ └─ mad_demo.php │ └─ mean_demo.php │ └─ median_demo.php │ └─ min_max_demo.php │ └─ mode_demo.php │ └─ percentile_demo.php │ └─ range_demo.php │ └─ standard_deviation_demo.php │ └─ trimmed_mean_demo.php │ └─ variance_demo.php ├─ src/ │ └─ DescriptiveStats.php ├─ tests/ │ └─ DescriptiveStatsTest.php └─ vendor/ └─ autoload.php" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">descriptive-statistics-php/</span></span>
<span class="line"><span style="color: #D4D4D4">├─ composer</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">json</span></span>
<span class="line"><span style="color: #D4D4D4">├─ composer</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">lock</span></span>
<span class="line"><span style="color: #D4D4D4">├─ phpunit</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">xml</span></span>
<span class="line"><span style="color: #D4D4D4">├─ examples/</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ geometric_mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ harmonic_mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ iqr_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ mad_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ median_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ min_max_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ mode_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ percentile_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ range_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ standard_deviation_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ trimmed_mean_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ variance_demo</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">├─ src/</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ DescriptiveStats</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">├─ tests/</span></span>
<span class="line"><span style="color: #D4D4D4">│  └─ DescriptiveStatsTest</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span>
<span class="line"><span style="color: #D4D4D4">└─ vendor/</span></span>
<span class="line"><span style="color: #D4D4D4">    └─ autoload</span><span style="color: #D4D4D4">.</span><span style="color: #D4D4D4">php</span></span></code></pre></div>

<p>As you can see, I have also included a folder for adding tests on the class methods (optional) using PHPUnit. The phpunit.xml file defines the test folder and the PHPUnit sources, which must be installed via Composer from the Packagist repository <code>phpunit/phpunit</code>, adding it as a development dependency using the command <code>composer require --dev phpunit/phpunit ^10</code>. </p>

<h2 class="wp-block-heading">Mean</h2>

<p>The <strong>arithmetic mean</strong> (or average) of a set of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> numerical observations <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-5e8319f54f72e7dac2238515c1499b27_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#49;&#44;&#32;&#120;&#95;&#50;&#44;&#32;&#92;&#100;&#111;&#116;&#115;&#44;&#32;&#120;&#95;&#110;" title="Rendered by QuickLaTeX.com" height="12" width="101" style="vertical-align: -4px;"/> is obtained by summing all the values and dividing the result by the total number of observations. Formally, it is written as: </p>

<p><br/><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-6e56531130e72a8ea51283096d7d0308_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#98;&#97;&#114;&#123;&#120;&#125;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#110;&#125;&#32;&#92;&#115;&#117;&#109;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#32;&#120;&#95;&#105;" title="Rendered by QuickLaTeX.com" height="22" width="119" style="vertical-align: -6px;"/></p>

<p><br/>where <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-20ebc7ea21d727ebe3840c06a433ab09_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#98;&#97;&#114;&#123;&#120;&#125;" title="Rendered by QuickLaTeX.com" height="11" width="10" style="vertical-align: 0px;"/> represents the arithmetic mean, <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> is the sample size, and <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3753926091c62342a46ec13bbebace46_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#115;&#117;&#109;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#32;&#120;&#95;&#105;" title="Rendered by QuickLaTeX.com" height="19" width="60" style="vertical-align: -5px;"/> indicates the sum of all observations. This measure provides a concise indication of the central tendency of the data, although it is sensitive to the presence of outliers, which can cause it to deviate significantly from the actual “center” of the distribution. </p>

<p>Let’s implement this method in our class. <code>DescriptiveStats.php</code></p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;encoded_tag_open /&gt;?php declare(strict_types=1); namespace Renor\Statistics; /** * Class DescriptiveStats * * A lightweight class to perform basic descriptive statistics on numeric datasets. */ class DescriptiveStats { /** * @var float[] Filtered and normalized numeric dataset. */ private array $data; /** * Main constructor. * * @param array $data An array containing the numeric values to be analyzed. * @throws \InvalidArgumentException If the array is empty or contains no numeric values. */ public function __construct(array $data) { // Filter only numeric values (int or float) and reset array keys $filtered = array_filter($data, 'is_numeric'); $this-&lt;encoded_tag_closed /&gt;data = array_values($filtered); if (count($this-&lt;encoded_tag_closed /&gt;data) === 0) { throw new \InvalidArgumentException('The dataset must contain at least one numeric value.'); } } /** * Calculates the arithmetic mean of the dataset. * * @return float The arithmetic mean. */ public function mean(): float { return array_sum($this-&lt;encoded_tag_closed /&gt;data) / count($this-&lt;encoded_tag_closed /&gt;data); } }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">&lt;?php</span></span>
<span class="line"/>
<span class="line"><span style="color: #C586C0">declare</span><span style="color: #D4D4D4">(strict_types=</span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #569CD6">namespace</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Renor\Statistics</span><span style="color: #D4D4D4">;</span></span>
<span class="line"/>
<span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Class DescriptiveStats</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * A lightweight class to perform basic descriptive statistics on numeric datasets.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">DescriptiveStats</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955">     * </span><span style="color: #569CD6">@var</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float[]</span><span style="color: #6A9955"> Filtered and normalized numeric dataset.</span></span>
<span class="line"><span style="color: #6A9955">     */</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">array</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$data</span><span style="color: #D4D4D4">;</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955">     * Main constructor.</span></span>
<span class="line"><span style="color: #6A9955">     *</span></span>
<span class="line"><span style="color: #6A9955">     * </span><span style="color: #569CD6">@param</span><span style="color: #6A9955"> </span><span style="color: #569CD6">array</span><span style="color: #6A9955"> $data An array containing the numeric values to be analyzed.</span></span>
<span class="line"><span style="color: #6A9955">     * </span><span style="color: #569CD6">@throws</span><span style="color: #6A9955"> </span><span style="color: #4EC9B0">\InvalidArgumentException</span><span style="color: #6A9955"> If the array is empty or contains no numeric values.</span></span>
<span class="line"><span style="color: #6A9955">     */</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">__construct</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">array</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$data</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">    {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// Filter only numeric values (int or float) and reset array keys</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$filtered</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_filter</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$data</span><span style="color: #D4D4D4">, </span><span style="color: #CE9178">'is_numeric'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_values</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$filtered</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">) === </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\InvalidArgumentException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'The dataset must contain at least one numeric value.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955">     * Calculates the arithmetic mean of the dataset.</span></span>
<span class="line"><span style="color: #6A9955">     *</span></span>
<span class="line"><span style="color: #6A9955">     * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The arithmetic mean.</span></span>
<span class="line"><span style="color: #6A9955">     */</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">mean</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">    {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">array_sum</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">) / </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p>As you can see, we have declared strict_types. This directive enforces PHP to avoid automatic type coercion, which is essential when we want full control over data types.   </p>

<p>Since we will accept the data as an array of values, we declare a private property of the class <code>private array $data</code>.</p>

<p>In the class constructor, we handle retrieving the data passed when instantiating the class to create a new object, verifying that the contents of the array <code>$data</code> are numeric, and we place the values inside the property <code>$this-&gt;data</code>.</p>

<p>In the <code>mean()</code> method, we implement the mathematical formula for the mean: we sum all the elements of the passed array and divide by the number of elements, returning the result. </p>

<h2 class="wp-block-heading">Median</h2>

<p>The median represents the central value of an ordered set of observations and divides the sample into two halves of equal size. Given a series of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> values <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-5e8319f54f72e7dac2238515c1499b27_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#49;&#44;&#32;&#120;&#95;&#50;&#44;&#32;&#92;&#100;&#111;&#116;&#115;&#44;&#32;&#120;&#95;&#110;" title="Rendered by QuickLaTeX.com" height="12" width="101" style="vertical-align: -4px;"/> such that <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-a8df85ae09e9285340b9227eddc7d1ab_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#49;&#32;&#92;&#108;&#101;&#32;&#120;&#95;&#50;&#32;&#92;&#108;&#101;&#32;&#92;&#100;&#111;&#116;&#115;&#32;&#92;&#108;&#101;&#32;&#120;&#95;&#110;" title="Rendered by QuickLaTeX.com" height="15" width="145" style="vertical-align: -3px;"/>: </p>

<ul class="wp-block-list">
<li>If <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> is odd, the median is simply the element in position <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-1450a1ab44bcfe30d8b103b06e9cd5bd_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#102;&#114;&#97;&#99;&#123;&#110;&#43;&#49;&#125;&#123;&#50;&#125;" title="Rendered by QuickLaTeX.com" height="22" width="26" style="vertical-align: -6px;"/>: <br/><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-74af485cea698cf57cdd7075ad9caf15_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#77;&#101;&#100;&#105;&#97;&#110;&#97;&#125;&#32;&#61;&#32;&#120;&#95;&#123;&#92;&#102;&#114;&#97;&#99;&#123;&#110;&#43;&#49;&#125;&#123;&#50;&#125;&#125;" title="Rendered by QuickLaTeX.com" height="23" width="125" style="vertical-align: -11px;"/></li>



<li>If <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> is even, the median is the arithmetic mean of the two central elements: <br/><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-91f6e57fe8ee5c52baa255e5225e1cd1_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#77;&#101;&#100;&#105;&#97;&#110;&#97;&#125;&#32;&#61;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#120;&#95;&#123;&#92;&#102;&#114;&#97;&#99;&#123;&#110;&#125;&#123;&#50;&#125;&#125;&#32;&#43;&#32;&#120;&#95;&#123;&#92;&#102;&#114;&#97;&#99;&#123;&#110;&#125;&#123;&#50;&#125;&#43;&#49;&#125;&#125;&#123;&#50;&#125;" title="Rendered by QuickLaTeX.com" height="28" width="158" style="vertical-align: -6px;"/></li>
</ul>

<p>Unlike the arithmetic mean, the median is robust to outliers, as it depends only on the ordering of the data and not on their magnitude. </p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="  /** * Calculates the median (50th percentile) of the dataset. * * @return float The median value. */ public function median(): float { // Clone and sort the dataset to avoid mutating the original array $sorted = $this-&lt;encoded_tag_closed /&gt;data; sort($sorted, SORT_NUMERIC); $count = count($sorted); $mid = intdiv($count, 2); // If the count is odd, return the middle value if ($count % 2 === 1) { return (float) $sorted[$mid]; } // If even, return the average of the two central values return ($sorted[$mid - 1] + $sorted[$mid]) / 2.0; }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4"> </span><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955">     * Calculates the median (50th percentile) of the dataset.</span></span>
<span class="line"><span style="color: #6A9955">     *</span></span>
<span class="line"><span style="color: #6A9955">     * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The median value.</span></span>
<span class="line"><span style="color: #6A9955">     */</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">median</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">    {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// Clone and sort the dataset to avoid mutating the original array</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">sort</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">, SORT_NUMERIC);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">   = </span><span style="color: #DCDCAA">intdiv</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// If the count is odd, return the middle value</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> % </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">];</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// If even, return the average of the two central values</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4"> - </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">] + </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">]) / </span><span style="color: #B5CEA8">2.0</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span></code></pre></div>

<p>In this method, I retrieve the data and sort it. We get the size and calculate the central index using integer division. For example, if we have 7 elements, <code>intdiv(7,2)</code> returns 3, which corresponds to the fourth position (remember that arrays in PHP are zero-based!). If the number of values is odd, there is a single perfectly central element, and it is returned. In the case of an even number of values, however, there is not just one central value but two: the ones in positions <code>$mid - 1</code> and <code>$mid</code>. By definition, the median is the arithmetic mean of these two values. We therefore sum the central elements and divide by 2.0 (using decimal notation to force floating-point division), which returns a float result. This way, the method returns the fiftieth percentile of the sample without modifying the original dataset and in accordance with the statistical definition for both odd and even-length series.        </p>

<h2 class="wp-block-heading">Mode</h2>

<p>After the mean and the median, the third most commonly used indicator in descriptive statistics is the mode, that is, the value or values that occur most frequently within a set of observations. If we denote by <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-a7ee323bc5a3f73ad5e066b13bed5504_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#102;&#40;&#120;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="34" style="vertical-align: -5px;"/> the absolute frequency of a value <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-ede05c264bba0eda080918aaa09c4658_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;" title="Rendered by QuickLaTeX.com" height="8" width="10" style="vertical-align: 0px;"/> in the sample, the mode is obtained as: </p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-2fa8242aeb4fa25a67e1f100b8cdf195_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#77;&#111;&#100;&#97;&#125;&#32;&#61;&#32;&#92;&#111;&#112;&#101;&#114;&#97;&#116;&#111;&#114;&#110;&#97;&#109;&#101;&#42;&#123;&#97;&#114;&#103;&#92;&#44;&#109;&#97;&#120;&#125;&#95;&#123;&#120;&#125;&#92;&#59;&#32;&#102;&#40;&#120;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="179" style="vertical-align: -5px;"/>.</p>

<p>We can distinguish, based on the input data series:</p>

<ul class="wp-block-list">
<li>Unimodal series when only one value has the highest frequency</li>



<li>Multimodal series when two or more values share the highest frequency</li>



<li>Series without a mode when all the values in the series appear only once each</li>
</ul>

<p>The mode is particularly useful when the data are categorical or when one wants to highlight concentration around certain integer values. Unlike the mean and the median, it does not measure central tendency but rather the value or values that occur most frequently in the series. It is no coincidence that when a clothing item is “in fashion” it is because, compared to other items in a sales series, it turns out to be the most sold and therefore “the one most in fashion.”   </p>

<p>Let’s therefore add the method to our class:</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Returns the mode(s) of the dataset. * * If the dataset is multimodal, an array with all modal values is returned. * If every value occurs only once, an empty array is returned (no mode). * * @return float[] list of modal values */ public function mode(): array { // Build a frequency table: value =&lt;encoded_tag_closed /&gt; occurrences $frequencies = array_count_values($this-&lt;encoded_tag_closed /&gt;data); // Determine the highest frequency $maxFrequency = max($frequencies); // If every value appears only once, there is no mode if ($maxFrequency === 1) { return []; } // Collect all values that share the highest frequency $modes = []; foreach ($frequencies as $value =&lt;encoded_tag_closed /&gt; $count) { if ($count === $maxFrequency) { // Cast to float so that return type is consistent $modes[] = (float) $value; } } sort($modes, SORT_NUMERIC); // return modes in ascending order return $modes; }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Returns the mode(s) of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * If the dataset is multimodal, an array with all modal values is returned.</span></span>
<span class="line"><span style="color: #6A9955"> * If every value occurs only once, an empty array is returned (no mode).</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float[]</span><span style="color: #6A9955"> list of modal values</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">mode</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">array</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Build a frequency table: value =&gt; occurrences</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$frequencies</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_count_values</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Determine the highest frequency</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$maxFrequency</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">max</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$frequencies</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// If every value appears only once, there is no mode</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$maxFrequency</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> [];</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Collect all values that share the highest frequency</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$modes</span><span style="color: #D4D4D4"> = [];</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">foreach</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$frequencies</span><span style="color: #D4D4D4"> as </span><span style="color: #9CDCFE">$value</span><span style="color: #D4D4D4"> =&gt; </span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> === </span><span style="color: #9CDCFE">$maxFrequency</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #6A9955">// Cast to float so that return type is consistent</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">$modes</span><span style="color: #D4D4D4">[] = (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">$value</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">sort</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$modes</span><span style="color: #D4D4D4">, SORT_NUMERIC); </span><span style="color: #6A9955">// return modes in ascending order</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$modes</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/><br/><code>array_count_values()</code> It scans the entire dataset and returns an associative array where the <strong>key</strong> is the observed value and the <strong>value</strong> is the number of times it occurs. This is a quick way to calculate <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-a7ee323bc5a3f73ad5e066b13bed5504_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#102;&#40;&#120;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="34" style="vertical-align: -5px;"/> for each <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-ede05c264bba0eda080918aaa09c4658_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;" title="Rendered by QuickLaTeX.com" height="8" width="10" style="vertical-align: 0px;"/>.<br/>With <code>max($frequencies)</code>, we obtain the highest number of occurrences present in the table; this corresponds to the value of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-1ccf759ba917b35db6276661cda96893_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#109;&#97;&#120;&#32;&#102;&#40;&#120;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="70" style="vertical-align: -5px;"/> in the mathematical definition.<br/>If the maximum frequency is 1, it means that each observation is unique. In this case, the method returns an empty array to indicate that no modal value exists.<br/>We iterate over the frequency table: for each key whose count equals the maximum frequency, we add that value (cast to float) to the array <code>$modes</code>. In this way, <strong>all</strong> modal values are included in the case of multimodality.<br/>Before returning the result, <code>sort($modes, SORT_NUMERIC)</code> ensures that the modes are sorted in ascending order, making the output more predictable.   </p>

<p>In this way, we have covered all possible scenarios: unimodal, multimodal, and without a mode. </p>

<h2 class="wp-block-heading">Geometric mean</h2>

<p>The <strong>geometric mean</strong> is the most appropriate measure of central tendency when the data represent <em>growth rates</em> or <em>ratios</em> (for example, percentage returns, variation indices, logarithmic scales).</p>

<p>Given a series of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> <strong>strictly positive values</strong> <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-5e8319f54f72e7dac2238515c1499b27_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#49;&#44;&#32;&#120;&#95;&#50;&#44;&#32;&#92;&#100;&#111;&#116;&#115;&#44;&#32;&#120;&#95;&#110;" title="Rendered by QuickLaTeX.com" height="12" width="101" style="vertical-align: -4px;"/>, the geometric mean <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-30a79c32f18567063fe44716929e7ced_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#71;" title="Rendered by QuickLaTeX.com" height="12" width="14" style="vertical-align: 0px;"/> is defined as:</p>

<p><br/><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-a4742ffe66183a651acae6a09c623c28_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#71;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#66;&#105;&#103;&#108;&#40;&#32;&#92;&#112;&#114;&#111;&#100;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#32;&#120;&#95;&#105;&#32;&#92;&#66;&#105;&#103;&#114;&#41;&#94;&#123;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#110;&#125;&#125;" title="Rendered by QuickLaTeX.com" height="39" width="137" style="vertical-align: -11px;"/></p>

<p>or, in a numerically more stable logarithmic form:</p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-d61959aa5fbed45f159915591eb7e023_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#108;&#110;&#32;&#71;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#110;&#125;&#32;&#92;&#115;&#117;&#109;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#32;&#92;&#108;&#110;&#32;&#120;&#95;&#105;" title="Rendered by QuickLaTeX.com" height="22" width="158" style="vertical-align: -6px;"/>.</p>

<p><br/>This quantity corresponds to the “average” growth factor which, applied n times in sequence, yields the same result as the actual product of the values.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the geometric mean of the dataset. * * @throws \DomainException If any value is zero or negative. * @return float The geometric mean. */ public function geometricMean(): float { // The geometric mean is defined only for strictly positive numbers. foreach ($this-&lt;encoded_tag_closed /&gt;data as $value) { if ($value &lt;encoded_tag_open /&gt;= 0) { throw new \DomainException('Geometric mean requires all values to be greater than zero.'); } } // Use logarithms for numerical stability: exp( (1/n) * sum(log(x_i)) ) $logSum = array_sum(array_map('log', $this-&lt;encoded_tag_closed /&gt;data)); return exp($logSum / count($this-&lt;encoded_tag_closed /&gt;data)); }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the geometric mean of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@throws</span><span style="color: #6A9955"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #6A9955"> If any value is zero or negative.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The geometric mean.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">geometricMean</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// The geometric mean is defined only for strictly positive numbers.</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">foreach</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> as </span><span style="color: #9CDCFE">$value</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$value</span><span style="color: #D4D4D4"> &lt;= </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Geometric mean requires all values to be greater than zero.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Use logarithms for numerical stability: exp( (1/n) * sum(log(x_i)) )</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$logSum</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_sum</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">array_map</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'log'</span><span style="color: #D4D4D4">, </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">));</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">exp</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$logSum</span><span style="color: #D4D4D4"> / </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">));</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/><br/>Since the geometric mean is only defined for positive numbers, the method scans the dataset and throws a DomainException if it encounters values ≤ 0. This prevents mathematically incorrect (or complex) results.<br/>Instead of directly calculating the product (which could easily overflow), we convert each value using <code>log()</code>, sum the logarithms, and divide by <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/>. Based on the properties of logarithms:   <br/><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-43f802e186e975abe5f7cfa92e0d7040_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#108;&#110;&#92;&#33;&#92;&#66;&#105;&#103;&#108;&#40;&#92;&#112;&#114;&#111;&#100;&#32;&#120;&#95;&#105;&#92;&#66;&#105;&#103;&#114;&#41;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#115;&#117;&#109;&#32;&#92;&#108;&#110;&#32;&#120;&#95;&#105;" title="Rendered by QuickLaTeX.com" height="32" width="156" style="vertical-align: -11px;"/><br/><br/>By applying <code>exp()</code> to the average of the logarithms, we obtain the geometric mean.<br/><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-bfbde60fff002baebb36779f76f72332_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#71;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#101;&#120;&#112;&#92;&#33;&#92;&#66;&#105;&#103;&#108;&#40;&#92;&#116;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#110;&#125;&#92;&#115;&#117;&#109;&#32;&#92;&#108;&#110;&#32;&#120;&#95;&#105;&#92;&#66;&#105;&#103;&#114;&#41;" title="Rendered by QuickLaTeX.com" height="32" width="160" style="vertical-align: -11px;"/></p>

<h2 class="wp-block-heading">Harmonic mean</h2>

<p>The <strong>harmonic mean</strong> is the most appropriate measure of central tendency when the data represent <strong>speeds, ratios, or fractions</strong>—for example, kilometers per hour traveled at different paces, average cost per unit, or average return on investments calculated as “units per euro.”</p>

<p>Given a series of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> positive values <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-5e8319f54f72e7dac2238515c1499b27_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#49;&#44;&#32;&#120;&#95;&#50;&#44;&#32;&#92;&#100;&#111;&#116;&#115;&#44;&#32;&#120;&#95;&#110;" title="Rendered by QuickLaTeX.com" height="12" width="101" style="vertical-align: -4px;"/> (no value can be zero, as it would appear in the denominator), the harmonic mean <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-379db1fc1f84b7ce56b92463183097f9_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#72;" title="Rendered by QuickLaTeX.com" height="12" width="16" style="vertical-align: 0px;"/> is defined as:<br/><br/><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3d6ce62f9aca45ea9d0e009d4e105ee4_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#72;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#110;&#125;&#123;&#92;&#100;&#105;&#115;&#112;&#108;&#97;&#121;&#115;&#116;&#121;&#108;&#101;&#92;&#115;&#117;&#109;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#120;&#95;&#105;&#125;&#125;" title="Rendered by QuickLaTeX.com" height="60" width="101" style="vertical-align: -47px;"/></p>

<p>In other words, it is calculated as the <strong>inverse of the arithmetic mean</strong> of the reciprocals. Compared to the arithmetic mean, the harmonic mean gives <strong>more weight to smaller values</strong>: it is therefore valuable when one wishes to strongly penalize poorer performances (e.g., the average time to travel one kilometer over multiple segments at different speeds). </p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the harmonic mean of the dataset. * * @throws \DomainException If any value is zero or negative. * @return float The harmonic mean. */ public function harmonicMean(): float { // Harmonic mean is defined only for strictly positive numbers. foreach ($this-&lt;encoded_tag_closed /&gt;data as $value) { if ($value &lt;encoded_tag_open /&gt;= 0) { throw new \DomainException('Harmonic mean requires all values to be greater than zero.'); } } $inverseSum = array_sum(array_map( static fn (float $v): float =&lt;encoded_tag_closed /&gt; 1.0 / $v, $this-&lt;encoded_tag_closed /&gt;data )); return count($this-&lt;encoded_tag_closed /&gt;data) / $inverseSum; }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the harmonic mean of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@throws</span><span style="color: #6A9955"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #6A9955"> If any value is zero or negative.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The harmonic mean.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">harmonicMean</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Harmonic mean is defined only for strictly positive numbers.</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">foreach</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> as </span><span style="color: #9CDCFE">$value</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$value</span><span style="color: #D4D4D4"> &lt;= </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Harmonic mean requires all values to be greater than zero.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$inverseSum</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_sum</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">array_map</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">static</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">fn</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$v</span><span style="color: #D4D4D4">): </span><span style="color: #569CD6">float</span><span style="color: #D4D4D4"> =&gt; </span><span style="color: #B5CEA8">1.0</span><span style="color: #D4D4D4"> / </span><span style="color: #9CDCFE">$v</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span></span>
<span class="line"><span style="color: #D4D4D4">    ));</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">) / </span><span style="color: #9CDCFE">$inverseSum</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p>First, a domain check is performed: any value less than or equal to zero triggers a DomainException, otherwise the result would be undefined or become infinite. Then, a sum of the reciprocals is computed: <code>array_map()</code> calculates the reciprocal of each element, <code>array_sum()</code> sums them.<br/>The direct formula divides the number of observations <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> by the obtained sum, according to the mathematical definition.  </p>

<h2 class="wp-block-heading">Truncated mean (or trimmed mean)</h2>

<p>When a sample contains extreme <strong>outliers</strong> that risk distorting the arithmetic mean, an elegant solution is the <strong>truncated mean</strong> (or <em>trimmed mean</em>).</p>

<p>A percentage <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3bf85f1087e9fbed3a319341134ac1a2_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#112;" title="Rendered by QuickLaTeX.com" height="12" width="10" style="vertical-align: -4px;"/>% (typically 5% or 10%) is chosen, the sample is sorted, and the first <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: 0px;"/> smallest and the last <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: 0px;"/> largest observations are <strong>discarded</strong>, where:</p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-ee3395887d7d468db47e03e3b0a83719_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#98;&#97;&#114;&#123;&#120;&#125;&#123;&#92;&#116;&#101;&#120;&#116;&#123;&#116;&#114;&#105;&#109;&#125;&#125;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#92;&#44;&#110;&#45;&#50;&#107;&#92;&#44;&#125;&#32;&#92;&#115;&#117;&#109;&#123;&#105;&#61;&#107;&#43;&#49;&#125;&#94;&#123;&#92;&#44;&#110;&#45;&#107;&#125;&#32;&#120;&#95;&#123;&#40;&#105;&#41;&#125;&#32;" title="Rendered by QuickLaTeX.com" height="24" width="267" style="vertical-align: -8px;"/>.<br/><br/>This way, a measure of central tendency is obtained that is <strong>more robust</strong> than the arithmetic mean but less drastic than the median.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the trimmed mean of the dataset. * * @param float $percent Percentage (0–50) of data to trim at each tail. * @throws \DomainException If $percent is out of range or removes all data. * @return float The trimmed mean. */ public function trimmedMean(float $percent): float { if ($percent &lt;encoded_tag_open /&gt; 0.0 || $percent &lt;encoded_tag_closed /&gt;= 50.0) { throw new \DomainException('Percent must be in the range 0 &lt;encoded_tag_open /&gt;= p &lt;encoded_tag_open /&gt; 50.'); } $count = count($this-&lt;encoded_tag_closed /&gt;data); if ($count &lt;encoded_tag_open /&gt; 3) { // Too few values to trim meaningfully; fall back to arithmetic mean return $this-&lt;encoded_tag_closed /&gt;mean(); } // Clone and sort to preserve original order $sorted = $this-&lt;encoded_tag_closed /&gt;data; sort($sorted, SORT_NUMERIC); // Number of elements to trim from each end $k = (int) floor($count * $percent / 100.0); // Ensure at least one value remains if ($k * 2 &lt;encoded_tag_closed /&gt;= $count) { throw new \DomainException('Trim percentage removes all data.'); } $trimmed = array_slice($sorted, $k, $count - 2 * $k); return array_sum($trimmed) / count($trimmed); }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the trimmed mean of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@param</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> $percent Percentage (0–50) of data to trim at each tail.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@throws</span><span style="color: #6A9955"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #6A9955"> If $percent is out of range or removes all data.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The trimmed mean.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">trimmedMean</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$percent</span><span style="color: #D4D4D4">): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$percent</span><span style="color: #D4D4D4"> &lt; </span><span style="color: #B5CEA8">0.0</span><span style="color: #D4D4D4"> || </span><span style="color: #9CDCFE">$percent</span><span style="color: #D4D4D4"> &gt;= </span><span style="color: #B5CEA8">50.0</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Percent must be in the range 0 &lt;= p &lt; 50.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> &lt; </span><span style="color: #B5CEA8">3</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// Too few values to trim meaningfully; fall back to arithmetic mean</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">mean</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Clone and sort to preserve original order</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">sort</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">, SORT_NUMERIC);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Number of elements to trim from each end</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$k</span><span style="color: #D4D4D4"> = (</span><span style="color: #569CD6">int</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">floor</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> * </span><span style="color: #9CDCFE">$percent</span><span style="color: #D4D4D4"> / </span><span style="color: #B5CEA8">100.0</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Ensure at least one value remains</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$k</span><span style="color: #D4D4D4"> * </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> &gt;= </span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Trim percentage removes all data.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$trimmed</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_slice</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">$k</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> - </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> * </span><span style="color: #9CDCFE">$k</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">array_sum</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$trimmed</span><span style="color: #D4D4D4">) / </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$trimmed</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p>The method begins by verifying that the chosen trimming percentage makes sense: it must be greater than or equal to zero and strictly less than fifty, otherwise an exception is thrown; if, for example, we were to request the removal of 60% of the data from each tail, there would be nothing left to average.</p>

<p>If the sample contains fewer than three observations, the function considers trimming meaningless and simply returns the arithmetic mean: with two values, removing even one would eliminate half the data, while with only one there is nothing to trim.</p>

<p>The process then proceeds by <strong>cloning</strong> the original array and <strong>sorting</strong> it in ascending order; cloning preserves the order in which the data was provided to the object, while sorting is essential because trimming is applied starting from the extremes of the distribution.</p>

<p>The number of elements to discard at each tail, denoted by <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: 0px;"/>, is obtained by multiplying the sample size by the requested percentage and rounding down; for example, if we have ten values and want a 10% trimmed mean, we will remove one element from the beginning and one from the end.</p>

<p>Before proceeding, the method checks that “twice <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: 0px;"/>” is not equal to or greater than the length of the array: if it were, the trimming would remove all the data and the mean would no longer make sense; in such case, an additional exception is thrown.</p>

<p>Once this check is passed, <code>array_slice()</code> extracts the central portion that remains after discarding the <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: 0px;"/> smallest and <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: 0px;"/> largest values; the arithmetic mean is then calculated on this subset, which is precisely the desired <strong>trimmed mean</strong>.</p>

<p>The function thus returns a measure of central tendency that is more <strong>robust</strong> than the classic mean: the outliers, removed before the calculation, can no longer pull the result toward extreme values.</p>

<h2 class="wp-block-heading">Range</h2>

<p>The <strong>range</strong> is the simplest measure of dispersion: it indicates the total spread of the observed values, that is, the distance between the minimum and maximum extremes of the sample. If we denote </p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3b118489e45d1027030f5b15276e5196_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#123;&#92;&#109;&#105;&#110;&#125;&#32;&#61;&#32;&#92;&#109;&#105;&#110;&#40;&#120;&#95;&#49;&#44;&#92;&#100;&#111;&#116;&#115;&#44;&#120;&#95;&#110;&#41;&#32;&#92;&#113;&#117;&#97;&#100;&#92;&#116;&#101;&#120;&#116;&#123;&#101;&#125;&#92;&#113;&#117;&#97;&#100;&#32;&#120;&#95;&#123;&#92;&#109;&#97;&#120;&#125;&#32;&#61;&#32;&#92;&#109;&#97;&#120;&#40;&#120;&#95;&#49;&#44;&#92;&#100;&#111;&#116;&#115;&#44;&#120;&#95;&#110;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="403" style="vertical-align: -5px;"/>,</p>

<p>then the range <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-dae6bae3dcdac4629730754352c5e329_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#82;" title="Rendered by QuickLaTeX.com" height="12" width="14" style="vertical-align: 0px;"/> is defined as</p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-d399b72f8b0465415f1e24a72be4f993_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#82;&#32;&#61;&#32;&#120;&#95;&#123;&#92;&#109;&#97;&#120;&#125;&#32;&#92;&#59;&#45;&#92;&#59;&#32;&#120;&#95;&#123;&#92;&#109;&#105;&#110;&#125;" title="Rendered by QuickLaTeX.com" height="15" width="138" style="vertical-align: -3px;"/>.</p>

<p>Although it is sensitive to outliers (the same value that affects the maximum or minimum also affects the range), this measure provides an immediate indication of the distribution’s <strong>spread</strong> and is often reported alongside the mean or median to give a quick overview of overall dispersion.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the range (max – min) of the dataset. * * @return float The range of the data. */ public function range(): float { // min() e max() sono O(n) ma il dataset è già in memoria: soluzione lineare return max($this-&lt;encoded_tag_closed /&gt;data) - min($this-&lt;encoded_tag_closed /&gt;data); }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the range (max – min) of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The range of the data.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">range</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// min() e max() sono O(n) ma il dataset è già in memoria: soluzione lineare</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">max</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">) - </span><span style="color: #DCDCAA">min</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p>As can be seen, the method is very straightforward. It invokes the native PHP functions <code>max()</code> and <code>min()</code>, which each perform a single linear scan of the array to identify the largest and smallest values, respectively. Subtracting the minimum from the maximum yields the total spread of the sample; the result is returned as a float, consistent with the other methods in the class.   </p>

<h2 class="wp-block-heading">Quartiles and interquartile range (IQR)</h2>

<p>To describe the dispersion of a sample more robustly than with the simple range, <strong>quartiles</strong> are used:</p>

<ul class="wp-block-list">
<li><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-6b6b12b50fe43a209ecce557539ee185_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#49;" title="Rendered by QuickLaTeX.com" height="16" width="20" style="vertical-align: -4px;"/> – 25th percentile (first quartile)</li>



<li><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-7a8f7f7bc05736504761c873bfd99aa1_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#50;" title="Rendered by QuickLaTeX.com" height="16" width="21" style="vertical-align: -4px;"/> – 50th percentile (median)</li>



<li><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-6cef68a5946963445c5d51e5093bd228_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#51;" title="Rendered by QuickLaTeX.com" height="16" width="21" style="vertical-align: -4px;"/> – 75th percentile (third quartile)</li>
</ul>

<p>The interquartile range is defined as <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b7e8e27ef6cdf0eb6f547ea71a0510cb_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#73;&#81;&#82;&#125;&#32;&#61;&#32;&#81;&#95;&#51;&#32;&#45;&#32;&#81;&#95;&#49;" title="Rendered by QuickLaTeX.com" height="16" width="120" style="vertical-align: -4px;"/> and represents the spread of the central half of the data. It is not very sensitive to outliers because it is based only on the values between the 25th and 75th percentiles of the distribution.  </p>

<p>A classic use of the IQR is outlier detection using Tukey’s method (values less than <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-f2de8fe42732eb0aaaed68ccf31b0510_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#49;&#32;&#45;&#32;&#49;&#46;&#53;&#32;&#92;&#44;&#32;&#92;&#116;&#101;&#120;&#116;&#123;&#73;&#81;&#82;&#125;" title="Rendered by QuickLaTeX.com" height="17" width="102" style="vertical-align: -4px;"/> or greater than <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-35a5d5e74a4c88ba8ee2a6ffef76e3c2_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#51;&#32;&#43;&#32;&#49;&#46;&#53;&#32;&#92;&#44;&#32;&#92;&#116;&#101;&#120;&#116;&#123;&#73;&#81;&#82;&#125;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="108" style="vertical-align: -5px;"/>.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Returns an array with the first, second (median) and third quartile. * * Method: "Tukey hinges". * - Sort the dataset. * - For Q1 and Q3, exclude the median when the sample size is odd. * * @return float[] [Q1, Q2, Q3] in ascending order. */ public function quartiles(): array { $sorted = $this-&lt;encoded_tag_closed /&gt;data; sort($sorted, SORT_NUMERIC); $n = count($sorted); if ($n === 1) { return [$sorted[0], $sorted[0], $sorted[0]]; } $mid = intdiv($n, 2); // Median (Q2) $q2 = ($n % 2 === 0) ? ($sorted[$mid - 1] + $sorted[$mid]) / 2.0 : (float) $sorted[$mid]; // Lower half (exclude median if n is odd) $lower = array_slice($sorted, 0, $mid); // Upper half (exclude median if n is odd) $upper = array_slice($sorted, ($n % 2 === 0) ? $mid : $mid + 1); // Q1 and Q3 are medians of the two halves $q1 = $this-&lt;encoded_tag_closed /&gt;medianOfArray($lower); $q3 = $this-&lt;encoded_tag_closed /&gt;medianOfArray($upper); return [$q1, $q2, $q3]; } /** * Calculates the interquartile range (Q3 – Q1). * * @return float The interquartile range. */ public function iqr(): float { [$q1, , $q3] = $this-&lt;encoded_tag_closed /&gt;quartiles(); return $q3 - $q1; } /* ---------- Helper ---------- */ /** * Median of a pre-sorted array (helper for quartiles). * * @param float[] $arr Sorted numeric array. * @return float Median value. */ private function medianOfArray(array $arr): float { $count = count($arr); if ($count === 0) { throw new \LogicException('Cannot compute median of an empty array.'); } $mid = intdiv($count, 2); return ($count % 2 === 0) ? ($arr[$mid - 1] + $arr[$mid]) / 2.0 : (float) $arr[$mid]; }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Returns an array with the first, second (median) and third quartile.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * Method: "Tukey hinges".</span></span>
<span class="line"><span style="color: #6A9955"> *  - Sort the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *  - For Q1 and Q3, exclude the median when the sample size is odd.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float[]</span><span style="color: #6A9955"> [Q1, Q2, Q3] in ascending order.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #D4D4D4"> </span><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">quartiles</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">array</span></span>
<span class="line"><span style="color: #D4D4D4">    {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">sort</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">, SORT_NUMERIC);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> [</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">], </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">], </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">]];</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">intdiv</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// Median (Q2)</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$q2</span><span style="color: #D4D4D4"> = (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> % </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">            ? (</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4"> - </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">] + </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">]) / </span><span style="color: #B5CEA8">2.0</span></span>
<span class="line"><span style="color: #D4D4D4">            : (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">];</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// Lower half (exclude median if n is odd)</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$lower</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_slice</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// Upper half (exclude median if n is odd)</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$upper</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">array_slice</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">, (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> % </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">) ? </span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4"> : </span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4"> + </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// Q1 and Q3 are medians of the two halves</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$q1</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">medianOfArray</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$lower</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$q3</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">medianOfArray</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$upper</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> [</span><span style="color: #9CDCFE">$q1</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">$q2</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">$q3</span><span style="color: #D4D4D4">];</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the interquartile range (Q3 – Q1).</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The interquartile range.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">iqr</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    [</span><span style="color: #9CDCFE">$q1</span><span style="color: #D4D4D4">, , </span><span style="color: #9CDCFE">$q3</span><span style="color: #D4D4D4">] = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">quartiles</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$q3</span><span style="color: #D4D4D4"> - </span><span style="color: #9CDCFE">$q1</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"/>
<span class="line"><span style="color: #6A9955">/* ---------- Helper ---------- */</span></span>
<span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Median of a pre-sorted array (helper for quartiles).</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@param</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float[]</span><span style="color: #6A9955"> $arr Sorted numeric array.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> Median value.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">medianOfArray</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">array</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$arr</span><span style="color: #D4D4D4">): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$arr</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\LogicException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Cannot compute median of an empty array.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">intdiv</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$count</span><span style="color: #D4D4D4"> % </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">        ? (</span><span style="color: #9CDCFE">$arr</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4"> - </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">] + </span><span style="color: #9CDCFE">$arr</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">]) / </span><span style="color: #B5CEA8">2.0</span></span>
<span class="line"><span style="color: #D4D4D4">        : (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">$arr</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$mid</span><span style="color: #D4D4D4">];</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p>When the <code>quartiles()</code> method is called, the first thing it does is create a copy of the internal data and sort it in ascending order; this copy preserves the original order provided by the user and allows working with a monotonically ordered vector, which is a necessary prerequisite for identifying quartiles. Immediately afterward, the variable <code>$n</code> stores the sample size, and <code>$mid</code> represents the central index calculated via integer division. With these two pieces of information, the median of the entire sample is determined, which becomes the second quartile <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-7a8f7f7bc05736504761c873bfd99aa1_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#50;" title="Rendered by QuickLaTeX.com" height="16" width="21" style="vertical-align: -4px;"/>; if the number of observations is even, the median is the arithmetic mean of the two central values, whereas in the odd case, it coincides with the value at the central position.  </p>

<p>Once <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-7a8f7f7bc05736504761c873bfd99aa1_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#50;" title="Rendered by QuickLaTeX.com" height="16" width="21" style="vertical-align: -4px;"/> is known, the sorted array is split into two halves. If the sample size is odd, the median must not be included in either the lower or upper part, so the function <code>array_slice</code> explicitly excludes it; if it is even, the division occurs exactly in half. At this point, the outer quartiles come into play: to calculate <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-6b6b12b50fe43a209ecce557539ee185_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#49;" title="Rendered by QuickLaTeX.com" height="16" width="20" style="vertical-align: -4px;"/> and <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-6cef68a5946963445c5d51e5093bd228_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#51;" title="Rendered by QuickLaTeX.com" height="16" width="21" style="vertical-align: -4px;"/>, no new object is created—rather, the private helper <code>medianOfArray</code> is invoked on the two already sorted halves. This small routine receives a vector, counts its elements, determines the central index, and returns the median using the same logic as before; all of this remains confined within the class, keeping the public interface clean and avoiding any code duplication.   </p>

<p>The <code>quartiles()</code> method finally returns an array with the three values <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-2a57835ccc4419c852fb77d64952d763_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#91;&#81;&#95;&#49;&#44;&#32;&#81;&#95;&#50;&#44;&#32;&#81;&#95;&#51;&#93;" title="Rendered by QuickLaTeX.com" height="18" width="87" style="vertical-align: -5px;"/> in ascending order. Its counterpart, <code>iqr()</code>, simply unpacks that array, subtracts <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-6b6b12b50fe43a209ecce557539ee185_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#49;" title="Rendered by QuickLaTeX.com" height="16" width="20" style="vertical-align: -4px;"/> from <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-6cef68a5946963445c5d51e5093bd228_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#51;" title="Rendered by QuickLaTeX.com" height="16" width="21" style="vertical-align: -4px;"/>, and returns the interquartile range, providing in a single call the most robust measure of dispersion in the class. </p>

<p>In this setup, the logic for computing the median remains centralized within the private helper, and is reused both for quartiles and, implicitly, for any other internal functionality that might need to calculate a median on a sorted subset. Meanwhile, the public API continues to offer self-explanatory and easy-to-understand methods for anyone integrating your library.</p>

<h2 class="wp-block-heading">Variance</h2>

<p>To measure how much the values deviate from their central tendency, <strong>variance</strong> is introduced, which calculates the mean of the squared deviations from the arithmetic mean. Given <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> observations <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-08f4f7c471a86ab23d63ecf2d1c0071b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#49;&#44;&#32;&#120;&#95;&#50;&#44;&#32;&#92;&#100;&#111;&#116;&#115;&#44;&#32;&#120;&#95;&#110;&#32;" title="Rendered by QuickLaTeX.com" height="12" width="101" style="vertical-align: -4px;"/>with mean <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-20ebc7ea21d727ebe3840c06a433ab09_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#98;&#97;&#114;&#123;&#120;&#125;" title="Rendered by QuickLaTeX.com" height="11" width="10" style="vertical-align: 0px;"/>, the <strong>population variance</strong> is defined as: </p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3cde5d7135396b988fc10797bf8b06e4_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#115;&#105;&#103;&#109;&#97;&#94;&#123;&#50;&#125;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#110;&#125;&#92;&#115;&#117;&#109;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#40;&#120;&#95;&#105;&#32;&#45;&#32;&#92;&#98;&#97;&#114;&#123;&#120;&#125;&#41;&#94;&#123;&#50;&#125;" title="Rendered by QuickLaTeX.com" height="22" width="177" style="vertical-align: -6px;"/></p>

<p>If instead the data represent a <strong>sample</strong> drawn from a larger population, the correct estimator (sample variance) divides by <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-88ba1a69ceedf1a3a91b958bb253b927_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;&#32;&#8722;&#32;&#49;" title="Rendered by QuickLaTeX.com" height="12" width="19" style="vertical-align: 0px;"/>:</p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-932ecac38eb830d655be06b4360392dd_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#115;&#94;&#123;&#50;&#125;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#110;&#45;&#49;&#125;&#92;&#115;&#117;&#109;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#40;&#120;&#95;&#105;&#32;&#45;&#32;&#92;&#98;&#97;&#114;&#123;&#120;&#125;&#41;&#94;&#123;&#50;&#125;" title="Rendered by QuickLaTeX.com" height="22" width="192" style="vertical-align: -6px;"/></p>

<p>Variance returns a <strong>quadratic</strong> value: it is always non-negative and increases rapidly as deviations grow. For this reason, it is often used in conjunction with its square root (standard deviation) to return to the same units of measurement as the data.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the variance of the dataset. * * @param bool $sample If true, uses (n-1) in the denominator (sample variance). * If false, uses n (population variance). * @return float The variance value. */ public function variance(bool $sample = false): float { $n = count($this-&lt;encoded_tag_closed /&gt;data); // For a single value, population variance is 0, sample variance is undefined if ($n &lt;encoded_tag_open /&gt; 2 &amp;&amp; $sample) { throw new \DomainException('Sample variance requires at least two observations.'); } if ($n === 1) { return 0.0; } $mean = $this-&lt;encoded_tag_closed /&gt;mean(); $sumSquares = 0.0; foreach ($this-&lt;encoded_tag_closed /&gt;data as $v) { $diff = $v - $mean; $sumSquares += $diff * $diff; } $denominator = $sample ? ($n - 1) : $n; return $sumSquares / $denominator; }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the variance of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@param</span><span style="color: #6A9955"> </span><span style="color: #569CD6">bool</span><span style="color: #6A9955"> $sample If true, uses (n-1) in the denominator (sample variance).</span></span>
<span class="line"><span style="color: #6A9955"> *                     If false, uses n (population variance).</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The variance value.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">variance</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">bool</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$sample</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// For a single value, population variance is 0, sample variance is undefined</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> &lt; </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> &amp;&amp; </span><span style="color: #9CDCFE">$sample</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Sample variance requires at least two observations.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">0.0</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$mean</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">mean</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$sumSquares</span><span style="color: #D4D4D4"> = </span><span style="color: #B5CEA8">0.0</span><span style="color: #D4D4D4">;</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">foreach</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> as </span><span style="color: #9CDCFE">$v</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$diff</span><span style="color: #D4D4D4">        = </span><span style="color: #9CDCFE">$v</span><span style="color: #D4D4D4"> - </span><span style="color: #9CDCFE">$mean</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$sumSquares</span><span style="color: #D4D4D4"> += </span><span style="color: #9CDCFE">$diff</span><span style="color: #D4D4D4"> * </span><span style="color: #9CDCFE">$diff</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$denominator</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">$sample</span><span style="color: #D4D4D4"> ? (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> - </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">) : </span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$sumSquares</span><span style="color: #D4D4D4"> / </span><span style="color: #9CDCFE">$denominator</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/>The method receives a flag indicating whether to calculate the population variance or the sample estimator. It begins by evaluating the dataset size: with only one value, the population variance is by definition zero, whereas the sample variance does not exist and an exception is thrown. Once the mean is known, the loop traverses each observation, subtracts the mean, squares the deviation, and accumulates it. After the summation is complete, the division is performed by <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> or <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-88ba1a69ceedf1a3a91b958bb253b927_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;&#32;&#8722;&#32;&#49;" title="Rendered by QuickLaTeX.com" height="12" width="19" style="vertical-align: 0px;"/> depending on the specified context, thus returning the desired measure of dispersion.   </p>

<h2 class="wp-block-heading">Standard deviation</h2>

<p><br/>The <strong>standard deviation</strong> is simply the square root of the variance: it serves to bring the measure of dispersion back to the same units as the original data. If variance indicates “how many squared units” the observations deviate on average from their mean, the standard deviation expresses that deviation in linear units, making it much more intuitive for a non-specialist reader. For the population, it is obtained as <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-084a79d5a7bbe330f12ebed3c76aa066_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#115;&#105;&#103;&#109;&#97;&#32;&#61;&#32;&#92;&#115;&#113;&#114;&#116;&#123;&#92;&#115;&#105;&#103;&#109;&#97;&#94;&#123;&#50;&#125;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="68" style="vertical-align: -1px;"/> while in the sample case it is <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-2aa1eaecb6b446e07c4b2edf8fa49880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#115;&#32;&#61;&#32;&#92;&#115;&#113;&#114;&#116;&#123;&#115;&#94;&#123;&#50;&#125;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="64" style="vertical-align: -1px;"/>. Small standard deviation values indicate a distribution concentrated around the mean; large values indicate widely dispersed data.   </p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the standard deviation of the dataset. * * @param bool $sample If true, returns the sample standard deviation (n-1 in the denominator). * If false, returns the population standard deviation. * @return float The standard deviation. */ public function standardDeviation(bool $sample = false): float { return sqrt($this-&lt;encoded_tag_closed /&gt;variance($sample)); }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the standard deviation of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@param</span><span style="color: #6A9955"> </span><span style="color: #569CD6">bool</span><span style="color: #6A9955"> $sample If true, returns the sample standard deviation (n-1 in the denominator).</span></span>
<span class="line"><span style="color: #6A9955"> *                     If false, returns the population standard deviation.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The standard deviation.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">standardDeviation</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">bool</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$sample</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">sqrt</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">variance</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sample</span><span style="color: #D4D4D4">));</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/>The method contains no additional logic: it simply calls <code>variance()</code> with the same flag and returns its square root, delegating all calculation and domain checks to the already tested algorithm.</p>

<h2 class="wp-block-heading">Standard error of the mean</h2>

<p>When we observe only a sample from the population, the sample mean is an estimate subject to fluctuations: the smaller the sample, the more the estimate may vary from one sample to another. The <strong>standard error of the mean</strong> (<em>SEM</em>) precisely quantifies this expected variability. If s is the sample standard deviation and n is the sample size, the standard error is calculated as  </p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-01ae367d3efe370932420a9b723937f4_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#83;&#69;&#77;&#125;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#115;&#125;&#123;&#92;&#115;&#113;&#114;&#116;&#123;&#110;&#125;&#125;" title="Rendered by QuickLaTeX.com" height="24" width="94" style="vertical-align: -11px;"/>.</p>

<p>A small SEM indicates that the mean calculated from that sample is likely close to the true population mean; a large SEM suggests greater uncertainty. The SEM is also the basis for constructing confidence intervals for the mean. </p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the standard error of the mean (SEM) of the dataset. * * SEM = sample standard deviation / sqrt(n) * Requires at least two observations. * * @throws \DomainException If the dataset size is less than 2. * @return float The standard error of the mean. */ public function standardError(): float { $n = count($this-&lt;encoded_tag_closed /&gt;data); if ($n &lt;encoded_tag_open /&gt; 2) { throw new \DomainException('Standard error requires at least two observations.'); } return $this-&lt;encoded_tag_closed /&gt;standardDeviation(true) / sqrt($n); }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the standard error of the mean (SEM) of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * SEM = sample standard deviation / sqrt(n)</span></span>
<span class="line"><span style="color: #6A9955"> * Requires at least two observations.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@throws</span><span style="color: #6A9955"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #6A9955"> If the dataset size is less than 2.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The standard error of the mean.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">standardError</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> &lt; </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Standard error requires at least two observations.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">standardDeviation</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">true</span><span style="color: #D4D4D4">) / </span><span style="color: #DCDCAA">sqrt</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/>The method first retrieves the sample size; if there is only one value, it makes no sense to speak of standard error, so a domain exception is raised. In all other cases, it calls the already implemented sample standard deviation method to obtain <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-ae1901659f469e6be883797bfd30f4f8_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#115;" title="Rendered by QuickLaTeX.com" height="8" width="8" style="vertical-align: 0px;"/>. By dividing <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-ae1901659f469e6be883797bfd30f4f8_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#115;" title="Rendered by QuickLaTeX.com" height="8" width="8" style="vertical-align: 0px;"/> by the square root of n, it computes the SEM according to the canonical statistical definition and returns the result as a floating-point value. In this way, all numerical logic remains consistent with the other measures of dispersion: the function relies on well-tested methods, does not replicate existing calculations, and guarantees a very simple usage contract.   </p>

<h2 class="wp-block-heading">Mean absolute deviation (MAD)</h2>

<p>To measure dispersion in an intuitive way, without amplifying deviations by squaring them as variance does, the <strong>mean absolute deviation</strong> (MAD) is used. The idea is simple: calculate the absolute distance between each observation and the arithmetic mean, then take the average of those distances. If the sample consists of the values <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-5e8319f54f72e7dac2238515c1499b27_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#95;&#49;&#44;&#32;&#120;&#95;&#50;&#44;&#32;&#92;&#100;&#111;&#116;&#115;&#44;&#32;&#120;&#95;&#110;" title="Rendered by QuickLaTeX.com" height="12" width="101" style="vertical-align: -4px;"/> and their mean is <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-20ebc7ea21d727ebe3840c06a433ab09_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#98;&#97;&#114;&#123;&#120;&#125;" title="Rendered by QuickLaTeX.com" height="11" width="10" style="vertical-align: 0px;"/>, the mean absolute deviation is given by:  </p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-851de72c318f9c4e22aa336e66091878_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#77;&#65;&#68;&#125;&#32;&#92;&#59;&#61;&#92;&#59;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#110;&#125;&#92;&#115;&#117;&#109;&#95;&#123;&#105;&#61;&#49;&#125;&#94;&#123;&#110;&#125;&#32;&#92;&#108;&#118;&#101;&#114;&#116;&#32;&#120;&#95;&#105;&#32;&#45;&#32;&#92;&#98;&#97;&#114;&#123;&#120;&#125;&#32;&#92;&#114;&#118;&#101;&#114;&#116;" title="Rendered by QuickLaTeX.com" height="22" width="189" style="vertical-align: -6px;"/>.</p>

<p>The mean absolute deviation maintains the same units of measurement as the data, is less sensitive to outliers than the standard deviation, and offers an immediate interpretation: it indicates by how many units, on average, each value deviates from the center of the distribution.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Calculates the mean absolute deviation (MAD) of the dataset. * * @return float The mean absolute deviation. */ public function meanAbsoluteDeviation(): float { $mean = $this-&lt;encoded_tag_closed /&gt;mean(); $sumAbs = 0.0; foreach ($this-&lt;encoded_tag_closed /&gt;data as $v) { $sumAbs += abs($v - $mean); } return $sumAbs / count($this-&lt;encoded_tag_closed /&gt;data); }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Calculates the mean absolute deviation (MAD) of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The mean absolute deviation.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">meanAbsoluteDeviation</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$mean</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #DCDCAA">mean</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$sumAbs</span><span style="color: #D4D4D4"> = </span><span style="color: #B5CEA8">0.0</span><span style="color: #D4D4D4">;</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">foreach</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> as </span><span style="color: #9CDCFE">$v</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">$sumAbs</span><span style="color: #D4D4D4"> += </span><span style="color: #DCDCAA">abs</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$v</span><span style="color: #D4D4D4"> - </span><span style="color: #9CDCFE">$mean</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$sumAbs</span><span style="color: #D4D4D4"> / </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/>The method first retrieves the arithmetic mean of the sample using the already existing <code>mean()</code> function. With this information, it iterates over each observation, subtracts the mean, takes the absolute value of the deviation, and accumulates it in <code>$sumAbs</code>. Once the loop is complete, it divides the sum of absolute deviations by the sample size, returning the result as a floating-point number. The logic remains linear and without conditional branches because the MAD formula does not require distinctions between population and sample: division by <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> always applies.   </p>

<h2 class="wp-block-heading">Percentiles</h2>

<p>To locate any given value along the distribution, <strong>percentiles</strong> are used: the <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-915a1ea9fb3b667730a2c3dbdc8f1427_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#112;&#116;&#104;" title="Rendered by QuickLaTeX.com" height="16" width="26" style="vertical-align: -4px;"/> percentile identifies the point below which exactly <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b4f1234e2703e8efe959dc269d478e45_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#112;&#8239;&#37;" title="Rendered by QuickLaTeX.com" height="12" width="10" style="vertical-align: -4px;"/> of the ordered data fall. The 50th percentile coincides with the median, the 25th and 75th form the quartiles already implemented, and in general, knowing multiple percentiles allows for a very detailed description of the distribution’s shape. Since the sample is finite, the percentile position rarely corresponds to an exact integer index: interpolation between adjacent elements is therefore required. A widely adopted convention (Excel and NumPy “linear” method) consists in calculating:   </p>

<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-abfa90ac6a2818ca37cc2160c0eda4d0_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#114;&#32;&#61;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#112;&#125;&#123;&#49;&#48;&#48;&#125;&#92;&#44;&#40;&#110;&#45;&#49;&#41;" title="Rendered by QuickLaTeX.com" height="20" width="114" style="vertical-align: -6px;"/></p>

<p>where <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-b170995d512c659d8668b4e42e1fef6b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#110;" title="Rendered by QuickLaTeX.com" height="8" width="11" style="vertical-align: 0px;"/> is the sample size: if r falls between indices <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: 0px;"/> and <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-1e22674f5099474b9902f53541dfc8dc_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;&#43;&#49;" title="Rendered by QuickLaTeX.com" height="14" width="39" style="vertical-align: -2px;"/>, the percentile is the linear combination of the two values, weighted by the fractional part of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-c409433a9e2dfcdb83360a974d243f18_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#114;" title="Rendered by QuickLaTeX.com" height="8" width="8" style="vertical-align: 0px;"/>.</p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Returns the p-th percentile of the dataset (linear interpolation). * * @param float $p Percentile in the closed range [0, 100]. * @throws \DomainException If $p is outside 0–100. * @return float The requested percentile. */ public function percentile(float $p): float { if ($p &lt;encoded_tag_open /&gt; 0.0 || $p &lt;encoded_tag_closed /&gt; 100.0) { throw new \DomainException('Percentile must be between 0 and 100.'); } $sorted = $this-&lt;encoded_tag_closed /&gt;data; sort($sorted, SORT_NUMERIC); $n = count($sorted); // Edge cases: 0th and 100th percentile if ($p === 0.0) { return (float) $sorted[0]; } if ($p === 100.0) { return (float) $sorted[$n - 1]; } // Linear-interpolated rank $rank = ($p / 100.0) * ($n - 1); $lowerIndex = (int) floor($rank); $upperIndex = (int) ceil($rank); $weightUpper = $rank - $lowerIndex; // If rank is an integer, no interpolation is needed if ($lowerIndex === $upperIndex) { return (float) $sorted[$lowerIndex]; } $lowerValue = $sorted[$lowerIndex]; $upperValue = $sorted[$upperIndex]; return (1.0 - $weightUpper) * $lowerValue + $weightUpper * $upperValue; }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Returns the p-th percentile of the dataset (linear interpolation).</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@param</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> $p Percentile in the closed range [0, 100].</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@throws</span><span style="color: #6A9955"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #6A9955"> If $p is outside 0–100.</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The requested percentile.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">percentile</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$p</span><span style="color: #D4D4D4">): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$p</span><span style="color: #D4D4D4"> &lt; </span><span style="color: #B5CEA8">0.0</span><span style="color: #D4D4D4"> || </span><span style="color: #9CDCFE">$p</span><span style="color: #D4D4D4"> &gt; </span><span style="color: #B5CEA8">100.0</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">\DomainException</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">'Percentile must be between 0 and 100.'</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">sort</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">, SORT_NUMERIC);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">count</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">);</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Edge cases: 0th and 100th percentile</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$p</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">0.0</span><span style="color: #D4D4D4">)   { </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">]; }</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$p</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">100.0</span><span style="color: #D4D4D4">) { </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> - </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">]; }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// Linear-interpolated rank</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$rank</span><span style="color: #D4D4D4">        = (</span><span style="color: #9CDCFE">$p</span><span style="color: #D4D4D4"> / </span><span style="color: #B5CEA8">100.0</span><span style="color: #D4D4D4">) * (</span><span style="color: #9CDCFE">$n</span><span style="color: #D4D4D4"> - </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$lowerIndex</span><span style="color: #D4D4D4">  = (</span><span style="color: #569CD6">int</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">floor</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$rank</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$upperIndex</span><span style="color: #D4D4D4">  = (</span><span style="color: #569CD6">int</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">ceil</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">$rank</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$weightUpper</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">$rank</span><span style="color: #D4D4D4"> - </span><span style="color: #9CDCFE">$lowerIndex</span><span style="color: #D4D4D4">;</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// If rank is an integer, no interpolation is needed</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">$lowerIndex</span><span style="color: #D4D4D4"> === </span><span style="color: #9CDCFE">$upperIndex</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$lowerIndex</span><span style="color: #D4D4D4">];</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$lowerValue</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$lowerIndex</span><span style="color: #D4D4D4">];</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">$upperValue</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">$sorted</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">$upperIndex</span><span style="color: #D4D4D4">];</span></span>
<span class="line"/>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #B5CEA8">1.0</span><span style="color: #D4D4D4"> - </span><span style="color: #9CDCFE">$weightUpper</span><span style="color: #D4D4D4">) * </span><span style="color: #9CDCFE">$lowerValue</span><span style="color: #D4D4D4"> + </span><span style="color: #9CDCFE">$weightUpper</span><span style="color: #D4D4D4"> * </span><span style="color: #9CDCFE">$upperValue</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/>The method first validates that the requested percentile lies between 0 and 100 inclusive, thus ensuring consistency with the statistical definition. It then creates a sorted copy of the data, since any percentile localization requires a monotonically increasing vector. The boundary cases 0 and 100 are handled explicitly by returning the minimum and maximum of the sample, respectively. For all other values, the real rank <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-c409433a9e2dfcdb83360a974d243f18_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#114;" title="Rendered by QuickLaTeX.com" height="8" width="8" style="vertical-align: 0px;"/> is calculated, which may fall between two integer indices; the lower and upper indices define the interval containing the fractional position. If <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-c409433a9e2dfcdb83360a974d243f18_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#114;" title="Rendered by QuickLaTeX.com" height="8" width="8" style="vertical-align: 0px;"/> is already an integer, no interpolation is needed and the method returns the corresponding element directly. Otherwise, the lower and upper values are linearly combined using a weight equal to the decimal part of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-c409433a9e2dfcdb83360a974d243f18_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#114;" title="Rendered by QuickLaTeX.com" height="8" width="8" style="vertical-align: 0px;"/>, yielding a continuous result that flows smoothly between the elements of the sample.     </p>

<h2 class="wp-block-heading">Minimum and Maximum</h2>

<p><br/>To complete the overview of descriptive measures, it is useful to be able to quickly retrieve the lower and upper extremes of the distribution. The <strong>minimum value</strong> indicates the smallest observation recorded in the sample; <strong>the maximum </strong>marks the largest. These two quantities, although extremely simple, are essential both for providing context to the data (knowing where the observed interval begins and ends) and as components of other statistics, such as the range you have already implemented. Since the dataset is entirely in memory, finding the minimum and maximum requires only a single linear scan, and the computational cost is negligible.   </p>

<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewbox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/** * Returns the minimum value of the dataset. * * @return float The smallest observation. */ public function minValue(): float { return (float) min($this-&lt;encoded_tag_closed /&gt;data); } /** * Returns the maximum value of the dataset. * * @return float The largest observation. */ public function maxValue(): float { return (float) max($this-&lt;encoded_tag_closed /&gt;data); }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewbox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Returns the minimum value of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The smallest observation.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">minValue</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">min</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"/>
<span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Returns the maximum value of the dataset.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * </span><span style="color: #569CD6">@return</span><span style="color: #6A9955"> </span><span style="color: #569CD6">float</span><span style="color: #6A9955"> The largest observation.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span>
<span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">maxValue</span><span style="color: #D4D4D4">(): </span><span style="color: #569CD6">float</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">float</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">max</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">$this</span><span style="color: #D4D4D4">-&gt;</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>

<p><br/>The PHP functions min() and max() scan the array only once; the cast ensures that the returned type is float, consistent with the rest of the API.</p>

<h2 class="wp-block-heading">Conclusions</h2>

<p>With this final method, we have completed our <code>DescriptiveStats</code> class, which now encapsulates in a single component the most important tools of descriptive statistics: from measures of central tendency (arithmetic mean, median, mode, percentiles) to measures of dispersion (range, variance, standard deviation, IQR, MAD), including indicators of robustness (trimmed mean) and uncertainty (standard error).<br/>Each function is self-contained, strongly typed, and supported by automated PHPUnit tests, which we have not included here so as not to further lengthen an article already rich in content.  </p>

<p>Thanks to this library, a PHP project can quickly analyze small datasets without external dependencies and without having to use artificial intelligence just to compute a “mode”, integrate statistical calculations into reports, dashboards, or APIs, and extend the class with additional indicators by leveraging a clear and consistent architecture. </p>

<p>In the next article, we will see how to transform the code into a Composer package: we will create the final structure of the repository, review how to modify the <code>composer.json</code> file, configure CI to run the tests, and publish the library on Packagist, making it installable with a simple:</p>

<p><code>composer require thesimon82/descriptive-statistics</code></p>

<p>In this way, you will be able to distribute your open-source solution, receive contributions from the community, and reuse it in any project with maximum simplicity.</p>

<p>[starbox]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/a-descriptive-statistics-class-in-php/">A Descriptive Statistics Class in PHP</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Invisible Watermark in JPEG with PHP 8</title>
		<link>https://renor.it/en/blog/software-development-programming/invisible-watermark-in-jpeg-with-php-8/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Sun, 11 May 2025 02:27:30 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[copyright]]></category>
		<category><![CDATA[dct]]></category>
		<category><![CDATA[digital pgotography]]></category>
		<category><![CDATA[digital sign]]></category>
		<category><![CDATA[discrete cosine transform]]></category>
		<category><![CDATA[gd]]></category>
		<category><![CDATA[image security]]></category>
		<category><![CDATA[images protection]]></category>
		<category><![CDATA[imagick]]></category>
		<category><![CDATA[invisible metadata]]></category>
		<category><![CDATA[invisible watermark]]></category>
		<category><![CDATA[jpeg]]></category>
		<category><![CDATA[jpeg compression]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[photography blog]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[php script]]></category>
		<category><![CDATA[robust watermarking]]></category>
		<category><![CDATA[steganography]]></category>
		<guid isPermaLink="false">https://renor.it/invisible-watermark-in-jpeg-with-php-8/</guid>

					<description><![CDATA[<p>Protect your images by modifying the coefficients, without damaging them Publishing photographs online has become essential for photographers, e-commerce sites, and bloggers, but according to the latest studies on image theft, over 70% of visual content is reshared without credit or authorization. A visible watermark certainly protects the author, but to make it truly effective [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/invisible-watermark-in-jpeg-with-php-8/">Invisible Watermark in JPEG with PHP 8</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Protect your images by modifying the coefficients, without damaging them</h2>
<p>Publishing photographs online has become essential for photographers, e-commerce sites, and bloggers, but according to the latest studies on image theft, over 70% of visual content is reshared without credit or authorization. A visible watermark certainly protects the author, but to make it truly effective against intellectual property theft, it often ruins the aesthetic of the shot with an unsightly “patch”! </p>
<p>In contrast, an “invisible” watermark embedded in the coefficients of the Discrete Cosine Transform (DCT), on which the JPEG format is based, preserves perceived quality and can withstand resizing or light compression.</p>
<p>By encoding the information in the <strong>sign</strong> of specific low-frequency coefficients, it is possible to ensure an effective compromise between invisibility and robustness.</p>
<p>In this open-source project, available in my GitHub repository: <a href="https://github.com/thesimon82/php-dct-invisible-watermark">https://github.com/thesimon82/php-dct-invisible-watermark</a>, written in PHP 8.3 and based on GD/Imagick for pixel manipulation, we will demonstrate step by step how to embed a textual signature into the low-frequency (AC) coefficients of each 8×8 block, how to retrieve its fingerprint, and why using the <strong>coefficient’s sign</strong> represents a robust strategy against common JPEG compressions, even at 80% quality or lower.</p>
<p>The goal is not purely educational: the resulting microservice can be cloned from the repository and integrated, via CLI or API, into any image publishing workflow.</p>
<h2 class="wp-block-heading">Technical foundations</h2>
<p>Before writing a single line of code, it is worth clarifying how the JPEG compression algorithm processes the images it needs to compress.</p>
<p>The JPEG format divides the image into a grid of 8×8 pixel blocks; each block is transformed using the Discrete Cosine Transform (DCT), which decomposes the visual information into 64 coefficients ordered from the lowest frequency (the top-left corner, representing the average intensity) to the highest frequency (the finest details).</p>
<p>The mid-to-low frequency coefficients are the ideal spot for our watermark: altering the zero-frequency (DC) coefficient would produce visible halos, while modifying only the highest frequencies would lead to their removal during the very first JPEG compression.</p>
<p>By instead acting on intermediate coefficients, we can encode individual bits by <strong>modifying the sign of the coefficient</strong> (positive = 0, negative = 1), thus obtaining a marker that is invisible to the eye but robust enough to survive resizing and JPEG compressions down to 80% quality.</p>
<p>This mathematical principle will guide the entire implementation.</p>
<p>We will read each 8×8 block, compute the DCT using a native PHP function, modify the <strong>sign</strong> of one or more selected coefficients, and save the new JPEG using GD or Imagick.</p>
<p>In other words, the watermark will be hidden precisely in the coefficients that the JPEG compression algorithm tends to preserve: it will be invisible to the eye but difficult to remove, even in the presence of light recompressions.</p>
<h2 class="wp-block-heading">The mathematics that makes the watermark invisible</h2>
<p>Each <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-269f426dfc1dab3e5d9987e3361f4307_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#56;&times;&#56;" title="Rendered by QuickLaTeX.com" height="12" width="18" style="vertical-align: 0px;"/> block of intensities <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-533196a0d71c414a47d0753600045b35_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#102;&#95;&#123;&#120;&#44;&#121;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="28" style="vertical-align: -6px;"/> (where<img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-2cde234e6bcf66bbc8f3f37407c2dea8_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#32;&#120;&#44;&#121;&#32;&#92;&#105;&#110;&#32;&#92;&#123;&#48;&#44;&#92;&#100;&#111;&#116;&#115;&#44;&#55;&#92;&#125;" title="Rendered by QuickLaTeX.com" height="19" width="123" style="vertical-align: -5px;"/>) is converted into 64 coefficients <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-c82f152c8e6619cbeddd318d16e9aa3c_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#67;&#95;&#123;&#117;&#44;&#118;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="32" style="vertical-align: -6px;"/> (with <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-3568fd28364bcfd7b6e3a22d1242fb48_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#117;&#44;&#118;&#32;&#92;&#105;&#110;&#32;&#92;&#123;&#48;&#44;&#92;&#100;&#111;&#116;&#115;&#44;&#55;&#92;&#125;" title="Rendered by QuickLaTeX.com" height="19" width="123" style="vertical-align: -5px;"/>) using:</p>
<p class="p1">\[</p>
<p class="p1">C_{u,v}= \frac{1}{4}\,\alpha(u)\,\alpha(v)\!</p>
<p class="p1">\sum_{x=0}^{7}\,\sum_{y=0}^{7}</p>
<p class="p1">f_{x,y}\;</p>
<p class="p1">\cos\!\Bigl[\frac{(2x+1)u\pi}{16}\Bigr]\,</p>
<p class="p1">\cos\!\Bigl[\frac{(2y+1)v\pi}{16}\Bigr]</p>
<p class="p1">\tag{1}</p>
<p class="p1">\]</p>
</p>
<p>where</p>
<p class="p1">\[</p>
<p class="p1">\alpha(k)=</p>
<p class="p1">\begin{cases}</p>
<p class="p1">\dfrac1{\sqrt{2}}, &amp; k=0,\\[6pt]</p>
<p class="p1">1, &amp; k\ge 1.</p>
<p class="p1">\end{cases}</p>
<p class="p1">\]</p>
<p> <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-d28eb1caf4d0c1c6fad49a76dcf1fcd7_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#67;&#95;&#123;&#48;&#44;&#48;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="31" style="vertical-align: -6px;"/> (the DC coefficient) represents the average luminance of the block. The <b>AC</b> coefficients C_{u,v} with <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-f8b0a2994090535db3387a23b96d68f2_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#117;&#44;&#32;&#118;&#32;&#92;&#110;&#101;&#113;&#32;&#48;" title="Rendered by QuickLaTeX.com" height="17" width="60" style="vertical-align: -4px;"/> describe increasingly fine details as either <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-f6b6854c2a333869e2c2bb8c982339bd_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#117;&#32;" title="Rendered by QuickLaTeX.com" height="8" width="10" style="vertical-align: 0px;"/>or <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-f516060802ba8658c6967ff76b28e475_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#118;&#32;" title="Rendered by QuickLaTeX.com" height="8" width="9" style="vertical-align: 0px;"/>increase. </p>
<h3 class="wp-block-heading">Simplified numerical example</h3>
<p>Let’s suppose a block where all values are 128, a medium gray. Then: </p>
<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-10d14ca9d7630918f33b4edf581813bf_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#67;&#95;&#123;&#48;&#44;&#48;&#125;&#32;&#61;&#32;&#56;&#32;&#92;&#99;&#100;&#111;&#116;&#32;&#56;&#32;&#92;&#99;&#100;&#111;&#116;&#32;&#49;&#50;&#56;&#32;&#92;&#99;&#100;&#111;&#116;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#52;&#125;&#32;&#92;&#99;&#100;&#111;&#116;&#32;&#92;&#97;&#108;&#112;&#104;&#97;&#40;&#48;&#41;&#94;&#50;&#32;&#61;&#32;&#54;&#52;&#32;&#92;&#99;&#100;&#111;&#116;&#32;&#49;&#50;&#56;&#32;&#92;&#99;&#100;&#111;&#116;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#52;&#125;&#32;&#92;&#99;&#100;&#111;&#116;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#50;&#125;&#32;&#61;&#32;&#49;&#48;&#50;&#52;" title="Rendered by QuickLaTeX.com" height="22" width="390" style="vertical-align: -6px;"/> while <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-21f20b4860c60891e0cdad863c164b8d_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#67;&#95;&#123;&#117;&#44;&#118;&#125;&#32;&#61;&#32;&#48;&#32;&#92;&#113;&#117;&#97;&#100;&#32;&#92;&#116;&#101;&#120;&#116;&#123;&#102;&#111;&#114;&#32;&#125;" title="Rendered by QuickLaTeX.com" height="19" width="104" style="vertical-align: -6px;"/> <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-c60f08aca839d142803dad02c4ea0b69_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#40;&#117;&#44;&#118;&#41;&#32;&#92;&#110;&#101;&#113;&#32;&#40;&#48;&#44;&#48;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="102" style="vertical-align: -5px;"/><br />because there are no frequency variations.</p>
<p>The compressed image preserves the lower-frequency coefficients, while the higher ones are aggressively quantized—often down to 0.</p>
<h3 class="wp-block-heading">Quantization and selection of safe frequencies</h3>
<p>Before being saved in JPEG format, the DCT coefficients are divided by a quantization matrix <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-cd69090f5c2d8ab2c05b77ea4f2526b5_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#123;&#117;&#44;&#118;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="33" style="vertical-align: -6px;"/>, specific to each frequency:</p>
<p class="p1">\[</p>
<p class="p1">\tilde{C}{u,v}= \operatorname{round}\!\Bigl(\frac{C{u,v}}{Q_{u,v}}\right)\Big).</p>
<p class="p1">\tag{2}</p>
<p class="p1">\]</p>
<p>The higher the value of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-cd69090f5c2d8ab2c05b77ea4f2526b5_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#81;&#95;&#123;&#117;&#44;&#118;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="33" style="vertical-align: -6px;"/>, the greater the loss of precision, with the possibility that the coefficients will be reduced to zero.<br />For this reason, in our algorithm we operate on a low-frequency subset of the AC coefficients, in positions where quantization is moderate and the values are more likely to survive compression.<br />We also avoid coefficients that are too low (including DC), as modifying them could introduce visible artifacts.</p>
<h3 class="wp-block-heading">Bit encoding in the coefficient</h3>
<p>To ensure the bit survives JPEG compression, instead of using parity, we enforce the sign of the coefficient based on the bit to be written:</p>
<p>If the bit is 0, we make the coefficient positive</p>
<p class="p1">\[</p>
<p>\operatorname{embed}(c, b) =</p>
<p>\begin{cases}</p>
<p>+\text{minAmp} + \text{boost}, &amp; b = 0 \\\\</p>
<p>-\text{minAmp} &#8211; \text{boost}, &amp; b = 1</p>
<p>\end{cases}</p>
<p>\tag{3’}</p>
<p class="p1">\]</p>
<p class="p1">Where:</p>
<p><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-fc1945d79b767f05e0fb239e856a6ba4_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#109;&#105;&#110;&#65;&#109;&#112;&#125;" title="Rendered by QuickLaTeX.com" height="17" width="68" style="vertical-align: -4px;"/> ensures a value sufficiently far from zero (typically ≥ 80),<br /><img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-adb5446b7c5c59b04b6130e6bf9ee412_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#101;&#120;&#116;&#123;&#98;&#111;&#111;&#115;&#116;&#125;" title="Rendered by QuickLaTeX.com" height="12" width="42" style="vertical-align: 0px;"/> (e.g., 64) increases the distance from zero to prevent nullification during quantization.</p>
<p class="p2">In this way, even after lossy transformations, the sign remains unchanged and the bit can be recovered during reading.</p>
<p>If we later wish to read the bit, we simply need to evaluate the <strong>sign</strong> of the quantized DCT coefficient:</p>
<p class="p1">\[</p>
<p>b =</p>
<p>\begin{cases}</p>
<p>0, &amp; \tilde{C}{u,v} \ge 0 \\\\</p>
<p>1, &amp; \tilde{C}{u,v} &lt; 0</p>
<p>\end{cases}</p>
<p>\tag{4’}</p>
<p class="p1">\]</p>
<p>In other words, a positive coefficient indicates bit 0, a negative coefficient indicates bit 1.</p>
<p>This method is more robust because the sign of a coefficient survives many lossy transformations, unlike the least significant bit.</p>
<h3 class="wp-block-heading">Reconstruction (Inverse DCT)</h3>
<p>After modifying the coefficients, we invert the transformation to obtain the watermarked image block: </p>
<p class="p1">\[</p>
<p class="p1">f_{x,y}= \frac{1}{4}\!</p>
<p class="p1">\sum_{u=0}^{7}\sum_{v=0}^{7}</p>
<p class="p1">\alpha(u)\,\alpha(v)\,</p>
<p class="p1">\tilde{C}_{u,v}\,</p>
<p class="p1">\cos\!\Bigl[\frac{(2x+1)u\pi}{16}\Bigr]\,</p>
<p class="p1">\cos\!\Bigl[\frac{(2y+1)v\pi}{16}\Bigr].</p>
<p class="p1">\tag{5}</p>
<p class="p1">\]</p>
<p>The maximum error introduced by a ±1 variation on a mid-frequency coefficient is less than 1 quantum in luminance, which is below the human visual perception threshold for real photographs.</p>
<h3 class="wp-block-heading">Cyclic redundancy to induce robustness</h3>
<p>To withstand compression or cropping, we encode the message M by duplicating each bit k times and applying a majority vote during reading. With k = 3: </p>
<p class="p1">\[</p>
<p class="p1">M = b_1b_2\dots b_n</p>
<p class="p1">\;\longrightarrow\;</p>
<p class="p1">b_1b_1b_1\,b_2b_2b_2\dots b_nb_nb_n</p>
<p class="p1">\tag{6}</p>
<p class="p1">\]</p>
<p>Correct recovery is guaranteed as long as <strong>at least two out of three copies survive</strong>.</p>
<h2 class="wp-block-heading">From formulas to code: the DCT class</h2>
<p>Now that we have formalized the entire mathematical framework, we can translate equation (1) and its inverse directly into PHP.</p>
<p>To keep the project in pure PHP, without native extensions or external dependencies, we implement the DCT manually on 8×8 blocks.</p>
<p>This approach is sufficiently fast for images up to a few megapixels and, most importantly, it is transparent and easy to understand and modify.</p>
<p>The project is organized with Composer and PSR-4 autoloading, but the Dct class is <strong>completely self-contained</strong> and <strong>does not require any external libraries</strong>.</p>
<p>If in the future you wish to extend the system with matrix analyses (e.g., box counting or distortion metrics), you can integrate MathPHP, but it is not required for the DCT.</p>
<h3 class="wp-block-heading">composer.json</h3>


<div class="wp-block-kevinbatdorf-code-block-pro" style="font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono">
<pre class="shiki nord" style="background-color: #2e3440ff;" tabindex="0"><code><span class="line"><span style="color: #eceff4;">{</span></span>
<span class="line">    <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">name</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">"</span><span style="color: #a3be8c;">renornad/php-dct-invisible-watermark</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">,</span></span>
<span class="line">    <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">description</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">"</span><span style="color: #a3be8c;">Pure-PHP DCT watermarking library for JPEG images.</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">,</span></span>
<span class="line">    <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">type</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">"</span><span style="color: #a3be8c;">project</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">,</span></span>
<span class="line">    <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">license</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">"</span><span style="color: #a3be8c;">MIT</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">,</span></span>
<span class="line">    <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">require</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">{</span></span>
<span class="line">      <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">php</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">"</span><span style="color: #a3be8c;">&gt;=8.3</span><span style="color: #eceff4;">"</span></span>
<span class="line">    <span style="color: #eceff4;">},</span></span>
<span class="line">    <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">autoload</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">{</span></span>
<span class="line">      <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">psr-4</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">{</span></span>
<span class="line">        <span style="color: #eceff4;">"</span><span style="color: #8fbcbb;">Renor</span><span style="color: #ebcb8b;">\\</span><span style="color: #8fbcbb;">Watermark</span><span style="color: #ebcb8b;">\\</span><span style="color: #eceff4;">"</span><span style="color: #eceff4;">:</span> <span style="color: #eceff4;">"</span><span style="color: #a3be8c;">src/</span><span style="color: #eceff4;">"</span></span>
<span class="line">      <span style="color: #eceff4;">}</span></span>
<span class="line">    <span style="color: #eceff4;">}</span></span>
<span class="line">  <span style="color: #eceff4;">}</span></span></code></pre>
</div>
<p>After defining our composer.json file, we proceed with the installation. Open the terminal and type the command: </p>

<pre class="wp-block-code"><code>composer install</code></pre>
<p>At this point, Composer will create the composer.lock file and the vendor directory, but it won’t install any packages from Packagist because we are not including any dependencies.</p>
<h2 class="wp-block-heading">src/Dct.php</h2>
<p>We can now finally write the Dct class. Remember that it is preferable to include comments in the code, following the standard in English: </p>


<div class="wp-block-kevinbatdorf-code-block-pro" style="font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono">
<pre class="shiki dark-plus" style="background-color: #1e1e1e;" tabindex="0"><code><span class="line"><span style="color: #d4d4d4;">&lt;?php</span></span>

<span class="line"><span style="color: #c586c0;">declare</span><span style="color: #d4d4d4;">(strict_types=</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>

<span class="line"><span style="color: #569cd6;">namespace</span> <span style="color: #4ec9b0;">Renor\Watermark</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;"> * 2-D Discrete Cosine Transform (DCT) and its inverse (IDCT) for 8x8 blocks.</span></span>
<span class="line"><span style="color: #6a9955;"> */</span></span>
<span class="line"><span style="color: #569cd6;">final</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Dct</span></span>
<span class="line"><span style="color: #d4d4d4;">{</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> N = </span><span style="color: #b5cea8;">8</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> SQRT_2_INV = </span><span style="color: #b5cea8;">0.7071067811865476</span><span style="color: #d4d4d4;">; </span><span style="color: #6a9955;">// 1 / sqrt(2)</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Performs the forward DCT on an 8x8 block of pixel intensities (0-255).</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">forward</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">array</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">assertBlock</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$C</span><span style="color: #d4d4d4;"> = [];</span></span>

<span class="line">        <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #9cdcfe;">$alphaU</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> === </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) ? </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::SQRT_2_INV : </span><span style="color: #b5cea8;">1.0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">            <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$alphaV</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> === </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) ? </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::SQRT_2_INV : </span><span style="color: #b5cea8;">1.0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #9cdcfe;">$sum</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0.0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">                    <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$cosX</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">cos</span><span style="color: #d4d4d4;">((</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">) * </span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> * M_PI / (</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N));</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$cosY</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">cos</span><span style="color: #d4d4d4;">((</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">) * </span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> * M_PI / (</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N));</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$sum</span><span style="color: #d4d4d4;"> += </span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">] * </span><span style="color: #9cdcfe;">$cosX</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$cosY</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">                    }</span></span>
<span class="line"><span style="color: #d4d4d4;">                }</span></span>
<span class="line">                <span style="color: #6a9955;">// The DCT formula includes a factor of 1/4</span></span>
<span class="line">                <span style="color: #9cdcfe;">$C</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">] = </span><span style="color: #b5cea8;">0.25</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$alphaU</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$alphaV</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$sum</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$C</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Performs the inverse DCT on an 8x8 block of coefficients, </span></span>
<span class="line"><span style="color: #6a9955;">     * returning pixel values in the range [0..255].</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">inverse</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$coeff</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">array</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">assertBlock</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$coeff</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$f</span><span style="color: #d4d4d4;"> = [];</span></span>

<span class="line">        <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$sum</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0.0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">                    <span style="color: #9cdcfe;">$alphaU</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> === </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) ? </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::SQRT_2_INV : </span><span style="color: #b5cea8;">1.0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                    <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N; ++</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$alphaV</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> === </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) ? </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::SQRT_2_INV : </span><span style="color: #b5cea8;">1.0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$cosX</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">cos</span><span style="color: #d4d4d4;">((</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">) * </span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;"> * M_PI / (</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N));</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$cosY</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">cos</span><span style="color: #d4d4d4;">((</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">) * </span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> * M_PI / (</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;"> * </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N));</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$sum</span><span style="color: #d4d4d4;"> += </span><span style="color: #9cdcfe;">$alphaU</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$alphaV</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$coeff</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">] * </span><span style="color: #9cdcfe;">$cosX</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$cosY</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">                    }</span></span>
<span class="line"><span style="color: #d4d4d4;">                }</span></span>
<span class="line">                <span style="color: #9cdcfe;">$pixel</span><span style="color: #d4d4d4;"> = (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #dcdcaa;">round</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0.25</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$sum</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                <span style="color: #9cdcfe;">$f</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">] = </span><span style="color: #dcdcaa;">max</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">min</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">255</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$pixel</span><span style="color: #d4d4d4;">));</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$f</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Checks that the array is exactly 8x8.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">assertBlock</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">void</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #dcdcaa;">count</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">) !== </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N) {</span></span>
<span class="line">            <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\InvalidArgumentException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'Block must have 8 rows.'</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;"> as </span><span style="color: #9cdcfe;">$row</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #dcdcaa;">count</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$row</span><span style="color: #d4d4d4;">) !== </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::N) {</span></span>
<span class="line">                <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\InvalidArgumentException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'Each row must have 8 columns.'</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Disallow instantiation.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">__construct</span><span style="color: #d4d4d4;">()</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>
<span class="line"><span style="color: #d4d4d4;">}</span></span></code></pre>
</div>
<p>This class is the core of the project. It encapsulates the algorithm of the two-dimensional Discrete Cosine Transform applied to 8×8 pixel blocks, the same scheme used by the JPEG standard.<br />At the start of the forward method, the class checks that the input array contains exactly eight rows and eight columns; otherwise, it throws an exception to prevent a sizing error from propagating silently.  </p>
<p>Once the shape has been verified, for each pair of frequency indices <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-75fa73f534ffea86ffac4074123b96b5_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#117;&#44;&#32;&#118;" title="Rendered by QuickLaTeX.com" height="12" width="27" style="vertical-align: -4px;"/>, it computes the double summation defined by the canonical DCT formula: it takes each luminance value <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-533196a0d71c414a47d0753600045b35_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#102;&#95;&#123;&#120;&#44;&#121;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="28" style="vertical-align: -6px;"/>, multiplies it by two cosine terms — one as a function of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-43fe27dc3e528266a619764d90fce60b_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#117;" title="Rendered by QuickLaTeX.com" height="8" width="10" style="vertical-align: 0px;"/> and coordinate <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-ede05c264bba0eda080918aaa09c4658_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;" title="Rendered by QuickLaTeX.com" height="8" width="10" style="vertical-align: 0px;"/>, the other as a function of <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-ef71511c70f0e4b25cc6bd69f3bc20c2_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#118;" title="Rendered by QuickLaTeX.com" height="8" width="9" style="vertical-align: 0px;"/> and coordinate <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-0af556714940c351c933bba8cf840796_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#121;" title="Rendered by QuickLaTeX.com" height="12" width="9" style="vertical-align: -4px;"/> — and accumulates the result. Once the entire block has been scanned, it applies the normalization factor     <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-fb3d4f7d1fb07bdbf19390d35df9d5c1_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#52;&#125;&#32;&#92;&#97;&#108;&#112;&#104;&#97;&#40;&#117;&#41;&#92;&#97;&#108;&#112;&#104;&#97;&#40;&#118;&#41;" title="Rendered by QuickLaTeX.com" height="22" width="78" style="vertical-align: -6px;"/>, where <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-1bcc20827e12fcb2c5fa968a7d1b895a_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#97;&#108;&#112;&#104;&#97;&#40;&#48;&#41;&#32;&#61;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#92;&#115;&#113;&#114;&#116;&#123;&#50;&#125;&#125;" title="Rendered by QuickLaTeX.com" height="27" width="79" style="vertical-align: -11px;"/> and <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-e95917e94dcf66be56c331e91a855b06_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#97;&#108;&#112;&#104;&#97;&#40;&#107;&#41;&#32;&#61;&#32;&#92;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#50;&#125;" title="Rendered by QuickLaTeX.com" height="22" width="68" style="vertical-align: -6px;"/> for <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-58f509ef01deb77042355f3d2111cc1a_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#107;&#32;&#92;&#103;&#101;&#113;&#32;&#49;" title="Rendered by QuickLaTeX.com" height="15" width="41" style="vertical-align: -3px;"/>, so that the coefficient <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-d28eb1caf4d0c1c6fad49a76dcf1fcd7_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#67;&#95;&#123;&#48;&#44;&#48;&#125;" title="Rendered by QuickLaTeX.com" height="18" width="31" style="vertical-align: -6px;"/> preserves the average luminance, and all the others represent, at the correct scale, the increasingly finer detail components. The method therefore returns an 8×8 matrix of floating-point coefficients: it is on this frequency representation that we will later embed the watermark bits.    </p>
<p>The inverse path is handled by the inverse method. Here too, before any processing, the class enforces a check on the shape of the block. For each pair of pixel coordinates <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-40ca3e51a9803f260de25590c47d925e_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#120;&#44;&#32;&#121;" title="Rendered by QuickLaTeX.com" height="12" width="27" style="vertical-align: -4px;"/>, it reconstructs the luminance by summing the 64 frequency components, each weighted by the product of cosine terms and the normalization factors <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-7e8439594a36787d99d464e65263b0d6_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#97;&#108;&#112;&#104;&#97;&#40;&#117;&#41;&#92;&#97;&#108;&#112;&#104;&#97;&#40;&#118;&#41;" title="Rendered by QuickLaTeX.com" height="19" width="69" style="vertical-align: -5px;"/>. The final value is then multiplied by <img loading="lazy" decoding="async" src="https://renor.it/wp-content/ql-cache/quicklatex.com-bc0cbfb7e7a2c46054a9130a25f73d75_l3.png" class="ql-img-inline-formula quicklatex-auto-format" alt="&#92;&#116;&#102;&#114;&#97;&#99;&#123;&#49;&#125;&#123;&#52;&#125;" title="Rendered by QuickLaTeX.com" height="22" width="7" style="vertical-align: -6px;"/>, rounded to the nearest integer, and clamped to the 0–255 range, to prevent any numerical errors from pushing the result outside the valid color space.   </p>
<p>The implementation closely follows the mathematical definition, without relying on FFT or SIMD accelerations, favoring educational clarity over performance. The constructor is private, the class cannot be instantiated, and all methods are static and stateless: in this way, the DCT behaves like a pure function—easily testable and reusable. When, in the following sections, we use it to embed a bit by modifying the sign of a low-frequency coefficient, we can be confident that the forward and inverse transformations are numerically consistent and that the alteration remains confined to the values we intend to manipulate.  </p>
<h2 class="wp-block-heading">src/Watermarker.php</h2>
<p>The Watermarker class is the director that orchestrates the most delicate phase of the entire project. It receives as input or output a sequence of 8×8 DCT coefficient blocks already computed by the Dct class and, without dealing with the physical reading of the JPEG file, simply manipulates those numbers to hide or retrieve an ASCII text. </p>


<div class="wp-block-kevinbatdorf-code-block-pro" style="font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono">
<pre class="shiki dark-plus" style="background-color: #1e1e1e;" tabindex="0"><code><span class="line"><span style="color: #d4d4d4;">&lt;?php</span></span>

<span class="line"><span style="color: #c586c0;">declare</span><span style="color: #d4d4d4;">(strict_types=</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>

<span class="line"><span style="color: #569cd6;">namespace</span> <span style="color: #4ec9b0;">Renor\Watermark</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;"> * Watermarker class</span></span>
<span class="line"><span style="color: #6a9955;"> *</span></span>
<span class="line"><span style="color: #6a9955;"> * Embeds/extracts a short ASCII message (max 10 characters + terminator)</span></span>
<span class="line"><span style="color: #6a9955;"> * into low-frequency DCT coefficients. Instead of relying on the single</span></span>
<span class="line"><span style="color: #6a9955;"> * parity bit – too fragile after rounding and quantization – here</span></span>
<span class="line"><span style="color: #6a9955;"> * we encode the information into the **sign** of the coefficient:</span></span>
<span class="line"><span style="color: #6a9955;"> *</span></span>
<span class="line"><span style="color: #6a9955;"> *   bit 0  → positive coefficient</span></span>
<span class="line"><span style="color: #6a9955;"> *   bit 1  → negative coefficient</span></span>
<span class="line"><span style="color: #6a9955;"> *</span></span>
<span class="line"><span style="color: #6a9955;"> * The sign is never affected during:</span></span>
<span class="line"><span style="color: #6a9955;"> *   1. IDCT → integer rounding,</span></span>
<span class="line"><span style="color: #6a9955;"> *   2. division by JPEG quantization tables,</span></span>
<span class="line"><span style="color: #6a9955;"> *   3. further rounding or transformations.</span></span>
<span class="line"><span style="color: #6a9955;"> */</span></span>
<span class="line"><span style="color: #569cd6;">final</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Watermarker</span></span>
<span class="line"><span style="color: #d4d4d4;">{</span></span>
<span class="line">    <span style="color: #6a9955;">/** Low-frequency coordinates (survive even at q≈90–100). */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> EMBED_COORDS = [</span></span>
<span class="line"><span style="color: #d4d4d4;">        [</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">], [</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">], [</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">], [</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;">],</span></span>
<span class="line"><span style="color: #d4d4d4;">        [</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">], [</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;">], [</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">], [</span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;">]</span></span>
<span class="line"><span style="color: #d4d4d4;">    ];</span></span>

<span class="line">    <span style="color: #6a9955;">/** Redundancy: each bit is repeated 25 times. */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> REDUNDANCY = </span><span style="color: #b5cea8;">25</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> TERMINATOR = </span><span style="color: #ce9178;">"</span><span style="color: #d7ba7d;">\0</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">    <span style="color: #6a9955;">/* ------------------------------------------------------------------</span></span>
<span class="line"><span style="color: #6a9955;">       Public API</span></span>
<span class="line"><span style="color: #6a9955;">    -------------------------------------------------------------------*/</span></span>

<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">embed</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #569cd6;">&amp;</span><span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$msg</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">void</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #dcdcaa;">strlen</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$msg</span><span style="color: #d4d4d4;">) &gt; </span><span style="color: #b5cea8;">10</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\RuntimeException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'Message too long (max 10).'</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">   = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">stringToBitStream</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$msg</span> <span style="color: #d4d4d4;">.</span> <span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::TERMINATOR);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$cursor</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">        <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;"> as </span><span style="color: #569cd6;">&amp;</span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::EMBED_COORDS as [</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">]) {</span></span>
<span class="line">                <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$cursor</span><span style="color: #d4d4d4;"> &gt;= </span><span style="color: #dcdcaa;">count</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">)) {</span></span>
<span class="line">                    <span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">                }</span></span>
<span class="line">                <span style="color: #9cdcfe;">$bit</span><span style="color: #d4d4d4;">           = </span><span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$cursor</span><span style="color: #d4d4d4;">++];</span></span>
<span class="line">                <span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">] = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">forceSign</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">], </span><span style="color: #9cdcfe;">$bit</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$cursor</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #dcdcaa;">count</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">)) {</span></span>
<span class="line">            <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\RuntimeException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'Image too small for the message.'</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">extract</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">string</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;"> = [];</span></span>
<span class="line">        <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;"> as </span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::EMBED_COORDS as [</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">]) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">[] = ( (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #dcdcaa;">round</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$u</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">]) &lt; </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;"> ) ? </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;"> : </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #9cdcfe;">$filtered</span><span style="color: #d4d4d4;">   = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">majority</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::REDUNDANCY);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$byteChunks</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">array_chunk</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$filtered</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">8</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;"> = </span><span style="color: #ce9178;">''</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">        <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$byteChunks</span><span style="color: #d4d4d4;"> as </span><span style="color: #9cdcfe;">$chunk</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #dcdcaa;">count</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$chunk</span><span style="color: #d4d4d4;">) &lt; </span><span style="color: #b5cea8;">8</span><span style="color: #d4d4d4;">) </span><span style="color: #c586c0;">break</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">            <span style="color: #9cdcfe;">$ch</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">chr</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">bitsToInt</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$chunk</span><span style="color: #d4d4d4;">));</span></span>
<span class="line">            <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$ch</span><span style="color: #d4d4d4;"> === </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::TERMINATOR) </span><span style="color: #c586c0;">break</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">            <span style="color: #9cdcfe;">$out</span> <span style="color: #d4d4d4;">.=</span> <span style="color: #9cdcfe;">$ch</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/* ------------------------------------------------------------------</span></span>
<span class="line"><span style="color: #6a9955;">       Private helpers</span></span>
<span class="line"><span style="color: #6a9955;">    -------------------------------------------------------------------*/</span></span>

<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">stringToBitStream</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$s</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">array</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;"> = [];</span></span>
<span class="line">        <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #dcdcaa;">str_split</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$s</span><span style="color: #d4d4d4;">) as </span><span style="color: #9cdcfe;">$c</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #9cdcfe;">$byte</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">ord</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$c</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">            <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">7</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;"> &gt;= </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; --</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$bit</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$byte</span><span style="color: #d4d4d4;"> &gt;&gt; </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">) &amp; </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">array_merge</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">array_fill</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::REDUNDANCY, </span><span style="color: #9cdcfe;">$bit</span><span style="color: #d4d4d4;">));</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">majority</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">$group</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">array</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;"> = [];</span></span>
<span class="line">        <span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;"> (</span><span style="color: #dcdcaa;">array_chunk</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$group</span><span style="color: #d4d4d4;">) as </span><span style="color: #9cdcfe;">$chunk</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;">[] = (</span><span style="color: #dcdcaa;">array_sum</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$chunk</span><span style="color: #d4d4d4;">) &gt; </span><span style="color: #9cdcfe;">$group</span><span style="color: #d4d4d4;"> / </span><span style="color: #b5cea8;">2</span><span style="color: #d4d4d4;">) ? </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;"> : </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">bitsToInt</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">int</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #dcdcaa;">array_reduce</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$bits</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">fn</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">) =&gt; (</span><span style="color: #9cdcfe;">$v</span><span style="color: #d4d4d4;"> &lt;&lt; </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">) | </span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Forces the sign of the coefficient:</span></span>
<span class="line"><span style="color: #6a9955;">     *   bit 0 → positive   (&gt;= +minAmp)</span></span>
<span class="line"><span style="color: #6a9955;">     *   bit 1 → negative   (&lt;= -minAmp)</span></span>
<span class="line"><span style="color: #6a9955;">     * so the quantizer cannot flip it.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">forceSign</span><span style="color: #d4d4d4;">(</span></span>
<span class="line">        <span style="color: #569cd6;">float</span> <span style="color: #9cdcfe;">$coeff</span><span style="color: #d4d4d4;">,</span></span>
<span class="line">        <span style="color: #569cd6;">int</span>   <span style="color: #9cdcfe;">$bit</span><span style="color: #d4d4d4;">,</span></span>
<span class="line">        <span style="color: #569cd6;">int</span>   <span style="color: #9cdcfe;">$minAmp</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">80</span><span style="color: #d4d4d4;">,   </span><span style="color: #6a9955;">// safe amplitude &gt; luminance quantization divisors</span></span>
<span class="line">        <span style="color: #569cd6;">int</span>   <span style="color: #9cdcfe;">$boost</span><span style="color: #d4d4d4;">  = </span><span style="color: #b5cea8;">64</span>    <span style="color: #6a9955;">// further offset to move away from zero</span></span>
<span class="line"><span style="color: #d4d4d4;">    ): </span><span style="color: #569cd6;">float</span><span style="color: #d4d4d4;"> {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> = (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #dcdcaa;">round</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$coeff</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Enforce minimum amplitude</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #dcdcaa;">abs</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;">) &lt; </span><span style="color: #9cdcfe;">$minAmp</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> &gt;= </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) ? </span><span style="color: #9cdcfe;">$minAmp</span><span style="color: #d4d4d4;"> : -</span><span style="color: #9cdcfe;">$minAmp</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #6a9955;">// Set the sign according to the bit</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$bit</span><span style="color: #d4d4d4;"> === </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;"> &amp;&amp; </span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) </span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">abs</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$bit</span><span style="color: #d4d4d4;"> === </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;"> &amp;&amp; </span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> &gt; </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) </span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> = -</span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">        <span style="color: #6a9955;">// Push further from zero: +/-boost (preserving the sign)</span></span>
<span class="line">        <span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> += (</span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;"> &gt; </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">) ? </span><span style="color: #9cdcfe;">$boost</span><span style="color: #d4d4d4;"> : -</span><span style="color: #9cdcfe;">$boost</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">        <span style="color: #c586c0;">return</span><span style="color: #d4d4d4;"> (</span><span style="color: #569cd6;">float</span><span style="color: #d4d4d4;">) </span><span style="color: #9cdcfe;">$rounded</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">__construct</span><span style="color: #d4d4d4;">() {}</span></span>
<span class="line"><span style="color: #d4d4d4;">}</span></span></code></pre>
</div>
<p>When the embed method is called, the caller provides the array of DCT blocks and the message to be embedded. The method converts each character into 8 bits, and each bit is replicated 25 times (as specified by the REDUNDANCY constant), so that it can later be read using a majority vote and withstand alterations due to JPEG compression. The resulting redundant bitstream flows block by block and relies on 8 <strong>low-frequency</strong> coordinates defined in the constant array EMBED_COORDS. These positions were chosen because they do not affect visual perception but tend to survive even moderate JPEG compressions (quality 60–80).   </p>
<p>For each selected coefficient, the private function forceSign() enforces a specific sign: positive to represent 0, negative to represent 1. This approach—more robust than using parity—ensures that the information remains readable even after JPEG quantization.<br />If the image does not contain enough blocks to represent the entire message, an exception is thrown with an explicit message.  </p>
<p>The inverse process is handled by the extract method. Here too, the class reads the sign of the same 8 coefficients in each block, reconstructing a long vector of zeros and ones. The bits are then grouped according to the redundancy factor and passed through a majority vote function (majority()), which recovers the original bit. Every group of 8 bits yields one character, until a NUL terminator is encountered, signaling the end of the message. The resulting sequence of characters forms the original message.    </p>
<p>The helper functions stringToBitStream, bitsToInt, and majority encapsulate binary-level details, keeping the public methods short and readable. Like the Dct class, Watermarker is also stateless and cannot be instantiated: all methods are static. This functional approach facilitates automated testing and use in concurrent environments.<br />In summary, the Watermarker class concretely implements the mathematical principles described earlier: cyclic redundancy, bit encoding via sign, explicit message termination—all in a compact and robust flow designed to survive the typical transformations of images published online.   </p>
<h2 class="wp-block-heading">ImageIO: from the image to DCT blocks and back</h2>
<p>The ImageIO class is the bridge between the mathematical theory and the actual JPEG file.</p>


<div class="wp-block-kevinbatdorf-code-block-pro" style="font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono">
<pre class="shiki dark-plus" style="background-color: #1e1e1e;" tabindex="0"><code><span class="line"><span style="color: #d4d4d4;">&lt;?php</span></span>

<span class="line"><span style="color: #c586c0;">declare</span><span style="color: #d4d4d4;">(strict_types=</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>

<span class="line"><span style="color: #569cd6;">namespace</span> <span style="color: #4ec9b0;">Renor\Watermark</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #569cd6;">use</span> <span style="color: #4ec9b0;">GdImage</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;"> * Class ImageIO</span></span>
<span class="line"><span style="color: #6a9955;"> *</span></span>
<span class="line"><span style="color: #6a9955;"> * - Loads a JPEG image.</span></span>
<span class="line"><span style="color: #6a9955;"> * - Converts RGB to the Y (luminance) channel.</span></span>
<span class="line"><span style="color: #6a9955;"> * - Splits it into 8x8 blocks and applies DCT/IDCT.</span></span>
<span class="line"><span style="color: #6a9955;"> * - Overwrites the Y channel with the embedded watermark, merging with the original CbCr.</span></span>
<span class="line"><span style="color: #6a9955;"> * - Saves the final image as JPEG with a specified quality.</span></span>
<span class="line"><span style="color: #6a9955;"> */</span></span>
<span class="line"><span style="color: #569cd6;">final</span> <span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">ImageIO</span></span>
<span class="line"><span style="color: #d4d4d4;">{</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> BLOCK_SIZE = </span><span style="color: #b5cea8;">8</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> MIN_WIDTH = </span><span style="color: #b5cea8;">200</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">const</span><span style="color: #d4d4d4;"> MIN_HEIGHT = </span><span style="color: #b5cea8;">200</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Embed a watermark in a JPEG image and save the result.</span></span>
<span class="line"><span style="color: #6a9955;">     *</span></span>
<span class="line"><span style="color: #6a9955;">     * </span><span style="color: #569cd6;">@param</span> <span style="color: #569cd6;">string</span><span style="color: #6a9955;"> $src      Path to the original JPEG</span></span>
<span class="line"><span style="color: #6a9955;">     * </span><span style="color: #569cd6;">@param</span> <span style="color: #569cd6;">string</span><span style="color: #6a9955;"> $dest     Output path for the watermarked image</span></span>
<span class="line"><span style="color: #6a9955;">     * </span><span style="color: #569cd6;">@param</span> <span style="color: #569cd6;">string</span><span style="color: #6a9955;"> $message  ASCII watermark (up to 10 chars)</span></span>
<span class="line"><span style="color: #6a9955;">     * </span><span style="color: #569cd6;">@param</span> <span style="color: #569cd6;">int</span><span style="color: #6a9955;">    $quality  JPEG quality in [0..100], default 90</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">embed</span><span style="color: #d4d4d4;">(</span></span>
<span class="line">        <span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;">,</span></span>
<span class="line">        <span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$dest</span><span style="color: #d4d4d4;">,</span></span>
<span class="line">        <span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$message</span><span style="color: #d4d4d4;">,</span></span>
<span class="line">        <span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">$quality</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">90</span></span>
<span class="line"><span style="color: #d4d4d4;">    ): </span><span style="color: #569cd6;">void</span><span style="color: #d4d4d4;"> {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">loadJpeg</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesx</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Ensure a minimum image size</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::MIN_WIDTH || </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::MIN_HEIGHT) {</span></span>
<span class="line">            <span style="color: #dcdcaa;">imagedestroy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">            <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\RuntimeException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">"Image must be at least 200x200 for robust embedding."</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #6a9955;">// Convert RGB → Y</span></span>
<span class="line">        <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">rgbToY</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Split into 8x8 blocks, apply forward DCT</span></span>
<span class="line">        <span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">forwardBlocks</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Embed the message into the DCT coefficients</span></span>
<span class="line">        <span style="color: #4ec9b0;">Watermarker</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">embed</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$message</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Reconstruct the Y channel from the inverse DCT</span></span>
<span class="line">        <span style="color: #9cdcfe;">$Ywm</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">inverseBlocks</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Merge the modified Y channel and save as JPEG</span></span>
<span class="line">        <span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">mergeAndSave</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$Ywm</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$dest</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$quality</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #dcdcaa;">imagedestroy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Extract the watermark from a JPEG image.</span></span>
<span class="line"><span style="color: #6a9955;">     *</span></span>
<span class="line"><span style="color: #6a9955;">     * </span><span style="color: #569cd6;">@param</span> <span style="color: #569cd6;">string</span><span style="color: #6a9955;"> $src Path to the watermarked JPEG</span></span>
<span class="line"><span style="color: #6a9955;">     * </span><span style="color: #569cd6;">@return</span> <span style="color: #569cd6;">string</span><span style="color: #6a9955;">     The recovered ASCII message</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">extract</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">string</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">loadJpeg</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesx</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::MIN_WIDTH || </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::MIN_HEIGHT) {</span></span>
<span class="line">            <span style="color: #dcdcaa;">imagedestroy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">            <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\RuntimeException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">"Image is too small (&lt;200x200). It may not contain a robust watermark."</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #6a9955;">// Convert to Y</span></span>
<span class="line">        <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">rgbToY</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Apply DCT on 8x8 blocks</span></span>
<span class="line">        <span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">forwardBlocks</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// Extract the message from the DCT blocks</span></span>
<span class="line">        <span style="color: #9cdcfe;">$msg</span><span style="color: #d4d4d4;"> = </span><span style="color: #4ec9b0;">Watermarker</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">extract</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #dcdcaa;">imagedestroy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$msg</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/* -------------------------------------------------------------------------</span></span>
<span class="line"><span style="color: #6a9955;">       INTERNAL METHODS</span></span>
<span class="line"><span style="color: #6a9955;">    -------------------------------------------------------------------------- */</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Loads a JPEG image from disk or throws an exception if invalid.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">loadJpeg</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$path</span><span style="color: #d4d4d4;">): </span><span style="color: #4ec9b0;">GdImage</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (!</span><span style="color: #dcdcaa;">is_file</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$path</span><span style="color: #d4d4d4;">)) {</span></span>
<span class="line">            <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\RuntimeException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">"File not found: </span><span style="color: #9cdcfe;">$path</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;"> = @</span><span style="color: #dcdcaa;">imagecreatefromjpeg</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$path</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (!</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">            <span style="color: #c586c0;">throw</span> <span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\RuntimeException</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">"Invalid JPEG: </span><span style="color: #9cdcfe;">$path</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Converts an RGB image to a 2D Y (luminance) matrix.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">rgbToY</span><span style="color: #d4d4d4;">(</span><span style="color: #4ec9b0;">GdImage</span> <span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">array</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesx</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">array_fill</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">array_fill</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">));</span></span>

<span class="line">        <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">            <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagecolorat</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$im</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                <span style="color: #9cdcfe;">$r</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> &gt;&gt; </span><span style="color: #b5cea8;">16</span><span style="color: #d4d4d4;">) &amp; </span><span style="color: #b5cea8;">0xFF</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #9cdcfe;">$g</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> &gt;&gt; </span><span style="color: #b5cea8;">8</span><span style="color: #d4d4d4;">) &amp; </span><span style="color: #b5cea8;">0xFF</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> &amp; </span><span style="color: #b5cea8;">0xFF</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #6a9955;">// ITU-R BT.601 luma formula</span></span>
<span class="line">                <span style="color: #9cdcfe;">$luma</span><span style="color: #d4d4d4;"> = (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #dcdcaa;">round</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0.299</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$r</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">0.587</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$g</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">0.114</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">] = </span><span style="color: #dcdcaa;">max</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">min</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">255</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$luma</span><span style="color: #d4d4d4;">));</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Splits the Y matrix into 8x8 blocks and applies forward DCT.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">public</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">forwardBlocks</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">array</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;"> = [];</span></span>
<span class="line">        <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> += </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE) {</span></span>
<span class="line">            <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> += </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;"> = [];</span></span>
<span class="line">                <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE; </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">                    <span style="color: #9cdcfe;">$row</span><span style="color: #d4d4d4;"> = [];</span></span>
<span class="line">                    <span style="color: #9cdcfe;">$srcY</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">min</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> + </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                    <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE; </span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$srcX</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">min</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> + </span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$row</span><span style="color: #d4d4d4;">[] = </span><span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$srcY</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$srcX</span><span style="color: #d4d4d4;">];</span></span>
<span class="line"><span style="color: #d4d4d4;">                    }</span></span>
<span class="line">                    <span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">[] = </span><span style="color: #9cdcfe;">$row</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">                }</span></span>
<span class="line">                <span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">[] = </span><span style="color: #4ec9b0;">Dct</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">forward</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$block</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>
<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Applies inverse DCT to each 8x8 block and rebuilds the Y matrix.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">inverseBlocks</span><span style="color: #d4d4d4;">(</span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">array</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">array_fill</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">array_fill</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">, </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">));</span></span>
<span class="line">        <span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">        <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> += </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE) {</span></span>
<span class="line">            <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> += </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$coeffBlock</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$blocks</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">++];</span></span>
<span class="line">                <span style="color: #9cdcfe;">$pixBlock</span><span style="color: #d4d4d4;"> = </span><span style="color: #4ec9b0;">Dct</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">inverse</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$coeffBlock</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">                <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE; </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">                    <span style="color: #9cdcfe;">$dstY</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$by</span><span style="color: #d4d4d4;"> + </span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                    <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$dstY</span><span style="color: #d4d4d4;"> &gt;= </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">) </span><span style="color: #c586c0;">break</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                    <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #569cd6;">self</span><span style="color: #d4d4d4;">::BLOCK_SIZE; </span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$dstX</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$bx</span><span style="color: #d4d4d4;"> + </span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                        <span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$dstX</span><span style="color: #d4d4d4;"> &gt;= </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">) </span><span style="color: #c586c0;">break</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                        <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$dstY</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$dstX</span><span style="color: #d4d4d4;">] = </span><span style="color: #9cdcfe;">$pixBlock</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$j</span><span style="color: #d4d4d4;">];</span></span>
<span class="line"><span style="color: #d4d4d4;">                    }</span></span>
<span class="line"><span style="color: #d4d4d4;">                }</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #c586c0;">return</span> <span style="color: #9cdcfe;">$Y</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Merges the watermarked Y channel with the original CbCr, then saves the final JPEG.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">static</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">mergeAndSave</span><span style="color: #d4d4d4;">(</span><span style="color: #4ec9b0;">GdImage</span> <span style="color: #9cdcfe;">$orig</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">array</span> <span style="color: #9cdcfe;">$Ywm</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">string</span> <span style="color: #9cdcfe;">$dest</span><span style="color: #d4d4d4;">, </span><span style="color: #569cd6;">int</span> <span style="color: #9cdcfe;">$quality</span><span style="color: #d4d4d4;">): </span><span style="color: #569cd6;">void</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line">        <span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesx</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$orig</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagesy</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$orig</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// 1) Overwrite the RGB pixels of the GDImage using the new Y</span></span>
<span class="line">        <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$h</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">            <span style="color: #c586c0;">for</span><span style="color: #d4d4d4;"> (</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;"> &lt; </span><span style="color: #9cdcfe;">$w</span><span style="color: #d4d4d4;">; </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">++) {</span></span>
<span class="line">                <span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagecolorat</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$orig</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                <span style="color: #9cdcfe;">$r</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> &gt;&gt; </span><span style="color: #b5cea8;">16</span><span style="color: #d4d4d4;">) &amp; </span><span style="color: #b5cea8;">0xFF</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #9cdcfe;">$g</span><span style="color: #d4d4d4;"> = (</span><span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> &gt;&gt; </span><span style="color: #b5cea8;">8</span><span style="color: #d4d4d4;">) &amp; </span><span style="color: #b5cea8;">0xFF</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$rgb</span><span style="color: #d4d4d4;"> &amp; </span><span style="color: #b5cea8;">0xFF</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">                <span style="color: #9cdcfe;">$cb</span><span style="color: #d4d4d4;"> = -</span><span style="color: #b5cea8;">0.169</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$r</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">0.331</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$g</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">0.5</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">128</span><span style="color: #d4d4d4;">;</span></span>
<span class="line">                <span style="color: #9cdcfe;">$cr</span><span style="color: #d4d4d4;"> = </span><span style="color: #b5cea8;">0.5</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$r</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">0.419</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$g</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">0.081</span><span style="color: #d4d4d4;"> * </span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">128</span><span style="color: #d4d4d4;">;</span></span>

<span class="line">                <span style="color: #9cdcfe;">$Yval</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$Ywm</span><span style="color: #d4d4d4;">[</span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">][</span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">];</span></span>

<span class="line">                <span style="color: #9cdcfe;">$R</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$Yval</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">1.402</span><span style="color: #d4d4d4;"> * (</span><span style="color: #9cdcfe;">$cr</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">128</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                <span style="color: #9cdcfe;">$G</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$Yval</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">0.344136</span><span style="color: #d4d4d4;"> * (</span><span style="color: #9cdcfe;">$cb</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">128</span><span style="color: #d4d4d4;">) - </span><span style="color: #b5cea8;">0.714136</span><span style="color: #d4d4d4;"> * (</span><span style="color: #9cdcfe;">$cr</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">128</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                <span style="color: #9cdcfe;">$B</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$Yval</span><span style="color: #d4d4d4;"> + </span><span style="color: #b5cea8;">1.772</span><span style="color: #d4d4d4;"> * (</span><span style="color: #9cdcfe;">$cb</span><span style="color: #d4d4d4;"> - </span><span style="color: #b5cea8;">128</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">                <span style="color: #9cdcfe;">$R</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">max</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">min</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">255</span><span style="color: #d4d4d4;">, (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #dcdcaa;">round</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$R</span><span style="color: #d4d4d4;">)));</span></span>
<span class="line">                <span style="color: #9cdcfe;">$G</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">max</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">min</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">255</span><span style="color: #d4d4d4;">, (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #dcdcaa;">round</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$G</span><span style="color: #d4d4d4;">)));</span></span>
<span class="line">                <span style="color: #9cdcfe;">$B</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">max</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">0</span><span style="color: #d4d4d4;">, </span><span style="color: #dcdcaa;">min</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">255</span><span style="color: #d4d4d4;">, (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #dcdcaa;">round</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$B</span><span style="color: #d4d4d4;">)));</span></span>

<span class="line">                <span style="color: #9cdcfe;">$color</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">imagecolorallocate</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$orig</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$R</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$G</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$B</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">                <span style="color: #dcdcaa;">imagesetpixel</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$orig</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$x</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$y</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$color</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">            }</span></span>
<span class="line"><span style="color: #d4d4d4;">        }</span></span>

<span class="line">        <span style="color: #6a9955;">// 2) Convert GDImage to a PNG blob in memory (lossless)</span></span>
<span class="line">        <span style="color: #dcdcaa;">ob_start</span><span style="color: #d4d4d4;">();</span></span>
<span class="line">        <span style="color: #dcdcaa;">imagepng</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$orig</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$pngData</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">ob_get_clean</span><span style="color: #d4d4d4;">();</span></span>

<span class="line">        <span style="color: #6a9955;">// 3) Create an Imagick object from the PNG blob</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;"> = </span><span style="color: #569cd6;">new</span> <span style="color: #4ec9b0;">\Imagick</span><span style="color: #d4d4d4;">();</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">readImageBlob</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$pngData</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// 4) Set desired JPEG parameters</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">setOption</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'jpeg:sampling-factor'</span><span style="color: #d4d4d4;">, </span><span style="color: #ce9178;">'1x1'</span><span style="color: #d4d4d4;">); </span><span style="color: #6a9955;">// disable chroma subsampling</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">setImageCompressionQuality</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$quality</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">setImageCompression</span><span style="color: #d4d4d4;">(</span><span style="color: #4ec9b0;">\Imagick</span><span style="color: #d4d4d4;">::COMPRESSION_JPEG);</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">setImageFormat</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'jpeg'</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// 5) Save the file</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">writeImage</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$dest</span><span style="color: #d4d4d4;">);</span></span>

<span class="line">        <span style="color: #6a9955;">// 6) Cleanup</span></span>
<span class="line">        <span style="color: #9cdcfe;">$imagick</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">destroy</span><span style="color: #d4d4d4;">();</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>

<span class="line">    <span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;">     * Prevents instantiation.</span></span>
<span class="line"><span style="color: #6a9955;">     */</span></span>
<span class="line">    <span style="color: #569cd6;">private</span> <span style="color: #569cd6;">function</span> <span style="color: #dcdcaa;">__construct</span><span style="color: #d4d4d4;">()</span></span>
<span class="line"><span style="color: #d4d4d4;">    {</span></span>
<span class="line"><span style="color: #d4d4d4;">    }</span></span>
<span class="line"><span style="color: #d4d4d4;">}</span></span></code></pre>
</div>
<p>When the static embed method is invoked, the code opens the image using the GD extension and extracts the Y (luminance) component— the only one involved in watermarking—while leaving the chrominance channels Cb and Cr untouched, to ensure the watermark remains invisible to the human eye.<br />The Y matrix is then divided, row by row, into a grid of 8×8 blocks. Each block is transformed into the frequency domain using Dct::forward, enabling the Watermarker class to embed the message bits by <strong>forcing the sign</strong> of 8 specifically chosen <strong>low-frequency</strong> coefficients.  </p>
<p>Once the embedding is complete, ImageIO converts each block back into pixels using Dct::inverse, reconstructing a new, watermarked Y matrix. At this point, the class recalculates the RGB values by combining the modified luminance with the original chrominance components: for each pixel, it applies the inverse color space formulas according to the ITU-R BT.601 standard, ensuring that the results stay within the 0–255 range. </p>
<p>The complete bitmap is finally saved as a JPEG at the desired quality, using Imagick to disable chroma subsampling and maximize fidelity. The result is a file that is visually identical to the original but contains, within its DCT coefficients, an invisible yet resilient textual signature. </p>
<p>The inverse process, handled by the extract method, performs the opposite operation: it reloads the image, isolates the luminance channel, divides it into blocks, computes the DCT of each block, and passes the sequence of coefficients to Watermarker::extract. This method reads the <strong>sign</strong> of the coefficients, applies majority voting across the repetitions of each bit, and reconstructs the original string up to the NUL termination character. </p>
<p>All I/O logic—file opening, color conversion, and JPEG saving—is confined within the ImageIO class, while Dct and Watermarker remain pure mathematical functions. This clear separation of responsibilities promotes code modularity, testability, and easy integration into any image processing pipeline. </p>
<h2 class="wp-block-heading">CLI test</h2>
<h3 class="wp-block-heading">/embed.php</h3>
<p>Embedding a watermark in an image</p>


<div class="wp-block-kevinbatdorf-code-block-pro" style="font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono">
<pre class="shiki dark-plus" style="background-color: #1e1e1e;" tabindex="0"><code><span class="line"><span style="color: #6a9955;">#!/usr/bin/env php</span></span>
<span class="line"><span style="color: #d4d4d4;">&lt;?php</span></span>

<span class="line"><span style="color: #6a9955;">// Set memory limit to 1GB</span></span>
<span class="line"><span style="color: #dcdcaa;">ini_set</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'memory_limit'</span><span style="color: #d4d4d4;">, </span><span style="color: #ce9178;">'1024M'</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #c586c0;">require</span> <span style="color: #569cd6;">__DIR__</span> <span style="color: #d4d4d4;">.</span> <span style="color: #ce9178;">'/vendor/autoload.php'</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #569cd6;">use</span><span style="color: #d4d4d4;"> Renor\Watermark\</span><span style="color: #4ec9b0;">ImageIO</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;"> * This command-line script embeds a watermark (up to 10 characters)</span></span>
<span class="line"><span style="color: #6a9955;"> * into a JPEG and saves the result. By default, it uses quality=90.</span></span>
<span class="line"><span style="color: #6a9955;"> *</span></span>
<span class="line"><span style="color: #6a9955;"> * Example usage:</span></span>
<span class="line"><span style="color: #6a9955;"> *   php embed.php --src=original.jpg --out=watermarked.jpg --msg="HELLO" --q=95</span></span>
<span class="line"><span style="color: #6a9955;"> *</span></span>
<span class="line"><span style="color: #6a9955;"> * Or simply:</span></span>
<span class="line"><span style="color: #6a9955;"> *   php embed.php --src=original.jpg --out=watermarked.jpg</span></span>
<span class="line"><span style="color: #6a9955;"> *   (defaults to msg="DEFAULT" and quality=90)</span></span>
<span class="line"><span style="color: #6a9955;"> */</span></span>

<span class="line"><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">getopt</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">''</span><span style="color: #d4d4d4;">, [</span><span style="color: #ce9178;">'src:'</span><span style="color: #d4d4d4;">, </span><span style="color: #ce9178;">'out:'</span><span style="color: #d4d4d4;">, </span><span style="color: #ce9178;">'msg::'</span><span style="color: #d4d4d4;">, </span><span style="color: #ce9178;">'q::'</span><span style="color: #d4d4d4;">]);</span></span>
<span class="line"><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;">[</span><span style="color: #ce9178;">'src'</span><span style="color: #d4d4d4;">] ?? </span><span style="color: #569cd6;">null</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;">[</span><span style="color: #ce9178;">'out'</span><span style="color: #d4d4d4;">] ?? </span><span style="color: #569cd6;">null</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #9cdcfe;">$msg</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;">[</span><span style="color: #ce9178;">'msg'</span><span style="color: #d4d4d4;">] ?? </span><span style="color: #ce9178;">'DEFAULT'</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #9cdcfe;">$q</span><span style="color: #d4d4d4;">   = </span><span style="color: #dcdcaa;">isset</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;">[</span><span style="color: #ce9178;">'q'</span><span style="color: #d4d4d4;">]) ? (</span><span style="color: #569cd6;">int</span><span style="color: #d4d4d4;">) </span><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;">[</span><span style="color: #ce9178;">'q'</span><span style="color: #d4d4d4;">] : </span><span style="color: #b5cea8;">90</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (!</span><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;"> || !</span><span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">    <span style="color: #dcdcaa;">fwrite</span><span style="color: #d4d4d4;">(STDERR, </span><span style="color: #ce9178;">"Usage: embed --src=INPUT.jpg --out=OUTPUT.jpg [--msg=STRING] [--q=90]</span><span style="color: #d7ba7d;">\n</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">    <span style="color: #c586c0;">exit</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">}</span></span>

<span class="line"><span style="color: #c586c0;">try</span><span style="color: #d4d4d4;"> {</span></span>
<span class="line">    <span style="color: #4ec9b0;">ImageIO</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">embed</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$out</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$msg</span><span style="color: #d4d4d4;">, </span><span style="color: #9cdcfe;">$q</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">    <span style="color: #dcdcaa;">echo</span> <span style="color: #ce9178;">"Watermark </span><span style="color: #d7ba7d;">\"</span><span style="color: #9cdcfe;">$msg</span><span style="color: #d7ba7d;">\"</span><span style="color: #ce9178;"> embedded successfully into </span><span style="color: #9cdcfe;">$out</span><span style="color: #ce9178;"> at quality </span><span style="color: #9cdcfe;">$q</span><span style="color: #d7ba7d;">\n</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">} </span><span style="color: #c586c0;">catch</span><span style="color: #d4d4d4;"> (\</span><span style="color: #4ec9b0;">Exception</span> <span style="color: #9cdcfe;">$e</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">    <span style="color: #dcdcaa;">fwrite</span><span style="color: #d4d4d4;">(STDERR, </span><span style="color: #ce9178;">"Error: "</span> <span style="color: #d4d4d4;">.</span> <span style="color: #9cdcfe;">$e</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">getMessage</span><span style="color: #d4d4d4;">() </span><span style="color: #d4d4d4;">.</span> <span style="color: #ce9178;">"</span><span style="color: #d7ba7d;">\n</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">    <span style="color: #c586c0;">exit</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">}</span></span></code></pre>
</div>
<p>In this CLI script (embed.php), we define the message to embed in the image via the &#8211;msg option and call the static method ImageIO::embed() from the ImageIO class. The script accepts the following parameters: </p>
<p>&#8211;q (optional): the JPEG quality for saving (default: 90).</p>
<p>&#8211;src: the path to the source JPEG image (e.g., original.jpg);</p>
<p>&#8211;out: the path to the destination JPEG file that will contain the watermark;</p>
<p>&#8211;msg (optional): the ASCII text to embed (max 10 characters);</p>

<pre class="wp-block-code"><code>php embed.php --src=original.jpg --out=watermarked.jpg --msg="RENOR" --q=95</code></pre>
<p>it will return:</p>

<pre class="wp-block-code"><code>Watermark "RENOR" embedded successfully into watermarked.jpg at quality 95</code></pre>
<h2 class="wp-block-heading">/extract.php</h2>
<p>From here, we extract the watermark text contained in the watermarked.jpg image just created by embed.php.</p>


<div class="wp-block-kevinbatdorf-code-block-pro" style="font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono">
<pre class="shiki dark-plus" style="background-color: #1e1e1e;" tabindex="0"><code><span class="line"><span style="color: #6a9955;">#!/usr/bin/env php</span></span>
<span class="line"><span style="color: #d4d4d4;">&lt;?php</span></span>

<span class="line"><span style="color: #6a9955;">// Set memory limit to 1GB</span></span>
<span class="line"><span style="color: #dcdcaa;">ini_set</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">'memory_limit'</span><span style="color: #d4d4d4;">, </span><span style="color: #ce9178;">'1024M'</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #c586c0;">require</span> <span style="color: #569cd6;">__DIR__</span> <span style="color: #d4d4d4;">.</span> <span style="color: #ce9178;">'/vendor/autoload.php'</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #569cd6;">use</span><span style="color: #d4d4d4;"> Renor\Watermark\</span><span style="color: #4ec9b0;">ImageIO</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #6a9955;">/**</span></span>
<span class="line"><span style="color: #6a9955;"> * This command-line script extracts a watermark from a JPEG.</span></span>
<span class="line"><span style="color: #6a9955;"> *</span></span>
<span class="line"><span style="color: #6a9955;"> * Usage:</span></span>
<span class="line"><span style="color: #6a9955;"> *   php extract.php --src=watermarked.jpg</span></span>
<span class="line"><span style="color: #6a9955;"> */</span></span>

<span class="line"><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;"> = </span><span style="color: #dcdcaa;">getopt</span><span style="color: #d4d4d4;">(</span><span style="color: #ce9178;">''</span><span style="color: #d4d4d4;">, [</span><span style="color: #ce9178;">'src:'</span><span style="color: #d4d4d4;">]);</span></span>
<span class="line"><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;"> = </span><span style="color: #9cdcfe;">$options</span><span style="color: #d4d4d4;">[</span><span style="color: #ce9178;">'src'</span><span style="color: #d4d4d4;">] ?? </span><span style="color: #569cd6;">null</span><span style="color: #d4d4d4;">;</span></span>

<span class="line"><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;"> (!</span><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">    <span style="color: #dcdcaa;">fwrite</span><span style="color: #d4d4d4;">(STDERR, </span><span style="color: #ce9178;">"Usage: extract --src=IMAGE.jpg</span><span style="color: #d7ba7d;">\n</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">    <span style="color: #c586c0;">exit</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">}</span></span>

<span class="line"><span style="color: #c586c0;">try</span><span style="color: #d4d4d4;"> {</span></span>
<span class="line">    <span style="color: #9cdcfe;">$msg</span><span style="color: #d4d4d4;"> = </span><span style="color: #4ec9b0;">ImageIO</span><span style="color: #d4d4d4;">::</span><span style="color: #dcdcaa;">extract</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$src</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">    <span style="color: #dcdcaa;">echo</span> <span style="color: #ce9178;">"Recovered message: </span><span style="color: #d7ba7d;">\"</span><span style="color: #9cdcfe;">$msg</span><span style="color: #d7ba7d;">\"\n</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">;</span></span>
<span class="line"><span style="color: #d4d4d4;">} </span><span style="color: #c586c0;">catch</span><span style="color: #d4d4d4;"> (\</span><span style="color: #4ec9b0;">Exception</span> <span style="color: #9cdcfe;">$e</span><span style="color: #d4d4d4;">) {</span></span>
<span class="line">    <span style="color: #dcdcaa;">fwrite</span><span style="color: #d4d4d4;">(STDERR, </span><span style="color: #ce9178;">"Error: "</span> <span style="color: #d4d4d4;">.</span> <span style="color: #9cdcfe;">$e</span><span style="color: #d4d4d4;">-&gt;</span><span style="color: #dcdcaa;">getMessage</span><span style="color: #d4d4d4;">() </span><span style="color: #d4d4d4;">.</span> <span style="color: #ce9178;">"</span><span style="color: #d7ba7d;">\n</span><span style="color: #ce9178;">"</span><span style="color: #d4d4d4;">);</span></span>
<span class="line">    <span style="color: #c586c0;">exit</span><span style="color: #d4d4d4;">(</span><span style="color: #b5cea8;">1</span><span style="color: #d4d4d4;">);</span></span>
<span class="line"><span style="color: #d4d4d4;">}</span></span></code></pre>
</div>
<p>The extract.php script completes the invisible watermark usage cycle by allowing the extraction of the previously embedded message. It is a simple command-line script which, given a watermarked JPEG file, reads the hidden content by decoding the sign of specific modified DCT coefficients.<br />The message—robustly recovered even after medium-to-high quality JPEG compressions—is reconstructed using majority voting on the repeated bits.<br />Just run the command with the &#8211;src option to specify the image to analyze, and the script will return the originally embedded string in the terminal.   </p>
<p>We can run the command:</p>

<pre class="wp-block-code"><code>php extract.php --src=watermarked.jpg  </code></pre>
<p>we&#8217;ll get:</p>

<pre class="wp-block-code"><code>Recovered message: "RENOR"</code></pre>
<p>It is also worth analyzing photographs:</p>

<figure class="wp-block-image size-large"><img decoding="async" src="https://renor.it/wp-content/uploads/2025/05/test-1024x342.webp" alt="" class="wp-image-360"/></figure>
<p>On the left, the original image; on the right, the watermarked one.</p>
<p>We can observe that, as thoroughly described, there are indeed no elements visible to the naked eye that can be traced back to the application of a watermark, which nevertheless remains fully decodable using the extract script. </p>
<p>Clearly, the project is purely demonstrative, and using it at an enterprise level or in production environments would require further refinement.</p>

<p><strong>[starbox] </strong></p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/invisible-watermark-in-jpeg-with-php-8/">Invisible Watermark in JPEG with PHP 8</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>What the h*ll are you looking for? A backend or a frontend?</title>
		<link>https://renor.it/en/blog/software-development-programming/what-the-hll-are-you-looking-for-a-backend-or-a-frontend/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Wed, 12 Oct 2022 20:54:18 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[backend]]></category>
		<category><![CDATA[frontend]]></category>
		<category><![CDATA[fullstack]]></category>
		<category><![CDATA[what are you looking for]]></category>
		<category><![CDATA[what do you want]]></category>
		<guid isPermaLink="false">https://renor.it/what-the-hll-are-you-looking-for-a-backend-or-a-frontend/</guid>

					<description><![CDATA[<p>As usual, I’m going in hard—being direct is in my nature… You probably figured that out from the title ???? Let’s start with the classic question IT engineers and developers ask themselves when reading certain job offers:“Do companies actually think that developers and/or IT engineers are all idiots?Or is it simply that they have people [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/what-the-hll-are-you-looking-for-a-backend-or-a-frontend/">What the h*ll are you looking for? A backend or a frontend?</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>As usual, I’m going in hard—being direct is in my nature… You probably figured that out from the title ???? </p>
<p>Let’s start with the classic question IT engineers and developers ask themselves when reading certain job offers:<br />“Do companies actually think that developers and/or IT engineers are all idiots?<br />Or is it simply that they have people as ignorant as goats—pardon the expression, borrowing from Vittorio Sgarbi—meaning that whoever’s handling their recruitment doesn’t even know the difference between a backend developer and a frontend developer?” </p>
<p>Honestly, I believe the reason should be sought elsewhere… Because the offered gross annual salary (RAL) fully justifies the reasoning. And who can blame them?  </p>
<h2>Absurd ads</h2>
<p>I take a random fresh ad, but I assure you that just looking around a bit you can find thousands of them…</p>
<p class="t-24 t-bold jobs-unified-top-card__job-title">Ad title: <strong>PHP Back-end Developer</strong></p>
<blockquote>
<p><em><strong>REQUIRED QUALIFICATIONS</strong></em></p>
<ul>
<li><em>Knowledge of object-oriented development principles</em></li>
<li><em>Experience in development with the PHP language</em></li>
<li><em>Knowledge of the main frameworks</em></li>
<li><em>Knowledge of software versioning models and tools (GIT/SVN)</em></li>
<li><em>Knowledge of the SQL language</em></li>
<li><em>Knowledge of the web and its protocols</em></li>
<li><em>Knowledge of operating systems such as Windows and Linux</em></li>
<li><em>Knowledge of HTML, CSS, and JavaScript</em></li>
<li><em>Good knowledge of the English language, both spoken and written</em></li>
<li><em>Proven problem-solving skills related to achieving results</em></li>
</ul>
<p><em><strong>PREFERRED REQUIREMENTS</strong></em></p>
<ul>
<li><em>Knowledge of the main web CMS: Drupal / WordPress / Magento / Adobe Commerce</em></li>
<li><em>Knowledge and experience in (full stack) web application development</em></li>
<li><em>Certifications obtained on enterprise products. Example: Adobe Commerce </em></li>
<li><em>Experience in implementing online payment systems and/or e-commerce solutions</em></li>
</ul>
</blockquote>
<h2>Confusion, ignorance, or opportunism?</h2>
<p>Now, even a blind person here would see that they are not looking for a Back-end developer, as the ad title unequivocally suggests… What they are actually seeking is a Full Stack developer, and the funny thing is that in the preferred requirements they state it clearly without any half-measures!! “Knowledge and experience in (full stack) web application development.” Moreover, right there in the required qualifications, the knowledge of HTML, CSS, and JavaScript stands out as clear as day.   </p>
<p>Personally, I would never apply to such a job posting, because I consider it an insult to my intelligence but above all to the entire profession.</p>
<p>Con tutto il rispetto, queste non sono posizioni per le quali si può aprire una ricerca rivolgendosi a persone completamente incompetenti, facendogli credere che gli asini volino. Sono ruoli in cui chi si candida deve essere una persona allenata a usare bene il cervello. Non puoi pensare, con l’obiettivo finale di pagare una “Ferrari” 10 euro, di prendere in giro uno sviluppatore che, al contrario di certi ciarlatani, conosce perfettamente quali competenze richiedano lo sviluppo front-end e quali il back-end; cercando di mascherare tutto facendo passare la ricerca di un Full Stack come quella di un Back-end o di un Front-end per pagarlo meno. Questo è un inganno ridicolo nel quale non cadrebbe neanche un bambino di 10 anni che deve finire!  </p>
<p>It would definitely be more appreciated and honest to write: “We are looking for a Full Stack developer, we offer this compensation.” This way, candidates are not misled, and respect for the required professionalism is demonstrated by setting clear and transparent expectations from the very beginning.</p>
<p>In this way, everyone would know exactly which role is being sought and, depending on the offer, could decide whether to apply or not, without wasting precious time. In many cases, the company would not receive any applications, and at that point, perhaps, it would realize that it is necessary to increase the compensation! </p>
<h2>Job offers to be ashamed of</h2>
<p>Not to mention all those job offers that demand absurd skills, ones that even NASA employees might not possess, for ridiculously low salaries… I have read ads from companies looking for expertise in Back-end PHP, .NET, JAVA, Python, version control systems (GIT, SVN, DevOps), container orchestration systems (Kubernetes, Docker), REST API and SOAP, various PHP frameworks including Symfony/Laravel, knowledge of ecosystems and API integration of AWS, Google Cloud, and Microsoft Azure services. For the front-end: HTML, CSS, JavaScript, jQuery, React, Vue.js, Angular, with a preferred requirement in hybrid app development using Cordova, Flutter, or Ionic… All this for the modest sum of 1,200 euros, ladies and gentlemen! I mean… Aren’t you even a little ashamed? It makes me laugh, but honestly, this is something to cry about!</p>
<p>I don’t want to get in the middle of this, and although I don’t consider myself the last wheel in the IT field—probably the second to last, but not the last—I personally know very high-level professionals I have worked and still work with, who probably struggle to even reach half of these required skills… And all this for a salary of 1,200 euros per month? <a title="Face with Tears of Joy emoji" href="https://en.wikipedia.org/wiki/Face_with_Tears_of_Joy_emoji">????</a><a title="Face with Tears of Joy emoji" href="https://en.wikipedia.org/wiki/Face_with_Tears_of_Joy_emoji">????</a><a title="Face with Tears of Joy emoji" href="https://en.wikipedia.org/wiki/Face_with_Tears_of_Joy_emoji">????</a><a title="Face with Tears of Joy emoji" href="https://en.wikipedia.org/wiki/Face_with_Tears_of_Joy_emoji">????</a><a title="Face with Tears of Joy emoji" href="https://en.wikipedia.org/wiki/Face_with_Tears_of_Joy_emoji">????</a><a title="Face with Tears of Joy emoji" href="https://en.wikipedia.org/wiki/Face_with_Tears_of_Joy_emoji">????</a><br />It’s truly unbelievable and frankly an insult both to those who work seriously and to the value of the profession itself.  </p>
<p>You are ridiculous!!! To all these crafty little opportunists, exploiters of people in difficulty, I sincerely dedicate a big f*** off.</p>
<h2>The responsibilities of entrepreneurs</h2>
<p>I believe entrepreneurs should pause to reflect with a strong sense of self-criticism and adopt a perspective grounded in very simple market laws…</p>
<p>I have a cousin who had the courage to leave Italy and went to work in France. They were looking for a Front End developer: HTML, CSS, JavaScript, React.js — and for Node.js, if you don’t know it, no problem, we will invest in your training… Period! Is there anything else to add? Every time I hear from him, he makes me feel like an idiot for stubbornly believing that things can change here in Italy too, and he keeps repeating to me: “A professional like you would earn staggering amounts here in France.”    <br />Monthly salary: 3,800 euros per month plus bonuses, and may God increase them. The company offers continuous training and updates. It is clear that in France entrepreneurs have understood that investing in human capital, in their own employees, is crucial to successfully face the challenges of tomorrow and to continue growing and remaining competitive.  </p>
<h2>If the approach doesn’t change, failure is just one step away.</h2>
<p>Let’s come back to Italy…</p>
<p>We often meet “old-school” entrepreneurs… When you talk to them about automating business processes — which could save them up to 90% of their current labor costs, considering they have to bear outrageous expenses for employees who spend the whole day in front of Excel files doing in one day what software could do in a fraction of a second — they look at you as if you came from Mars. They don’t understand a thing of what you’ve explained, but they are too full of themselves to admit it or realize that if they don’t move in this direction now, the only path ahead will be failure! </p>
<p>Yes, I’m talking about failure, and I shout it to everyone, without fear of contradiction, fully aware that I am not exaggerating the matter in any way. Because we also have meetings with young entrepreneurs who fully understand the potential of process automation; they realize they can save a lot on labor costs and therefore can lower the prices of their services, making them more competitive in the market, and they achieve impressive results.</p>
<p>And this is where the usual question arises: “A consumer finds a product at €20 and the very same product — probably with some flaws since everything is the result of ‘manual processing’ — at €40. <strong>Which</strong> <strong>product, do you think, would they actually buy between the two?”</strong> </p>
<p>And as a corollary, a second question arises…</p>
<p>“People who, until now, have bought the product for €40 — when they realize that another company offers a better one for €20 — <strong>will they continue buying the €40 one?</strong>” It would be like believing that if tomorrow a brand of OLED TVs came out producing TVs of equal or even better quality than those made by well-known brands, people would still buy the ones that cost twice as much… Totally illogical!  </p>
<p>This is how failure becomes inevitable… The new generation of entrepreneurs will be able to aggressively enter the market with lower prices because they will be more efficient, having lower costs. And all the customers of the old companies, when they see that it is possible to save significantly by having products born from automated processes — and therefore certainly more reliable in terms of “human error” and production speed — will not take long to close contracts on one side and open them on the other. This argument is even more valid the larger the company is. In large enterprises, if I can find a washer for 2 cents less with the same quality as the one I have used until now, at the end of the year I could have a few million euros more in liquidity.   </p>
<h2>Obsolete ideas and cult of personality</h2>
<p>It is truly very difficult to make a person understand—someone who built their wealth in 1980 following a completely analog methodology—that we are no longer in 1980. There are those who, wisely, ask themselves questions, look around, and realize that the world has changed. They understand that in 1980, that exceptional spray for cleaning windows without leaving streaks was only available if you took your car and drove 20 km to the store where you were sure to find it. Now, you go on Amazon, and the next day it’s delivered right to your doorstep. But he, stubbornly, continues to drive 20 km, wasting gas, time, and maybe getting frustrated in traffic.</p>
<p>There are those who have understood that we live in a world very different from the one in 1980. These are the people who will survive and, in fact, will only see their revenues rise. But within five years, companies that have not embraced process digitalization and still rely on pen and paper, or at best Excel sheets, will meet a very bad end.      </p>
<p>Many readers might say: “Okay, but then you won’t need so many people anymore; they’ll lay off half of them.” That’s not necessarily true… However, continuing to pursue a business model that in a couple of years will collapse — well, at least everyone will be out of a job!</p>
<p>That said, you can still maintain employment levels by choosing to invest human resources in other company departments, such as marketing, to increase your business scale, since on the processing side you can now virtually scale infinitely thanks to your investment in automation.   </p>
<p>The world evolves, and especially in private companies — which are the economic engine of the Italian system — you must be able to adapt to the times and market demands. Only in this way can you have a solid company.</p>
<p>The economy doesn’t work solely based on the amount of money in circulation; much more important is the speed at which that money changes hands. If the current trend continues, soon we will have fewer and fewer active companies in the market, and wealth will concentrate in the hands of a few, because when others decide to move, it will already be too late.  </p>
<h2>The importance of certain roles and their recognition</h2>
<p>Let’s start by giving value to people… A Full Stack developer who solves problems in your company and saves you a million euros a year cannot be paid 800 euros per month. Entrepreneur!?!?!? Be consistent first with yourself and then with others!!! How can you show up with a mere crumb to someone who helped you keep 5,000 loaves in the cupboard?  </p>
<p>Someone who has worked their tail off studying book after book, pulling all-nighters to prepare for university exams, constantly keeping up with the latest technologies (and there are a couple of new ones every two months), while their peers were hanging out smoking on the village square wall, would never come to work for you—earning you a million euros a year—in exchange for 800 euros a month, which is basically what those who were smoking get.</p>
<p>Are we going to recognize a difference or not? And if you think you’re clever proposing job offers that should only be ashamed to be published, maybe it’s time to rethink your approach… Because if you follow this kind of logic, you’re probably not that smart after all.  </p>

<p><strong>[starbox] </strong></p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/what-the-hll-are-you-looking-for-a-backend-or-a-frontend/">What the h*ll are you looking for? A backend or a frontend?</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress or custom solution?</title>
		<link>https://renor.it/en/blog/software-development-programming/wordpress-or-custom-solution/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Fri, 19 Aug 2022 12:25:47 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[cms]]></category>
		<category><![CDATA[image optimization]]></category>
		<category><![CDATA[loading speed]]></category>
		<category><![CDATA[renor]]></category>
		<category><![CDATA[renor & partners]]></category>
		<category><![CDATA[scam sites]]></category>
		<category><![CDATA[scams]]></category>
		<category><![CDATA[SEO]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">https://renor.it/wordpress-or-custom-solution/</guid>

					<description><![CDATA[<p>There is no doubt that WordPress is the most widely used CMS in the world, the community has more than 55000 plugins, it is easy to install, there are plugins to customize themes without the need to write a line of code, it integrates a blog on which it is easy to write articles, it [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/wordpress-or-custom-solution/">WordPress or custom solution?</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>There is no doubt that <strong>WordPress</strong> is the <strong>most widely used CMS in the world</strong>, the community has more than 55000 plugins, it is easy to install, there are plugins to customize themes without the need to write a line of code, it integrates a blog on which it is easy to write articles, it has plugins for SEO curation, and you name it. If everything is so great why are there still custom sites that do not take advantage of plugins? </p>
<h2>Some motivations</h2>
<p>Although the strengths of this CMS are clear, there are many things that the average user may not know about not being an insider.</p>
<h3>There is a way and a way to develop websites</h3>
<p>How many of you have fallen to the temptation of getting a <strong>site</strong> developed <strong>for 300 euros all inclusive</strong>? Well, here&#8217;s some news for you&#8230;   <strong>You have thrown your money away!</strong></p>
<p>Developing a site just to say, “Hey man! I made the site,” is perfectly useless. Since time immemorial, when you decide to buy a product you do it because <strong>it has to be useful for something</strong>. A site developed with 300 euros is a <strong>useless site</strong>. There is little to go around. With 300€ it is impossible to put in place all those virtuous development practices necessary to make the site pleasing to search engines.    </p>
<h3>The speed of loading</h3>
<p>Sites pulled up at a cost of 300€ are sure to integrate a <strong>WYSIWYG</strong> (What You See Is What You Get) <strong>plugin</strong>. These plugins are notoriously heavy, using lots of JavaScript and a myriad of CSS that is mostly not even used. Let&#8217;s face it: they are mainly used by those who <strong>don&#8217;t know how to develop</strong> and want to make their own “unpretentious little site” or by those who, aware of the SEO implications, don&#8217;t give a damn just to deliver a job sooner and make cash.  </p>
<p>Banco alle ciance! Numbers in hand. In either case, <strong>the result will be this:</strong>  </p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-91 aligncenter" src="https://renor.it/wp-content/uploads/2022/08/page-speed-troubles.jpg" alt="page speed" width="800" height="394" srcset="https://renor.it/wp-content/uploads/2022/08/page-speed-troubles.jpg 800w, https://renor.it/wp-content/uploads/2022/08/page-speed-troubles-300x148.jpg 300w, https://renor.it/wp-content/uploads/2022/08/page-speed-troubles-768x378.jpg 768w" sizes="auto, (max-width: 800px) 100vw, 800px" /></p>
<p>Such a site will be lucky to appear on the <strong>100th page of the search engine</strong>, unless you have done painstaking work on SEO by studying content for compound keywords that are more likely to rank, or have a number of articles and content that integrates all human knowledge, then yes, you can turn a blind eye to the loading speed, but if you have made such compromises let alone focus all this attention on SEO and content!</p>
<p>Therefore: <strong>unnecessary expense!</strong></p>
<h4>Image optimization</h4>
<p>A well-done site has had behind it the work of a webmaster who <strong>has spent a great deal of time optimizing images</strong> for the Web.<br />Today there are formats designed to aid site <strong>loading speed</strong>, formats that offer a small size while maintaining decent image quality.</p>
<p>In addition,<strong> images should be resized</strong> for their intended use. If I need to fill a small area of 300 x 200 pixels with an image, it is bad-practice to upload a 2400 x 1600 pixel image. </p>
<p>At 300€ per site this care <strong>cannot exist</strong> because it is time-consuming, so one prefers to take and upload the image as is and resize it via CSS by adding a couple of attributes to the tag (much faster operation):</p>
<pre>[html]
&amp;amp;amp;amp;lt;img src=&amp;amp;amp;quot;./images/img01.jpg&amp;amp;amp;quot; width=&amp;amp;amp;quot;300px&amp;amp;amp;quot; height=&amp;amp;amp;quot;200px&amp;amp;amp;quot; alt=&amp;amp;amp;quot;&amp;amp;amp;quot; title=&amp;amp;amp;quot;&amp;amp;amp;quot; longdesc=&amp;amp;amp;quot;&amp;amp;amp;quot; /&amp;amp;amp;amp;gt;
[/html]</pre>
<p>Too bad the image weighs 4 megabytes! Basically just loading this image, from mobile would take 4 to 5 seconds with 4G coverage, compared to the 20 Kb of the same optimized image, which would load in about 30 milliseconds. </p>
<p>Also prominent in the above line of code are <strong>three attributes that are useful for SEO</strong> and intentionally left unfilled: each image should have an <strong>alternate text</strong>, a <strong>title</strong>, and a <strong>long description</strong>. The search engine pays attention to these details because they are useful for the <strong>blind</strong> to interpret the content of the image. Therefore, in addition to representing additional words to be categorized for SEO purposes, engines tend to reward those who also think of the less fortunate. Of course, filling out these fields also takes time, and I doubt that $300 sites have done prior <strong>research on keywords</strong> they can rank with and have gone out of their way to provide descriptions of all the images they include.   </p>
<p>Just to give you an idea of the time frame, <strong>an article</strong> comes out on this blog <strong>every day</strong>&#8230;. The motivation is not due to a lack of desire to write or a lack of imagination: we have hundreds of article titles to write in our editorial plan, the problem is that between SEO analysis and drafting, an article takes <strong>half a day</strong> to write&#8230; And do we still want to waste time talking about a €300 site?  </p>
<h4>Postpone CSS and JS blocking the display</h4>
<p>The browser when it receives the requested site data processes CSS and JavaScript in the order they are written and <strong>blocks the display of</strong> the page until those files have been fully loaded. This further lengthens the time it takes to interact with the site. Some of these files may not be needed immediately and can be delayed. In essence, one must simply tell the browser that those files can be loaded after the page content has loaded. This practice gives the user who is opening the site a way to view it and be able to interact with it much sooner. This practice lowers what search engines call the <strong>bounce rate</strong>. Basically if a site takes 20 seconds to load at 6 seconds <strong>80% of visitors have already left</strong>.      </p>
<h4>Delete unused CSS and JavaScript fragments</h4>
<p>Definitely difficult operation to perform on projects using CSS-JS frameworks! The developer would have to access the framework&#8217;s CSS and JS files looking for all those classes (for CSS) and functions (for JavaScript) that are not called within the site pages and delete them. Under normal conditions this would not be the end of the world, but it is not easy to find what you need inside a file where the most <strong>user friendly</strong> name for naming a variable is <em>“var k.”</em>  </p>
<p>The practice of replacing user-friendly variable names with short variables is put in place by the framework developers to <strong>reduce the size of the file</strong> that the browser will have to load, but it makes <strong>the JavaScript code</strong> written within it virtually <strong>unreadable</strong>. In essence, it is an unworkable practice. </p>
<p>One operation that can certainly be done involves <strong>minification of all CSS and JS</strong>. The entire file is written in a single line by eliminating the “carriage return” information and spaces within it. This saves an amount of space that can average 20 to 40 percent of the original weight. All this results in a<strong> shorter loading time</strong> at the same download speed.   </p>
<h3>Web sites visually all the same</h3>
<p>When using these shortcuts you immediately notice a similarity between all sites. Want it or not, the CSS classes always are! As much as you can then customize there always remains a very similar visual impression between sites that were developed using WYSIWYG Plugins or Frameworks.  </p>
<h2>Are we therefore saying that WordPress is a CMS to be avoided?</h2>
<p>Absolutely not. WordPress is a great CMS for creating websites with blogs and eCommerce, as long as <strong>you develop themes to the rule of art</strong>, without using these kinds of shortcuts that make the investment made to have a website online absolutely useless. </p>
<p>This site was also developed with WordPress, but not in half a day&#8230;. In a couple of months. It possesses a style that will be impossible to find on Sites developed with the shortcuts we have talked about, it has optimized images, optimized SEO, Sitemap updated in real time and definitely the cost of development was not 300€ unless you find a developer to work for you at <strong>150€ a month</strong>!  </p>
<p>For that matter, <strong>the Audit speaks for itself</strong>&#8230;.</p>
<p><img loading="lazy" decoding="async" class="alignleft wp-image-95 size-medium" src="https://renor.it/wp-content/uploads/2022/08/Schermata-2022-08-19-alle-16.04.47-300x158.png" alt="renor.it audit" width="300" height="158" srcset="https://renor.it/wp-content/uploads/2022/08/Schermata-2022-08-19-alle-16.04.47-300x158.png 300w, https://renor.it/wp-content/uploads/2022/08/Schermata-2022-08-19-alle-16.04.47.png 692w" sizes="auto, (max-width: 300px) 100vw, 300px" /></p>
<p>As you may have guessed, it&#8217;s not a matter of choosing WordPress yes or WordPress no; it&#8217;s. as always, a matter of evaluating <strong>how</strong> you work on WordPress and making all the appropriate assessments if you don&#8217;t want to throw money away. </p>
<p>Alas, most online sites are precisely developed by <em>“phantom web agencies”<strong> </strong></em>in precisely this way, trying to go on the speed of development and the number of clients trapped in the net like fish caught in the sea, foisting on them low-paying websites, developed just to get the client to see the site online and which turn out, however, to be completely useless for the purposes for which a website is supposed to be built: attracting new clients.</p>
<p>In Italy for some years now there has been an air of “<em><strong>excessive facilitation.”</strong></em>. It is thought that everyone can do everything with little effort. I am sorry, but that is not the case! It is only fair that someone sooner or later will take the responsibility to say that if you are a Cook you will be great at your job but you cannot develop websites.   </p>
<h2>The cons of WordPress</h2>
<p>As the most popular CMS in the world, it is also the one <strong>most stormed by the Hacker community</strong>. More than 16 million online sites have been developed using WordPress, the step is short: I find a flaw in WordPress and I can extend it to 16 million sites. </p>
<p>The WordPress community is always working to <strong>improve the core of the CMS</strong>, so much so that updates to the Core are released from time to time, but beware, there are also Plugins that are often developed by third-party agencies, and they may have closed their doors, so they will no longer release updates and someone will sooner or later find a flaw.</p>
<p>In fact, most attacks on WordPress come precisely from <strong>flaws in plugins and themes</strong> (which are no exception). Developing your own theme from scratch allows you to limit attacks on the theme front precisely because of the fact that if the theme is yours it will be unique and there is no Hacker in the world willing to find systemic flaws on a theme used by a single site, unless your name is INPS or Internal Revenue Service. </p>
<p>Basically, you should preferably go for plugins that have millions of installations and ensure that the Software House that developed them stays alive to ensure updates to their plugin over time.</p>
<p>WordPress to be safe needs to be <strong>updated continuously</strong>, as soon as an update is available.</p>
<p>From the latest versions it is also possible to set automatic updates, both for the WordPress core and plugins.</p>
<h2>When it is not advisable to use WordPress</h2>
<p>There is no rule, you just use common sense&#8230;. Personally, I would not use WordPress for purposes for which it was not designed. Remember that WordPress started as a <strong>CMS for Blog integration</strong>, for any other project I would think about it before using WordPress.  </p>
<p>I would not use WordPress in case the key word of the work to be done was: performance. Not because WordPress itself is slow but because, I remind you, in order to run it makes calls that for some projects might be totally unnecessary. </p>
<p>Structuring a Custom project implies that you write <strong>only and exclusively the code that serves</strong> the purposes for which the site or web application was designed. Similarly, I would use Custom solutions to avoid worrying about having to update themes and plugins, especially when some plugins have not yet been updated to work properly on a new WordPress core. This could generate problems with site usage.  </p>
<p>Basically when the budget allows, I am always for implementing custom solutions: more reliable, better performing, safer&#8230; Of course, as long as you don&#8217;t pay 300€ for them. </p></p>

<p><strong>[starbox] </strong></p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/wordpress-or-custom-solution/">WordPress or custom solution?</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>First the comments, then the code</title>
		<link>https://renor.it/en/blog/software-development-programming/first-the-comments-then-the-code/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Thu, 18 Aug 2022 17:57:25 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[code comments]]></category>
		<category><![CDATA[comments]]></category>
		<category><![CDATA[importance of comments]]></category>
		<category><![CDATA[procedural programming]]></category>
		<category><![CDATA[procedures]]></category>
		<category><![CDATA[renor]]></category>
		<category><![CDATA[renor & partners]]></category>
		<category><![CDATA[why comment on the code]]></category>
		<guid isPermaLink="false">https://renor.it/first-the-comments-then-the-code/</guid>

					<description><![CDATA[<p>In this article I want to talk about a topic that is very close to my heart and that many developers seem to leave on the back burner. This topic represents not only an undeniable “best practice” in the way of developing code, but also a form of respect towards the community of developers and [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/first-the-comments-then-the-code/">First the comments, then the code</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In this article I want to talk about a topic that is very close to my heart and that many developers seem to leave on the back burner. This topic represents not only an undeniable “best practice” in the way of developing code, but also a <strong>form of respect</strong> towards the community of developers and engineers who will have to put their hands back on code written by someone else: I am talking about <strong>code comments.</strong> </p>
<p>A very dear university professor of mine, Prof. Carlo Gaibisso, used to say this phrase in his lectures on <em>“Structured Programming”</em> in the C language: <em>“You write the comments first and then the code.”</em></p>
<p>How can you blame them?</p>
<p>Despite the fact that programmers are a class of <em>“stage animals”</em> in the world of computer science, and despite the training a developer may have, it is a fact that it is always easier and more immediate to read something that belongs to our <strong>common way of communicating</strong>. It is much easier to read in Italian what an algorithm is about than to understand what an algorithm written in code does; it becomes the more complex to understand it the more complex the algorithm. </p>
<p>Object-oriented programming also incorporates procedural programming. Within the methods of a class, one programs procedurally. </p>
<p>Procedural programming is so called because code is executed according to a <strong>procedure</strong>. I can write procedures for anything&#8230; For example if I have to calculate the Fibonacci succession I know that to calculate the number at step <em>n I have to </em>take the previous number and add it to the still previous number.<br />Therefore the procedure will be: starting with S = 1 1, <i>f3</i><sub>&#8211; </sub>&gt; I take <sub>f2</sub> I add it to <sub>f1</sub>, and I get  </p>
<p>S = 1 1 2</p>
<p>I take <sub>f3</sub> = 2 I add it to <sub>f2</sub> = 1 and get</p>
<p>S = 1 1 2 3</p>
<p>I take <sub>f4</sub> = 3 I add it to <sub>f2</sub> = 2 and get</p>
<p>S = 1 1 2 3 5</p>
<p>You will understand that it is much easier to instantly understand the definition, “To calculate the nth number of the Fibonacci sequence I add the previous two” than <sub>fn</sub> = <sub>fn-1</sub> + <sub>fn-2</sub> especially if this formula is written with variables and cycles within a computer algorithm!</p>
<p>PHP example for calculating Fibonacci series without comments</p>
<p>[php]&lt;br /&gt;<br />
&lt;?php&lt;br /&gt;<br />
class Fibonacci&lt;br /&gt;<br />
{&lt;br /&gt;<br />
  public static function calculateSuccession($n)&lt;br /&gt;<br />
  {&lt;br /&gt;<br />
  $n = $n &#8211; 2;&lt;br /&gt;<br />
  $a = 1;&lt;br /&gt;<br />
  $b = 1;&lt;br /&gt;<br />
  echo $a . “<br />”;&lt;br / &gt;<br />
  echo $b . “<br />”;&lt;br / &gt;<br />
  for ($i = 0; $i &lt; $n; $i++) {&lt;br /&gt;<br />
  $c = $a + $b;&lt;br /&gt;<br />
  echo $c . “<br />”;&lt;br / &gt;<br />
  $a = $b;&lt;br /&gt;<br />
  $b = $c;&lt;br /&gt;<br />
  }&lt;br /&gt;<br />
  }&lt;br /&gt;<br />
}&lt;/p&gt;<br />
&lt;p&gt;Fibonacci::calculateSuccession(1000);&lt;br /&gt;<br />
[/php]</p>
<p>PHP example with comments</p>
<p>[php]&lt;br /&gt;<br />
&lt;?php&lt;/p&gt;<br />
&lt;p&gt;/**&lt;br /&gt;<br />
  * Fibonacci is a class for calculating the Fibonacci succession&lt;br /&gt;<br />
  * and its verification&lt;br /&gt;<br />
  *&lt;br /&gt;<br />
  * Fibonacci is a class for calculating the Fibonacci succession&lt;br /&gt;<br />
  * The verification of the golden ratio through the relationship between successive terms&lt;br /&gt;<br />
  * verification by tartaglia triangle and verification by&lt;br /&gt;<br />
  * Cassini, Catalani and D&#8217;Ocagne&#8217;s equalities&lt;br /&gt;<br />
  *&lt;br /&gt;<br />
  * @author Simone Renzi&lt;br /&gt;<br />
  * @version 1.0&lt;br /&gt;<br />
  * @access public&lt;br /&gt;<br />
  * @see http://renor.it&lt;br /&gt;<br />
  *&lt;br /&gt;<br />
  **/&lt;br /&gt;<br />
class Fibonacci&lt;br /&gt;<br />
{&lt;br /&gt;<br />
  /**&lt;br /&gt;<br />
  * @method calculateSuccession &#8211; Static method to calculate succession&lt;br /&gt;<br />
  * of Fibonacci for $n terms&lt;br /&gt;<br />
  * @param int $n &#8211; The number of iterations&lt;br /&gt;<br />
  * @return void has no return values, it just prints the values&lt;br /&gt;<br />
  * of the series at each iteration&lt;br /&gt;<br />
  * @access public&lt;br /&gt;<br />
  **/&lt;/p&gt;<br />
&lt;p&gt; public static function calculateSuccession($n)&lt;br /&gt;<br />
  {&lt;br /&gt;<br />
  //As I have two fixed parameters I remove them from iterations&lt;br /&gt;<br />
  $n = $n &#8211; 2;&lt;br /&gt;<br />
  $a = 1;&lt;br /&gt;<br />
  $b = 1;&lt;br /&gt;<br />
  //Print the two starting parameters&lt;br /&gt;<br />
  echo $a . “<br />”;&lt;br / &gt;<br />
  echo $b . “<br />”;&lt;br / &gt;<br />
  //Cycle for $n&lt;br /&gt;<br />
  for ($i = 0; $i &lt; $n; $i++) {&lt;br /&gt;<br />
  //Calculate the value of the subsequence at step $n&lt;br /&gt;<br />
  $c = $a + $b;&lt;br /&gt;<br />
  //Print on screen the value&lt;br /&gt;<br />
  echo $c . “<br />”;&lt;br / &gt;<br />
  //move variable values&lt;br /&gt;<br />
  //f2 becomes f1&lt;br /&gt;<br />
  $a = $b;&lt;br /&gt;<br />
  //f3 becomes f2, the next iteration will calculate the new f3&lt;br /&gt;<br />
  $b = $c;&lt;br /&gt;<br />
  }&lt;br /&gt;<br />
  }&lt;br /&gt;<br />
}&lt;/p&gt;<br />
&lt;p&gt;Fibonacci::calculateSuccession(1000);&lt;br /&gt;<br />
[/php]</p>
<p>I can continue to apply the procedure of taking the previous two numbers to infinity. Obviously computing an infinite set of numbers takes an infinite amount of time so we opt to find the subsequence at term <em>n where </em> <em>n is </em>the number of iterations for which the algorithm will have to perform the procedure. </p>
<p>From the pictures we can see that writing commented code in a virtuous way requires many more lines and therefore more time but in case of maintenance everything will become extremely easier and faster. We invest a small part of our time before so that we do not have to waste whole days afterwards. </p>
<h2>Procedures</h2>
<p>I can apply a procedure in any context, even for cooking. A recipe is nothing but a procedure: take a saucepan, put 2 tablespoons of extra virgin olive oil, add a clove of garlic, a hot pepper, turn on the stove and simmer, etc. </p>
<p>Procedures are inherent within functions and can be combined to solve larger problems. Returning to the Fibonacci example, another algorithm might verify that the ratio between two consecutive numbers in the Fibonacci series approximates as <em>n </em>increases more <em>and </em>more the golden ratio. A further function could verify that the obtained succession of <em>n</em> terms considering the of the terms on each diagonal of the tartaglia triangle corresponds to the Fibonacci succession, etc.  </p>
<p>On par I could say that the previous procedure useful for preparing the soffritto should be carried out on par with the procedure for cooking the pasta.</p>
<p>As you may have realized, a procedure is best and most quickly understood if there are comments describing the steps.</p>
<p>It is usual to insert comments first and then code because this practice allows us to write code very quickly without forgetting anything and leaving other programmers who will have to integrate other functions to quickly understand the famous <em>“what is needed for what.” </em> </p>
<p>&nbsp;</p>

<p><strong>[starbox] </strong></p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/first-the-comments-then-the-code/">First the comments, then the code</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Programming: from procedural to OOP</title>
		<link>https://renor.it/en/blog/software-development-programming/programming-from-procedural-to-oop/</link>
		
		<dc:creator><![CDATA[Simone Renzi]]></dc:creator>
		<pubDate>Thu, 18 Aug 2022 09:55:27 +0000</pubDate>
				<category><![CDATA[Software Development & Programming]]></category>
		<category><![CDATA[functional]]></category>
		<category><![CDATA[functional programming]]></category>
		<category><![CDATA[how object-oriented programming works]]></category>
		<category><![CDATA[object-oriented programming]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[procedural]]></category>
		<category><![CDATA[procedural programming]]></category>
		<category><![CDATA[renor]]></category>
		<category><![CDATA[renor & partners]]></category>
		<guid isPermaLink="false">https://renor.it/programming-from-procedural-to-oop/</guid>

					<description><![CDATA[<p>There are many people who would like to learn how to program for the Web but don&#8217;t know where to start. Some rely on books, some rely on online courses but what I keep hearing is always the same phrase: “I started studying a Backend language on an online course but object-oriented programming is too [&#8230;]</p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/programming-from-procedural-to-oop/">Programming: from procedural to OOP</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>There are many people who would like to learn how to program for the Web but don&#8217;t know where to start. Some rely on books, some rely on online courses but what I keep hearing is always the same phrase: “I started studying a Backend language on an online course but object-oriented programming is too difficult.” </p>
<p>Indeed, almost every textbook I have read in life on the OOP paradigm complicates a concept that I would characterize as the <strong>natural evolution of procedural and functional programming</strong>.</p>
<h2>Why make it complicated?</h2>
<p>Because in my opinion, all books and all courses necessarily want to bring examples comparing object-oriented programming to real life. In fact, people who have learned to program procedurally up to that point have only seen examples in the development world: what is a variable, what is an array, how to cycle an array, how to write a condition, how to make a flowchart, how to write a function; therefore, I don&#8217;t think they are used to picking up examples in the real world at this stage, but after they understand what the object-oriented paradigm implies in computing. It is a method of thinking about code that becomes almost necessary when moving from a small project to a larger project or one that will need to be maintained and extended in the future. Only when you are clear about these basics will it be possible to bring examples into real life.   </p>
<h3>Complicating discourages</h3>
<p>By complicating things in this way, new “adepts” to the programming world become discouraged. The result is that some of them drop their interest in programming, others remain stuck in procedural or functional programming without knowing that from functional programming to OOP is really a very short step. </p>
<h3>Those who develop procedural projects can create headaches for those who will have to put their hands back on the code</h3>
<p>The object-oriented paradigm is not just a <em>“different way of organizing code”</em> but provides rules and a modus operandi such that those who will have to get their hands on code developed by someone else will be able to do so without splitting their heads for too long searching for <em>“what does what.”</em></p>
<p>Also, in object-oriented programming, one should never underestimate the power of the UML.</p>
<p>In essence, object-oriented programming provides all the best practices that a good developer should put in place to make code that is easily readable, structured as it should be, easily maintainable, and scalable.</p>
<h2>Speaking of scalability</h2>
<p>Not only does the object-oriented paradigm channel the developer into virtuous development, but it also allows the developer to take advantage of a whole host of ready-made and easily integrated tools. Git Hub is teeming with ready-implemented and easily integrated Classes, mostly through <strong>Composer</strong>, which provide a way to save so much time in development. Classes exist for just about anything: generating a PDF, sending eMail, integrating with eBay and Amazon APIs for automating eCommerce projects, generating QR Codes and Bar Codes, comparing images, resizing images, compression, etc. etc.  </p>
<p>These tools are always written with an object-oriented paradigm, and to take advantage of them and, in some cases, readjust them to our needs, it is necessary to be familiar with this philosophy of writing code.</p>
<h3>When is it difficult to climb?</h3>
<p>Several times I have had to rehash projects written procedurally&#8230; I had to literally lose days just to figure out how the code was organized, having to read files of tens of thousands of lines of code. It becomes very difficult to modify even a very trivial function because you have to go searching through the thousands of lines for that portion of code that is responsible for putting that action into effect, stand there for hours debugging&#8230; Hell.   </p>
<p>In object-oriented projects this would have been disarmingly trivial!</p>
<h2>How does object-oriented programming work?</h2>
<p>We have been circling the topic for minutes but still have not given a definition that is easily understood.</p>
<p>To learn object-oriented programming there is no other way than to learn first procedural programming, then functional programming and finally object-oriented programming, because one is the natural evolution of the other.<br />Programming means being able to analyze a big problem, break it down into smaller problems and find solutions to each of these smaller problems and then put the pieces back together to solve the main problem. To do this we use functions.<br />The transition from procedural to functional programming is quite simple: functions are binders of procedural code that specialize in solving a small problem. E.g. write a program that deals with searching for a string within another string; if it finds it return positive outcome otherwise negative. This program I can write within a function. Functions in programming are exactly like mathematical functions: you pass one or more variables as input after which the function processes them and returns a result, or if we want to say it in a more properly mathematical way. Given a function for every element in the domain of the function corresponds to one in the codomain. In programming, functions have input parameters that are processed and an output value is returned.      </p>
<p>This solves a small problem derived from the main problem, and I can continue to solve small problems with other functions and then put them together to solve the main problem.</p>
<p>At the point when I have finished preparing all these functions, I can evaluate which ones are solving a <em>class of problems</em>. For example, let&#8217;s say we need to calculate the total of n outstanding invoices from a company and then have to email this total to the customer who owes us for them. </p>
<p>First problem: getting all the data from a database</p>
<p>Second problem: examining invoices</p>
<p>Third problem: adding up the unpaid bills.</p>
<p>Fourth problem: email the total amount to the customer for payment</p>
<h3>How would we solve it with functional programming?</h3>
<p>First problem:</p>
<ul>
<li>I write a function that makes the connection to the database</li>
<li>I write a function that does a SELECT query in the database to retrieve the data</li>
<li>I put them into an array</li>
</ul>
<p>Second problem:</p>
<ul>
<li>I write a function that loops my array</li>
<li>I write a function that downloads all the invoices to me.</li>
<li>I write a function that inserts unpaid bills into another array</li>
</ul>
<p>Third problem:</p>
<ul>
<li>I write a function that loops the array and sums the total invoices to me in a return variable</li>
</ul>
<p>Fourth problem:</p>
<ul>
<li>I write a function that sends an email to the customer by passing them the value of the variable containing the invoice total</li>
<li>I verify the correct sending of the email</li>
</ul>
<p>As you can see, we solved the main problem <em>“Calculate the total of n outstanding invoices of a company and email the total to the customer for payment”</em> by using functions that solve subcategories of the main problem by simply putting them together in an orderly way.</p>
<h3>How to bring this into an OOP perspective?</h3>
<p>One must break down the actors involved to understand which <em>Problem Classes</em> these belong to&#8230;. For example, <strong>Database Connection</strong> is a Problem Class that differs from Invoices, so <strong>Invoices</strong> might be another Problem Class, just as<strong>Sending Mail</strong> is further another Problem Class than Database Connection and Sending Mail. </p>
<p>We have therefore identified Classes that are nothing more than binders of functions, which, within them, take on the name “Methods.”</p>
<p>For this problem we will have the <em>Connection</em> Class <em>for connecting to the database</em>, the <strong>Invoices </strong> Class<em>for all the functions that satisfy the requirement to solve invoice-related subproblems</em>, and the <strong>Mail </strong> Class<em>that contains the methods related to sending e-mail.</em> <strong>Each class collects the methods</strong> (or if we still want to use the term “functions”) <strong>related to their problem-solving class</strong>.</p>
<p>The<strong>object</strong> that is <strong>instantiated</strong> (created) by the Class, is nothing more than a container of the methods that implements the Class (and of properties that are nothing more than the variables in play within the Class) that can be used to solve the main problem.</p>
<p>Of course this is one approach, but everyone could draw their own conclusions about the actors as they see fit and there would be no right and wrong version. For example, a developer could have integrated the methods for sending Mail within the Invoice Class since they relate to sending Mail containing billing data. That would have been reasoning that I disagree with but not incorrect.  </p>
<h3>Why using the object-oriented paradigm makes the code easier to maintain?</h3>
<p>Simple! Because if I were to be asked tomorrow to integrate the average of the total invoices as well, all I would have to do is add a method to the Invoices Class that would take care of calculating the average value of that Customer&#8217;s invoices, without having to stand around looking in a single file for the part of the code that deals with the invoice. </p>
<p>The important thing to do when programming in objects is therefore to figure out what problems are involved, structure a diagram called the UML, and collect within each class all the microproblem-solving methods assigned to that Problem Class. Once the argument runs smoothly on paper you can start writing comments (a best practice that you can <strong><a href="https://renor.it/first-the-comments-then-the-code/?lang=en">read about in this article</a></strong>) and lastly lay out the code. The task will become really trivial because the bulk of the work was done first with pen and paper.  </p>
<p>Of course, this is not a course in OOP programming, the rules of object-oriented programming are many and must be understood in full: daughter class inheritance, abstract classes, interfaces, traits, etc., but once you get into the mindset of “What is a Class” and “Why move to object-oriented programming” it will be just a matter of studying these rules and applying them as you program and continue to program more and more. You will realize, once you understand it, the conceptual ease and how it really comes in handy for organizing code according to rules that can be easily interpreted even by another person who will have to redo your code.</p>
<p>As you have seen object-oriented programming is much simpler than people say, it is by no means an abstract and terrible concept that creates blocks for people who want to learn programming&#8230; It&#8217;s all about taking it the right way without standing there talking about Houses, Cars and Motorcycles (this is the most cited example), Palaces, Recipes, and you name it. </p>
<p>&nbsp;</p>

<p><strong>[starbox] </strong></p>
<p>L'articolo <a href="https://renor.it/en/blog/software-development-programming/programming-from-procedural-to-oop/">Programming: from procedural to OOP</a> proviene da <a href="https://renor.it/en/">RENOR &amp; Partners S.r.l.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
