Scalbn Function In C

The main objective of the scalbn function is to perform a multiplication operation on a floating-point value x by 2 raised to the power of an integer n. This function offers three different versions tailored to support various floating-point data types:

Example

#include <math.h>

double scalbn(double x, int n);

float scalbnf(float x, int n);

long double scalbnl(long double x, int n);
  • The scalbn operates on double precision floating-point numbers.
  • The scalbnf operates on float precision floating-point numbers.
  • The scalbnl operates on long double precision floating-point numbers.
  • Approach-1: Basic Scaling with scalbn

Scaling a provided floating-point number x by a power of 2, denoted as 2n, is a common application of the scalbn function. This functionality is valuable in a range of numerical and computational scenarios that demand efficient scaling operations.

How does scalbn work?

The scalbn function operates by adjusting the exponent of the floating-point value x to increase or decrease its scale by a factor of 2 raised to the power of n. In the context of floating-point encoding, such as the commonly used IEEE 754 standard, a numeric value is expressed as x = m × 2^e, where m represents the mantissa and e denotes the exponent. When the scalbn function is applied, it effectively increments the exponent e by the value of n, leading to the outcome where result = x × 2^n.

This direct adjustment of the exponent enhances the efficiency of scalbn compared to employing generic exponentiation functions such as pow for powers that are multiples of two.

Program:

Example

// Include necessary header files for input/output and mathematical functions

#include <stdio.h>

#include <math.h>

// Main function to demonstrate the use of scalbn for scaling floating-point numbers

int main() {

    // Initialize variables for scaling demonstration

    double x1 = 1.5;    // First floating-point number to be scaled

    double x2 = -2.25;  // Second floating-point number to be scaled

    double x3 = 0.75;   // Third floating-point number to be scaled

    int n1 = 3;         // First exponent

    int n2 = -2;        // Second exponent

    int n3 = 5;         // Third exponent

    // Scale the first number x1 by 2^n1

    double result1 = scalbn(x1, n1);  // Compute 1.5 * 2^3

    // Print the result with detailed information

    printf("scalbn(%f, %d) = %f\n", x1, n1, result1);  // Expected: 1.5 * 8 = 12.0

    // Scale the second number x2 by 2^n2

    double result2 = scalbn(x2, n2);  // Compute -2.25 * 2^-2

    // Print the result with detailed information

    printf("scalbn(%f, %d) = %f\n", x2, n2, result2);  // Expected: -2.25 / 4 = -0.5625

    // Scale the third number x3 by 2^n3

    double result3 = scalbn(x3, n3);  // Compute 0.75 * 2^5

    // Print the result with detailed information

    printf("scalbn(%f, %d) = %f\n", x3, n3, result3);  // Expected: 0.75 * 32 = 24.0

    // Demonstrate special cases for scalbn function

    // Case 1: Scaling a zero value

    double x_zero = 0.0;

    int n_zero = 10;

    double result_zero = scalbn(x_zero, n_zero);  // Compute 0.0 * 2^10

    printf("scalbn(%f, %d) = %f\n", x_zero, n_zero, result_zero);  // Expected: 0.0

    // Case 2: Scaling infinity

    double x_inf = INFINITY;

    int n_inf = 5;

    double result_inf = scalbn(x_inf, n_inf);  // Compute infinity * 2^5

    printf("scalbn(%f, %d) = %f\n", x_inf, n_inf, result_inf);  // Expected: infinity

    // Case 3: Scaling NaN (Not a Number)

    double x_nan = NAN;

    int n_nan = 3;

    double result_nan = scalbn(x_nan, n_nan);  // Compute NaN * 2^3

    printf("scalbn(%f, %d) = %f\n", x_nan, n_nan, result_nan);  // Expected: NaN

    // Additional example to illustrate the use of scalbn with different data types

    float x_float = 2.5f;  // Floating-point number with float precision

    int n_float = 4;       // Exponent for float precision

    float result_float = scalbnf(x_float, n_float);  // Compute 2.5 * 2^4

    printf("scalbnf(%f, %d) = %f\n", x_float, n_float, result_float);  // Expected: 2.5 * 16 = 40.0

    // Demonstrate use of scalbn with long double precision

    long double x_long_double = 1.125L;  // Floating-point number with long double precision

    int n_long_double = -3;              // Exponent for long double precision

    long double result_long_double = scalbnl(x_long_double, n_long_double);  // Compute 1.125 * 2^-3

    printf("scalbnl(%Lf, %d) = %Lf\n", x_long_double, n_long_double, result_long_double);  // Expected: 1.125 / 8 = 0.140625

    // Conclusion message

    printf("Demonstration of scalbn function completed.\n");

    // Return 0 to indicate successful execution of the program

    return 0;

}

Output:

Output

scalbn(1.500000, 3) = 12.000000

scalbn(-2.250000, -2) = -0.562500

scalbn(0.750000, 5) = 24.000000

scalbn(0.000000, 10) = 0.000000

scalbn(inf, 5) = inf

scalbn(nan, 3) = nan

scalbnf(2.500000, 4) = 40.000000

scalbnl(1.125000, -3) = 0.140625

Demonstration of scalbn function completed.

Explanation:

The code demonstrates the application of the scalbn function in C, which is used to scale floating-point numbers by powers of two. The example covers basic usage, handles special cases, and showcases how the function works with different floating-point types.

  • Header Files The program begins by including the necessary header files: <stdio.h>: It provides functions for input and output operations, such as displaying results on the screen. <math.h>: It contains mathematical functions, including scalbn, which is used for scaling floating-point numbers.
  • Main Function The main function orchestrates the demonstration of the scalbn function. Here's a breakdown of its operations: Variable Initialization: Several floating-point variables are declared, each representing a value that will be scaled. Exponent variables are declared, representing the power of two by which each floating-point number will be scaled. Handling Special Cases: Zero Value: Demonstrates that scaling zero by any power of two results in zero. This is verified by using scalbn with a zero value and checking the output. Infinity: Shows that scaling an infinite value by any power of two still results in infinity. This is tested by applying scalbn to an infinite value and observing the result. NaN (Not a Number): Indicates that scaling a NaN value remains NaN. This behavior is confirmed by using scalbn with a NaN value and inspecting the output. Different Floating-Point Types: Float Type: Demonstrates the use of scalbnf, a variant of scalbn for single-precision floating-point numbers. This part of the code scales a float value and shows the result. Long double Type: Uses scalbnl, a variant for extended-precision floating-point numbers. This section scales a long double value and presents the result.
  • Complexity Analysis:

The scalbn function in C is employed to adjust a floating-point number by a specific power of two. Evaluating the time and space complexity of scalbn requires comprehension of its internal functionality and its approach to managing floating-point values and exponents.

Time Complexity

The time complexity of the scalbn function is mainly impacted by the subsequent operations:

Exponent Manipulation:

The scalbn function modifies the exponent of the floating-point value to scale it by a factor of 2 raised to the power of n. This process requires adjusting the exponent component within the floating-point format, a task that is accomplished in constant time. Floating-point values adhere to the IEEE 754 standard, which defines a structured format for storing the mantissa and exponent. The manipulation of these components is achieved efficiently in O(1) time complexity, thanks to straightforward bit operations.

Scaling Computation:

The specific calculation carried out by scalbn is simple. It involves multiplying the floating-point value by 2n, which is accomplished by adjusting the exponent in the floating-point format. This multiplication operation is completed in a fixed amount of time, denoted as O(1), thanks to the efficient floating-point arithmetic capabilities of the hardware.

Special Case Handling:

The scalbn function also manages unique scenarios like zero, infinity, and NaN values. The evaluations for these scenarios are basic and are generally executed in constant time. Due to scalbn's direct handling of these exceptional cases and immediate return of the correct output, the time complexity stays at O(1) even when addressing these boundary conditions.

Finally, the time complexity of the scalbn function is O(1). This means that regardless of the input values' size, scalbn executes a fixed number of operations. The efficiency of scalbn stems from its utilization of bitwise operations and hardware-accelerated arithmetic, making it independent of the exponent or floating-point number's magnitude.

Space Complexity

Analyzing the scalability of the scalbn function involves considering the space complexity from various perspectives:

Input Storage:

The function receives a floating-point number and an integer exponent as input parameters. The storage needed for these inputs remains constant regardless of their values. As a result, the space complexity associated with input storage is O(1).

Internal Variables:

Internally, the scalbn function might utilize temporary storage to hold interim outcomes, like the adjusted value of the floating-point digit. These storage units usually have a constant size and are not influenced by the input size. As a result, the spatial complexity for internal storage remains O(1).

Function Call Overhead:

The invocation of a function does not introduce substantial extra space usage, as it functions within the stack frame of the calling function. The space allocated for function calls remains consistent and does not increase in proportion to the size of the input.

Finally, the space efficiency of scalbn is O(1). This function does not necessitate extra space that increases with the input size. It functions using a consistent amount of memory, mainly for holding the input values and interim outcomes.

Approach-2: Bit Manipulation:

Working with the IEEE 754 format of floating-point numbers requires a deep comprehension of their configuration and executing bitwise manipulations to modify the exponent. Below is an in-depth breakdown of this technique.

Program:

Example

#include <stdio.h>

#include <stdint.h>

#include <math.h>

// Union to access the bits of a double

union DoubleInt {

    double d;

    uint64_t i;

};

// Function to extract the exponent from the 64-bit representation

int extract_exponent(uint64_t bits) {

    return (bits >> 52) & 0x7FF; // Extract bits 52 to 62 (11 bits for the exponent)

}

// Function to set the exponent in the 64-bit representation

uint64_t set_exponent(uint64_t bits, int new_exponent) {

    bits &= ~(0x7FFULL << 52); // Clear the original exponent bits

    bits |= ((uint64_t)new_exponent & 0x7FF) << 52; // Set the new exponent

    return bits;

}

// Function to scale the floating-point number by a power of two

double scale(double x, int n) {

    union DoubleInt value;

    value.d = x;

    int current_exponent = extract_exponent(value.i);

    int exponent_bias = 1023; // Bias for double-precision floating-point numbers

    // Handle special cases: zero, infinity, NaN

    if (current_exponent == 0x7FF) { // All bits in the exponent field are 1

        // Infinity or NaN

        return x; // Scaling does not change infinity or NaN

    }

    if (current_exponent == 0) { // All bits in the exponent field are 0

        if (value.i << 12 == 0) {

            // Zero (both positive and negative)

            return x; // Scaling does not change zero

        } else {

            // Subnormal number

            // Normalize the subnormal number by adjusting the exponent

            while ((value.i & (1ULL << 52)) == 0) {

                value.i <<= 1;

                current_exponent--;

            }

            current_exponent++; // Adjust for the normalization shift

            value.i &= ~(1ULL << 52); // Clear the leading 1 bit

        }

    }

    // Calculate the new exponent

    int new_exponent = current_exponent + n;

    // Handle exponent overflow and underflow

    if (new_exponent >= 0x7FF) {

        // Overflow: return infinity with the same sign as the original number

        return copysign(INFINITY, x);

    }

    if (new_exponent <= 0) {

        // Underflow: the result is subnormal or zero

        if (new_exponent < -52) {

            // Too small to be represented as a subnormal number, return zero

            return copysign(0.0, x);

        } else {

            // Shift the significand to make it a subnormal number

            value.i = (value.i & ~(0x7FFULL << 52)) | ((uint64_t)(new_exponent + 52) << 52);

            return value.d;

        }

    }

    // Set the new exponent

    value.i = set_exponent(value.i, new_exponent);



    return value.d;

}

int main() {

    double x;

    int n;

    // Test cases to demonstrate the scaling function

    x = 3.14;

    n = 10;

    printf("Original: %f, Scaled: %f\n", x, scale(x, n)); // Scale by 2^10

    x = 3.14;

    n = -10;

    printf("Original: %f, Scaled: %f\n", x, scale(x, n)); // Scale by 2^-10

    x = 0.0;

    n = 5;

    printf("Original: %f, Scaled: %f\n", x, scale(x, n)); // Scale zero by 2^5

    x = -0.0;

    n = 5;

    printf("Original: %f, Scaled: %f\n", x, scale(x, n)); // Scale negative zero by 2^5

    x = INFINITY;

    n = 5;

    printf("Original: %f, Scaled: %f\n", x, scale(x, n)); // Scale infinity by 2^5

    x = -INFINITY;

    n = 5;

    printf("Original: %f, Scaled: %f\n", x, scale(x, n)); // Scale negative infinity by 2^5

    x = NAN;

    n = 5;

    printf("Original: %f, Scaled: %f\n", x, scale(x, n)); // Scale NaN by 2^5



    x = 1.0e-300;

    n = 500;

    printf("Original: %e, Scaled: %e\n", x, scale(x, n)); // Large positive scaling factor

    x = 1.0e300;

    n = -500;

    printf("Original: %e, Scaled: %e\n", x, scale(x, n)); // Large negative scaling factor

    return 0;

}

Output:

Output

Original: 3.140000, Scaled: 3215.360000

Original: 3.140000, Scaled: 0.003066

Original: 0.000000, Scaled: 0.000000

Original: -0.000000, Scaled: -0.000000

Original: inf, Scaled: inf

Original: -inf, Scaled: -inf

Original: nan, Scaled: nan

Original: 1.000000e-300, Scaled: 3.273391e-150

Original: 1.000000e+300, Scaled: 3.054936e+149

Explanation:

The provided code demonstrates the manipulation of IEEE 754 floating-point numbers directly through bit-level operations. This approach allows us to scale a floating-point number by a power of two by adjusting its exponent field directly.

  • Union for Bit Manipulation The code begins with the definition of a union DoubleInt that allows the same memory location to be interpreted as both a double and a 64-bit unsigned integer. This is crucial for directly accessing and modifying the bits of a double-precision floating-point number.
  • Extracting the Exponent The extract_exponent function isolates the exponent bits from the 64-bit representation of the floating-point number. In the IEEE 754 standard, the exponent is stored in bits 52 to 62. The function shifts the bits to the right by 52 places and then applies a bitmask to extract the 11-bit exponent.
  • Setting the Exponent The set_exponent function clears the existing exponent bits in the 64-bit representation and sets the new exponent. This involves clearing the bits in the exponent field using a bitmask and then setting the new exponent bits in the appropriate position.
  • Handling Special Cases The scale function begins by handling several special cases: Infinity and NaN: These are identified by an exponent of 2047 (all bits in the exponent field are 1). The function checks for this condition and returns the input value as-is since scaling does not change infinity or NaN. Zero: Both positive and negative zero have an exponent of 0 and a significand of 0. Scaling zero by any power of two still results in zero. Subnormal Numbers: These numbers have an exponent of 0 but a non-zero significand. To handle subnormal numbers, the function normalizes the significand by shifting it left until the leading bit is 1, adjusting the exponent accordingly.
  • Calculating the New Exponent After that, the function calculates the new exponent by adding the scaling factor n to the current exponent. The exponent in IEEE 754 is stored with a bias of 1023, so the function works directly with the biased exponent.
  • Handling Overflow and Underflow The function handles potential overflow and underflow of the exponent: Overflow: If the new exponent is greater than or equal to 2047, the result is positive or negative infinity, depending on the sign of the original number.
  • Underflow: If the new exponent is less than or equal to 0, the result can either be a subnormal number or zero. If the new exponent is less than -52, the result is zero because the number is too small to be represented even as a subnormal number. Otherwise, the significand is shifted accordingly to represent a subnormal number.
  • Reassembling the Number After adjusting the exponent, the function reassembles the floating-point number by setting the new exponent in the 64-bit representation. This involves combining the modified exponent with the original sign and significand.
  • Complexity Analysis:

Time Complexity Analysis

The main focus of this software lies in the scale(double x, int n) function, which adjusts a decimal number by a binary exponent through bit manipulation. Now, let's delve into its computational efficiency.

Union Definition and Initialization:

Creating the union DoubleInt and assigning the value of the input double x is an operation that runs in constant time, denoted as O(1).

Extracting the Exponent:

The extractexponent(uint64t bits) function performs a right shift on the bits by 52 positions and then applies a mask to isolate the 11-bit exponent. These operations, namely bit-shifting and masking, are both considered constant time operations.

Calculating the New Exponent:

Including the scaling factor n to the existing exponent entails a singular addition operation, which maintains a constant time complexity of O(1).

Handling Overflow and Underflow:

Checking for Overflow: This operation has a constant time complexity of O(1) and verifies whether the new exponent surpasses the maximum limit (2047), returning infinity in case it does.

Underflow Verification: Verifying whether the updated exponent is equal to or less than zero and managing subnormal values or zero requires several comparisons and possible adjustments, all of which have a constant time complexity of O(1).

Setting the New Exponent:

The function setexponent(uint64t bits, int new_exponent) resets the current exponent bits and assigns the new exponent value. This process includes bitwise masking and shifting procedures, both of which have a constant time complexity of O(1).

Reassembling the Number:

Merging the altered exponent with the initial significand and sign bit to generate the updated double value requires fundamental bitwise manipulations and operates in constant time complexity.

Overall Time Complexity

Every individual operation within the scale function requires a consistent amount of time. As a result, the scale function's time complexity is O(1), indicating that it executes a set quantity of work irrespective of the input's magnitude.

Space Complexity Analysis

The complexity of the program encompasses the storage space needed for input and output, along with any extra space utilized by the program.

Union for Bit Manipulation:

The DoubleInt union holds a double and a 64-bit integer. Both share the same memory location, so the union only requires space for the larger of the two, which is 64 bits (or 8 bytes).

Local Variables:

The function scale employs several local variables, such as currentexponent, newexponent, and value. These variables can be integers or a combination of a double and a 64-bit integer within a union. The overall memory footprint of these local variables is kept to a minimum and remains constant.

Return Value:

The function yields a double data type, requiring 8 bytes of memory storage.

Overall Space Complexity

The scale function has a space complexity of O(1) since it consumes a consistent amount of memory irrespective of the input's size. The memory allocated for variables like union, local variables, and the return value remains unchanged and doesn't expand with the input size.

Input Required

This code uses input(). Please provide values below: