You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

技术问询:浮点数精度与数值的函数计算及IEEE 754维基图表复现

Hey there! Let's tackle these two IEEE 754-related problems step by step—they're both great deep dives into how floating-point numbers work under the hood.

1. Python Function to Calculate Floating-Point Precision Based on Value

Floating-point precision (measured by the Unit in the Last Place, ULP) isn't fixed—it grows as the magnitude of the number increases. For IEEE 754 double-precision floats (Python's default), here's how to compute it:

The ULP is the smallest difference between two consecutive representable floating-point numbers. For normalized values, ULP scales with the exponent of the number in binary scientific notation.

Option 1: Use Built-in math.ulp() (Python 3.9+)

This is the simplest approach—Python's standard library has a native function for this:

import math

def float_precision(x):
    if x == 0.0:
        return math.ulp(0.0)  # ULP for 0 is the smallest positive denormal number
    return math.ulp(x)

Option 2: Manual Implementation (Compatible with Older Python Versions)

If you need to support versions before 3.9, you can calculate ULP by directly leveraging the IEEE 754 structure:

import struct

def float_precision_manual(x):
    if x == 0.0:
        # Smallest positive denormal double-precision float
        return 2 ** (-1074)
    
    # Convert float to its 64-bit binary representation
    bits = struct.unpack('Q', struct.pack('d', x))[0]
    # Extract exponent bits (positions 52 to 62)
    exponent = (bits >> 52) & 0x7FF
    
    if exponent == 0:
        # Denormal number: ULP is fixed at 2^-1074
        return 2 ** (-1074)
    else:
        # Normalized number: ULP = 2^(exponent - 1023 - 52) = 2^(exponent - 1075)
        return 2 ** (exponent - 1075)

Test the Functions

# Example usage
print(f"ULP of 0.0: {float_precision(0.0)}")
print(f"ULP of 1.0: {float_precision(1.0)}")  # ~2.22e-16 (2^-52)
print(f"ULP of 1,000,000.0: {float_precision(1000000.0)}")  # ~1.16e-10 (larger as value grows)

Notice how the ULP increases with the number's magnitude—this is why large floats lose precision for small increments.

2. Technical Plan to Reproduce IEEE 754 Charts from Wikipedia

Most IEEE 754 charts on Wikipedia visualize either ULP vs. number magnitude or the density of representable floats across the number line. Since the page doesn't spell out data generation, we can reverse-engineer it using our knowledge of IEEE 754.

Step 1: Identify the Target Chart Type

Focus on the two most common charts:

  • ULP vs. Value (Log-Log Scale): Shows precision loss as numbers grow.
  • Floating-Point Density: Shows how the number of representable floats per unit interval drops exponentially with magnitude.

Step 2: Generate Data & Plot

We'll use numpy for efficient data generation and matplotlib for plotting.

Example 1: ULP vs. Value Chart

import numpy as np
import matplotlib.pyplot as plt

# Generate values spanning denormal and normalized ranges (log-spaced to cover wide scope)
values = np.logspace(-1030, 1030, 1000)
# Calculate ULP for each value
ulps = np.array([math.ulp(x) for x in values])

# Plot on log-log scale
plt.figure(figsize=(10, 6))
plt.loglog(values, ulps, label='ULP (Double-Precision)')

# Annotate key regions
plt.axvline(x=2**-1022, color='red', linestyle='--', label='Normalized/Denormal Boundary')
plt.text(2**-1022, 2**-1074, 'Denormal\nRegion', ha='right', va='bottom')
plt.text(2**-1021, 2**-1073, 'Normalized\nRegion', ha='left', va='top')

plt.xlabel('Floating-Point Value')
plt.ylabel('Unit in the Last Place (ULP)')
plt.title('IEEE 754 Double-Precision ULP vs. Value')
plt.legend()
plt.grid(True, which='both', linestyle='--')
plt.show()

Example 2: Floating-Point Density Chart

For normalized floats, each interval [2^k, 2^(k+1)) has exactly 2^52 unique values. The density (number per unit) is 2^(52 - k).

# Generate exponent values for normalized range (-1022 to 1023)
exponents = np.arange(-1022, 1024)
# Midpoint of each interval [2^k, 2^(k+1))
midpoints = 2 ** (exponents + 0.5)
# Density for each interval
density = 2 ** (52 - exponents)

plt.figure(figsize=(10, 6))
plt.loglog(midpoints, density, label='Density (Normalized Region)')
# Add denormal region density (fixed at 2^1074)
plt.axhline(y=2**1074, xmin=0, xmax=2**-1022/max(midpoints), color='red', linestyle='--', label='Denormal Region Density')

plt.xlabel('Floating-Point Value')
plt.ylabel('Number of Representable Floats per Unit Interval')
plt.title('IEEE 754 Double-Precision Float Density')
plt.legend()
plt.grid(True, which='both', linestyle='--')
plt.show()

Step 3: Match Wikipedia's Style

To replicate the Wikipedia charts closely:

  • Use a clean, minimal style (remove excess grid lines, use neutral colors).
  • Add labels for key values (e.g., maximum finite float 2^1023*(2-2^-52)).
  • Include annotations for special cases (zero, infinity, NaN) if relevant.

Key Notes

  • Denormal numbers have a fixed ULP and density, which is why the chart flattens at the smallest values.
  • For single-precision floats, adjust constants: exponent bits=8 (offset 127), mantissa bits=23, so ULP for normalized values is 2^(exponent - 150).

内容的提问来源于stack exchange,提问作者Fabzi

火山引擎 最新活动