Standard library: Numerics

Decibel Factor

Goal: implement functions to convert from Decibel values to factors and vice-versa.

Steps:

  1. Implement the Decibels package.

    1. Implement the To_Decibel function.

    2. Implement the To_Factor function.

Requirements:

  1. The subtypes Decibel and Factor are based on a floating-point type.

  2. Function To_Decibel converts a multiplication factor (or ratio) to decibels.

    • For the implementation, use \(20 * log_{10}(F)\), where F is the factor/ratio.

  3. Function To_Decibel converts a value in decibels to a multiplication factor (or ratio).

    • For the implementation, use \(10^{D/20}\), where D is the value in Decibel.

Remarks:

  1. The Decibel is used to express the ratio of two values on a logarithmic scale.

    1. For example, an increase of 6 dB corresponds roughly to a multiplication by two (or an increase by 100 % of the original value).

  2. You can find the functions that you'll need for the calculation in the Ada.Numerics.Elementary_Functions package.

package Decibels is subtype Decibel is Float; subtype Factor is Float; function To_Decibel (F : Factor) return Decibel; function To_Factor (D : Decibel) return Factor; end Decibels;
package body Decibels is function To_Decibel (F : Factor) return Decibel is begin return 0.0; end To_Decibel; function To_Factor (D : Decibel) return Factor is begin return 0.0; end To_Factor; end Decibels;
with Ada.Command_Line; use Ada.Command_Line; with Ada.Text_IO; use Ada.Text_IO; with Decibels; use Decibels; procedure Main is type Test_Case_Index is (Db_Chk, Factor_Chk); procedure Check (TC : Test_Case_Index; V : Float) is package F_IO is new Ada.Text_IO.Float_IO (Factor); package D_IO is new Ada.Text_IO.Float_IO (Decibel); procedure Put_Decibel_Cnvt (D : Decibel) is F : constant Factor := To_Factor (D); begin D_IO.Put (D, 0, 2, 0); Put (" dB => Factor of "); F_IO.Put (F, 0, 2, 0); New_Line; end; procedure Put_Factor_Cnvt (F : Factor) is D : constant Decibel := To_Decibel (F); begin Put ("Factor of "); F_IO.Put (F, 0, 2, 0); Put (" => "); D_IO.Put (D, 0, 2, 0); Put_Line (" dB"); end; begin case TC is when Db_Chk => Put_Decibel_Cnvt (Decibel (V)); when Factor_Chk => Put_Factor_Cnvt (Factor (V)); end case; end Check; begin if Argument_Count < 2 then Put_Line ("ERROR: missing arguments! Exiting..."); return; elsif Argument_Count > 2 then Put_Line ("Ignoring additional arguments..."); end if; Check (Test_Case_Index'Value (Argument (1)), Float'Value (Argument (2))); end Main;

Root-Mean-Square

Goal: implement a function to calculate the root-mean-square of a sequence of values.

Steps:

  1. Implement the Signals package.

    1. Implement the Rms function.

Requirements:

  1. Subtype Sig_Value is based on a floating-point type.

  2. Type Signal is an unconstrained array of Sig_Value elements.

  3. Function Rms calculates the RMS of a sequence of values stored in an array of type Signal.

    1. See the remarks below for a description of the RMS calculation.

Remarks:

  1. The root-mean-square (RMS) value is an important information associated with sequences of values.

    1. It's used, for example, as a measurement for signal processing.

    2. It is calculated by:

      1. Creating a sequence \(S\) with the square of each value of an input sequence \(S_{in}\).

      2. Calculating the mean value \(M\) of the sequence \(S\).

      3. Calculating the square-root \(R\) of \(M\).

    3. You can optimize the algorithm above by combining steps #1 and #2 into a single step.

package Signals is subtype Sig_Value is Float; type Signal is array (Natural range <>) of Sig_Value; function Rms (S : Signal) return Sig_Value; end Signals;
with Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions; package body Signals is function Rms (S : Signal) return Sig_Value is begin return 0.0; end; end Signals;
package Signals.Std is Sample_Rate : Float := 8000.0; function Generate_Sine (N : Positive; Freq : Float) return Signal; function Generate_Square (N : Positive) return Signal; function Generate_Triangular (N : Positive) return Signal; end Signals.Std;
with Ada.Numerics; use Ada.Numerics; with Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions; package body Signals.Std is function Generate_Sine (N : Positive; Freq : Float) return Signal is S : Signal (0 .. N - 1); begin for I in S'First .. S'Last loop S (I) := 1.0 * Sin (2.0 * Pi * (Freq * Float (I) / Sample_Rate)); end loop; return S; end; function Generate_Square (N : Positive) return Signal is S : constant Signal (0 .. N - 1) := (others => 1.0); begin return S; end; function Generate_Triangular (N : Positive) return Signal is S : Signal (0 .. N - 1); S_Half : constant Natural := S'Last / 2; begin for I in S'First .. S_Half loop S (I) := 1.0 * (Float (I) / Float (S_Half)); end loop; for I in S_Half .. S'Last loop S (I) := 1.0 - (1.0 * (Float (I - S_Half) / Float (S_Half))); end loop; return S; end; end Signals.Std;
with Ada.Command_Line; use Ada.Command_Line; with Ada.Text_IO; use Ada.Text_IO; with Signals; use Signals; with Signals.Std; use Signals.Std; procedure Main is type Test_Case_Index is (Sine_Signal_Chk, Square_Signal_Chk, Triangular_Signal_Chk); procedure Check (TC : Test_Case_Index) is package Sig_IO is new Ada.Text_IO.Float_IO (Sig_Value); N : constant Positive := 1024; S_Si : constant Signal := Generate_Sine (N, 440.0); S_Sq : constant Signal := Generate_Square (N); S_Tr : constant Signal := Generate_Triangular (N + 1); begin case TC is when Sine_Signal_Chk => Put ("RMS of Sine Signal: "); Sig_IO.Put (Rms (S_Si), 0, 2, 0); New_Line; when Square_Signal_Chk => Put ("RMS of Square Signal: "); Sig_IO.Put (Rms (S_Sq), 0, 2, 0); New_Line; when Triangular_Signal_Chk => Put ("RMS of Triangular Signal: "); Sig_IO.Put (Rms (S_Tr), 0, 2, 0); New_Line; end case; end Check; begin if Argument_Count < 1 then Put_Line ("ERROR: missing arguments! Exiting..."); return; elsif Argument_Count > 1 then Put_Line ("Ignoring additional arguments..."); end if; Check (Test_Case_Index'Value (Argument (1))); end Main;

Rotation

Goal: use complex numbers to calculate the positions of an object in a circle after rotation.

Steps:

  1. Implement the Rotation package.

    1. Implement the Rotation function.

Requirements:

  1. Type Complex_Points is an unconstrained array of complex values.

  2. Function Rotation returns a list of positions (represented by the Complex_Points type) when dividing a circle in N equal slices.

    1. See the remarks below for a more detailed explanation.

    2. You must use functions from Ada.Numerics.Complex_Types to implement Rotation.

  3. Subtype Angle is based on a floating-point type.

  4. Type Angles is an unconstrained array of angles.

  5. Function To_Angles returns a list of angles based on an input list of positions.

Remarks:

  1. Complex numbers are particularly useful in computer graphics to simplify the calculation.

    1. For example, let's assume you've drawn an object on your screen on position (1.0, 0.0).

    2. Now, you want to move this object in a circular path — i.e. make it rotate around position (0.0, 0.0) on your screen.

      • You could use sine and cosine functions to calculate each position of the path.

      • However, you could also calculate the positions using complex numbers.

  2. In this exercise, you'll use complex numbers to calculate the positions of an object that starts on zero degrees — on position (1.0, 0.0) — and rotates around (0.0, 0.0) for N slices of a circle.

    1. For example, if we divide the circle in four slices, the object's path will consist of following points / positions:

      Point #1: ( 1.0,  0.0)
      Point #2: ( 0.0,  1.0)
      Point #3: (-1.0,  0.0)
      Point #4: ( 0.0, -1.0)
      Point #5: ( 1.0,  0.0)
      

      Or graphically:

      ../../../_images/rotation.svg
      1. As expected, point #5 is equal to the starting point (point #1), since the object rotates around (0.0, 0.0) and returns to the starting point.

    2. We can also describe this path in terms of angles. The following list presents the angles for the path on a four-sliced circle:

      Point #1:    0.00 degrees
      Point #2:   90.00 degrees
      Point #3:  180.00 degrees
      Point #4:  -90.00 degrees (= 270 degrees)
      Point #5:    0.00 degrees
      
with Ada.Numerics.Complex_Types; use Ada.Numerics.Complex_Types; package Rotation is type Complex_Points is array (Positive range <>) of Complex; function Rotation (N : Positive) return Complex_Points; end Rotation;
with Ada.Numerics; use Ada.Numerics; package body Rotation is function Rotation (N : Positive) return Complex_Points is C : Complex_Points (1 .. 1) := (others => (0.0, 0.0)); begin return C; end; end Rotation;
with Rotation; use Rotation; package Angles is subtype Angle is Float; type Angles is array (Positive range <>) of Angle; function To_Angles (C : Complex_Points) return Angles; end Angles;
with Ada.Numerics; use Ada.Numerics; with Ada.Numerics.Complex_Types; use Ada.Numerics.Complex_Types; package body Angles is function To_Angles (C : Complex_Points) return Angles is begin return A : Angles (C'Range) do for I in A'Range loop A (I) := Argument (C (I)) / Pi * 180.0; end loop; end return; end To_Angles; end Angles;
package Rotation.Tests is procedure Test_Rotation (N : Positive); procedure Test_Angles (N : Positive); end Rotation.Tests;
with Ada.Text_IO; use Ada.Text_IO; with Ada.Text_IO.Complex_IO; with Ada.Numerics; use Ada.Numerics; with Angles; use Angles; package body Rotation.Tests is package C_IO is new Ada.Text_IO.Complex_IO (Complex_Types); package F_IO is new Ada.Text_IO.Float_IO (Float); -- -- Adapt value due to floating-point inaccuracies -- function Adapt (C : Complex) return Complex is function Check_Zero (F : Float) return Float is (if F <= 0.0 and F >= -0.01 then 0.0 else F); begin return C_Out : Complex := C do C_Out.Re := Check_Zero (C_Out.Re); C_Out.Im := Check_Zero (C_Out.Im); end return; end Adapt; function Adapt (A : Angle) return Angle is (if A <= -179.99 and A >= -180.01 then 180.0 else A); procedure Test_Rotation (N : Positive) is C : constant Complex_Points := Rotation (N); begin Put_Line ("---- Points for " & Positive'Image (N) & " slices ----"); for V of C loop Put ("Point: "); C_IO.Put (Adapt (V), 0, 1, 0); New_Line; end loop; end Test_Rotation; procedure Test_Angles (N : Positive) is C : constant Complex_Points := Rotation (N); A : constant Angles.Angles := To_Angles (C); begin Put_Line ("---- Angles for " & Positive'Image (N) & " slices ----"); for V of A loop Put ("Angle: "); F_IO.Put (Adapt (V), 0, 2, 0); Put_Line (" degrees"); end loop; end Test_Angles; end Rotation.Tests;
with Ada.Command_Line; use Ada.Command_Line; with Ada.Text_IO; use Ada.Text_IO; with Rotation.Tests; use Rotation.Tests; procedure Main is type Test_Case_Index is (Rotation_Chk, Angles_Chk); procedure Check (TC : Test_Case_Index; N : Positive) is begin case TC is when Rotation_Chk => Test_Rotation (N); when Angles_Chk => Test_Angles (N); end case; end Check; begin if Argument_Count < 2 then Put_Line ("ERROR: missing arguments! Exiting..."); return; elsif Argument_Count > 2 then Put_Line ("Ignoring additional arguments..."); end if; Check (Test_Case_Index'Value (Argument (1)), Positive'Value (Argument (2))); end Main;