محاسبهٔ RMS کوتاه به نظر می‌رسد: نمونه‌ها را مربع کنیم، میانگین بگیریم و ریشه بزنیم. همین ترتیب ممکن است برای عددی بسیار بزرگ، پیش از رسیدن به جواب متناهی سرریز کند؛ برای عدد بسیار کوچک نیز مربع به صفر گرد شود. در این آموزش، نمونه‌های ولتاژ کاملاً ساختگی‌اند. هدف، آزمون قرارداد و استحکام عددی یک محاسبهٔ محدود است؛ نه اندازه‌گیری برق، کالیبراسیون حسگر یا ادعای برتری سرعت یک کتابخانه.

۱. مقدار مؤثر کل با میانگین و RMS حول میانگین فرق دارد

تفکیک مقدار مؤثر کل، میانگین و مؤلفهٔ حول میانگین
تصویر مفهومی: تفکیک مقدار مؤثر کل، میانگین و مؤلفهٔ حول میانگین؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

برای N نمونهٔ حقیقی با وزن برابر، RMS کل برابر ریشهٔ میانگین مربع نمونه‌هاست. این همان تعریف برداری در مستندات جاری rms متلب است. ورودی ما فقط یک بردار حقیقی محدود دارد؛ رفتار ماتریس، مختلط یا گزینه‌های حذف NaN را بازسازی نمی‌کنیم. واحد نمونه و خروجی دامنه، ولت است؛ مربع‌ها واحد ولتِ مربع دارند و ریشه، واحد ولت را برمی‌گرداند.

سه عدد جدا گزارش می‌کنیم: RMS کل، میانگین علامت‌دار پنجره و RMS نمونه‌ها حول همان میانگین. مقدار سوم را در این متن مؤلفهٔ AC پنجره می‌نامیم؛ حذف یک میانگین عددی است و فیلتر فرکانسی یا مدار کوپلینگ AC نیست. برای دنبالهٔ یک، سه، یک، سه ولت، میانگین دو ولت، RMS حول میانگین یک ولت و RMS کل رادیکال پنج، تقریباً ۲٫۲۳۶۰۶۸ ولت است.

همهٔ تقسیم‌ها بر N هستند؛ تصحیح N منهای یکِ برآورد نمونه‌ای را وارد تعریف RMS نمی‌کنیم. در حساب دقیق، مربع RMS کل برابر مربع میانگین به‌علاوهٔ مربع RMS حول میانگین است. این رابطه در float به‌صورت تقریبی ارزیابی می‌شود. از آن برای بازسازی مؤلفهٔ کوچک با تفریق دو عدد بزرگ استفاده نمی‌کنیم، چون ممکن است اطلاعات تغییرات کوچک در تفریق از دست برود.

بحث مقدار مؤثر در OpenStax رابطهٔ دامنه تقسیم بر رادیکال دو را در زمینهٔ موج سینوسی توضیح می‌دهد. آن نسبت، فرمول همهٔ دنباله‌ها نیست. در این مثال نه دورهٔ موج معلوم است، نه نرخ نمونه‌برداری و نه مقاومت بار؛ بنابراین از خروجی، توان واقعی یا RMS زمانیِ یک سیگنال پیوسته نتیجه نمی‌گیریم.

۲. پیش از مربع‌کردن، مقیاس را جدا کنیم

پرهیز از مربع کردن مستقیم مقادیر بسیار بزرگ و کوچک
تصویر مفهومی: پرهیز از مربع کردن مستقیم مقادیر بسیار بزرگ و کوچک؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

بزرگ‌ترین قدرمطلق نمونه‌ها را s می‌گیریم. اگر s صفر باشد، هر سه خروجی صفرند. در غیر این صورت، هر نمونه بر s تقسیم می‌شود؛ نسبت‌ها بین منفی یک و مثبت یک قرار می‌گیرند. ابتدا ریشهٔ میانگین مربع نسبت‌ها را حساب می‌کنیم و در پایان در s ضرب می‌کنیم. این ترتیب، مربع‌کردن مستقیم مقدارهایی مانند ده به توان دویست را از مسیر اصلی حذف می‌کند.

برای جمع نسبت‌های مربع‌شده از math.fsum استفاده می‌کنیم. این تابع برای کاهش خطای جمع، جمع‌های میانی را نگه می‌دارد؛ عددی را که پیش‌تر بر اثر گردکردن ناپدید شده بازنمی‌گرداند. تقسیم، مربع، ریشه و ضرب پایانی همچنان عملیات ممیز شناورند. نسبتِ نمونه‌ای بسیار کوچک به مقیاس بزرگ ممکن است صفر شود؛ پس روش را دقیق برای هر دامنهٔ دینامیکی معرفی نمی‌کنیم.

برای تغییرات کوچک نزدیک DC، ابتدا اختلاف هر نمونه با نمونهٔ نخست را می‌سازیم و سپس آن اختلاف‌ها را مقیاس‌بندی و مرکزگذاری می‌کنیم. در زوج ده به توان شانزده و دو واحد بیشتر، اختلاف دو قابل نمایش است و RMS حول میانگین یک ولت می‌ماند. میانگین گزارش‌شده ممکن است گرد شود؛ مؤلفهٔ کوچک را با کم‌کردن آن میانگینِ گزارش‌شده از ورودی بازسازی نمی‌کنیم.

خود اختلاف دو مقدار متناهی با علامت مخالف ممکن است سرریز کند. کد در آن حالت به نسبت‌های مقیاس کلی برمی‌گردد و مسیر انتخاب‌شده را گزارش می‌کند. این جایگزین برای مرز مشخص آزمون مفید است؛ دقت نسبی دلخواه برای هر تغییر کوچک را تضمین نمی‌کند. اگر خروجی نهایی نامتناهی شود، خطای صریح می‌دهد. انتخاب تلورانس باید با محدوده و مرجع آزمایش همراه باشد.

۳. کد کامل و مرجع مستقل برای نمونه‌های معلوم

کاهش حذف عددی با مرکزگذاری جابه‌جاشده نزدیک DC
تصویر مفهومی: کاهش حذف عددی با مرکزگذاری جابه‌جاشده نزدیک DC؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

فایل sampled_rms.py با Python 3.14.5 اجرا شده و وابستگی خارجی ندارد. ظرف ورودی باید list یا tuple غیرخالی و هر عضو int یا float دقیق باشد؛ bool، رشته، مختلط، NaN و بی‌نهایت رد می‌شوند. عدد صحیح هنگام تبدیل به float می‌تواند گرد شود؛ اگر در دامنهٔ متناهی جا نشود رد می‌شود. واحد فقط V است؛ mV به‌طور خودکار حدس زده یا تبدیل نمی‌شود.

"""Finite equally weighted synthetic voltage samples; no hardware or time trace."""
from decimal import Decimal, localcontext
from fractions import Fraction
import json
import math
import platform
import sys


def validate_samples(samples, unit):
    if type(unit) is not str or unit != 'V':
        raise ValueError('unit must be exactly V')
    if type(samples) not in (list, tuple) or not samples:
        raise ValueError('samples must be a nonempty list or tuple')
    values = []
    for value in samples:
        if type(value) not in (int, float):
            raise TypeError('sample must be an exact int or float, excluding bool')
        try:
            converted = float(value)
        except OverflowError:
            raise ValueError('sample does not fit finite float') from None
        if not math.isfinite(converted):
            raise ValueError('sample must be finite')
        values.append(converted)
    return tuple(values)


def rms_scaled(values):
    scale = max(abs(value) for value in values)
    if scale == 0:
        return 0.0
    mean_square = math.fsum((value / scale) ** 2 for value in values) / len(values)
    return scale * math.sqrt(mean_square)


def sampled_rms(samples, unit='V'):
    values = validate_samples(samples, unit)
    scale = max(abs(value) for value in values)
    if scale == 0:
        return {'count': len(values), 'unit': 'V', 'total_rms_V': 0.0,
                'dc_mean_V': 0.0, 'centered_rms_V': 0.0, 'centering': 'all_zero'}
    ratios = tuple(value / scale for value in values)
    mean_ratio = math.fsum(ratios) / len(values)
    dc = scale * mean_ratio
    total = rms_scaled(values)
    # Shifting before normalization preserves small represented differences near DC.
    offsets = tuple(value - values[0] for value in values)
    if all(math.isfinite(value) for value in offsets):
        offset_scale = max(abs(value) for value in offsets)
        if offset_scale == 0:
            centered = 0.0
        else:
            relative = tuple(value / offset_scale for value in offsets)
            center = math.fsum(relative) / len(values)
            centered = offset_scale * rms_scaled(tuple(value - center for value in relative))
        path = 'shifted_offsets'
    else:
        # Extreme opposite signs can overflow a subtraction even for finite inputs.
        centered = scale * rms_scaled(tuple(value - mean_ratio for value in ratios))
        path = 'global_scaled_fallback'
    if not all(math.isfinite(value) for value in (total, dc, centered)):
        raise ArithmeticError('nonfinite result after floating-point scaling')
    return {'count': len(values), 'unit': 'V', 'total_rms_V': total,
            'dc_mean_V': dc, 'centered_rms_V': centered, 'centering': path}


def exact_fixture_reference(samples):
    """Exact rational moments of already represented floats; 100-digit sqrt only."""
    exact = tuple(Fraction.from_float(float(value)) for value in samples)
    mean = sum(exact, Fraction(0)) / len(exact)
    total_square = sum((value * value for value in exact), Fraction(0)) / len(exact)
    centered_square = sum(((value - mean) ** 2 for value in exact), Fraction(0)) / len(exact)
    with localcontext() as context:
        context.prec = 100
        def sqrt_fraction(value):
            return float((Decimal(value.numerator) / Decimal(value.denominator)).sqrt())
        return {'dc_mean_V': float(mean), 'total_rms_V': sqrt_fraction(total_square),
                'centered_rms_V': sqrt_fraction(centered_square)}


def close(a, b):
    return math.isclose(a, b, rel_tol=2e-15, abs_tol=0.0)


def rejects(samples, unit='V'):
    try:
        sampled_rms(samples, unit)
    except (TypeError, ValueError):
        return True
    return False


def run_example():
    main = [1.0, 3.0, 1.0, 3.0]
    result = sampled_rms(main)
    huge = sampled_rms([1e200, -1e200])
    tiny = sampled_rms([1e-200, -1e-200])
    near_dc = sampled_rms([1e16, 1e16 + 2])
    maximum = sampled_rms([sys.float_info.max, -sys.float_info.max])
    fixtures = [main, [0.0], [-4.0, -4.0], [3.0, 4.0], [-3, -1, 1, 3],
                [1e200, -1e200], [1e-200, -1e-200], [1e16, 1e16 + 2],
                [sys.float_info.max, -sys.float_info.max]]
    checks = {}
    checks['hand_total_mean_and_centered'] = close(result['total_rms_V'], math.sqrt(5)) and result['dc_mean_V'] == 2.0 and result['centered_rms_V'] == 1.0
    checks['independent_exact_fraction_fixture_moments'] = all(
        all(close(sampled_rms(values)[key], expected) for key, expected in exact_fixture_reference(values).items())
        for values in fixtures)
    checks['all_zero_and_single_sample'] = sampled_rms([0, -0.0])['total_rms_V'] == 0.0 and sampled_rms([7])['centered_rms_V'] == 0.0 and sampled_rms([7])['total_rms_V'] == 7.0
    checks['negative_constant_has_nonzero_total_zero_centered'] = sampled_rms([-4, -4])['total_rms_V'] == 4.0 and sampled_rms([-4, -4])['dc_mean_V'] == -4.0 and sampled_rms([-4, -4])['centered_rms_V'] == 0.0
    checks['huge_samples_avoid_naive_square_overflow'] = math.isinf(1e200 * 1e200) and huge['total_rms_V'] == 1e200 and huge['centered_rms_V'] == 1e200
    checks['tiny_samples_avoid_naive_square_underflow'] = 1e-200 * 1e-200 == 0.0 and tiny['total_rms_V'] == 1e-200 and tiny['centered_rms_V'] == 1e-200
    checks['opposite_maximum_values_take_finite_fallback'] = maximum['centering'] == 'global_scaled_fallback' and maximum['dc_mean_V'] == 0.0 and maximum['total_rms_V'] == sys.float_info.max and maximum['centered_rms_V'] == sys.float_info.max
    checks['shifted_offsets_preserve_small_represented_ripple'] = near_dc['centering'] == 'shifted_offsets' and near_dc['centered_rms_V'] == 1.0 and exact_fixture_reference([1e16, 1e16 + 2])['centered_rms_V'] == 1.0
    checks['homogeneity_on_authored_binary_scales'] = all(
        close(sampled_rms([factor * x for x in main])['total_rms_V'], abs(factor) * result['total_rms_V']) and
        close(sampled_rms([factor * x for x in main])['centered_rms_V'], abs(factor) * result['centered_rms_V']) and
        close(sampled_rms([factor * x for x in main])['dc_mean_V'], factor * result['dc_mean_V'])
        for factor in (0.25, -2.0, 8.0))
    permuted = sampled_rms([3.0, 1.0, -1.0, -3.0])
    checks['permutation_on_symmetric_fixture'] = close(permuted['total_rms_V'], math.sqrt(5)) and permuted['dc_mean_V'] == 0.0 and close(permuted['centered_rms_V'], math.sqrt(5))
    checks['duplicating_window_preserves_summaries'] = all(close(sampled_rms(main * 3)[key], result[key]) for key in ('total_rms_V', 'dc_mean_V', 'centered_rms_V'))
    checks['nonnegative_bounds_and_energy_identity_safe_range'] = all(
        0 <= sampled_rms(values)['total_rms_V'] <= max(abs(x) for x in values) and
        sampled_rms(values)['centered_rms_V'] >= 0 and
        close(sampled_rms(values)['total_rms_V'] ** 2,
              sampled_rms(values)['dc_mean_V'] ** 2 + sampled_rms(values)['centered_rms_V'] ** 2)
        for values in ([1, 3, 1, 3], [-4, -4], [-3, -1, 1, 3], [3, 4]))
    checks['rejects_bool_string_none_complex_and_nested'] = all(rejects([value]) for value in (True, '1', None, 1j, [1]))
    checks['rejects_nonfinite_and_integer_float_overflow'] = all(rejects([value]) for value in (float('nan'), float('inf'), -float('inf'), 10 ** 400))
    checks['nonempty_list_tuple_only'] = all(rejects(value) for value in ([], (), {}, iter(main), '123')) and sampled_rms(tuple(main)) == result
    checks['unit_exactly_volts_no_inference'] = all(rejects(main, value) for value in ('mV', 'v', '', None, True))
    checks['integer_conversion_uses_represented_float'] = sampled_rms([2 ** 53 + 1])['dc_mean_V'] == float(2 ** 53 + 1) == float(2 ** 53)
    copy = main[:]
    checks['repeatable_without_input_mutation'] = sampled_rms(main) == result and main == copy
    if len(checks) != 18 or not all(type(v) is bool and v for v in checks.values()):
        raise AssertionError(checks)
    return {'python_version': platform.python_version(), 'runtime': 'Python standard library only',
            'units': 'All input and output amplitudes in V; equally weighted finite samples, no timestamps.',
            'main_window': main, 'main_summary': result, 'huge_summary': huge, 'tiny_summary': tiny,
            'near_dc_summary': near_dc, 'maximum_summary': maximum,
            'checks': checks, 'unique_checks': 18,
            'reference': 'Exact Fraction moments of represented floats, then Decimal sqrt with precision100 for finite authored fixtures.',
            'limitations': ['No measured waveform, sampling clock, MATLAB execution, power-meter or calibrated instrument.',
                            'Centered RMS uses the arithmetic mean of this same finite equally weighted window; it is not frequency-selective AC coupling.',
                            'Scaling/rounding can lose tiny ratios and differences; no universal exactness or arbitrary dynamic-range relative-error guarantee.',
                            'Integer-to-float conversion may round; values represent floats, not exact original engineering measurements.',
                            'Moment identity checked only at safe magnitudes to avoid squaring huge/tiny outputs again.']}


if __name__ == '__main__':
    print(json.dumps(run_example(), ensure_ascii=False, indent=2))

مرجع دوم، میانگین و مربع‌ها را با Fraction روی مقدار دقیقِ floatهای موجود محاسبه می‌کند؛ مسیرش جمع نسبت‌های float نیست. سپس فقط تقسیم و ریشهٔ مرجع با Decimal و دقت صد رقم انجام می‌شود. Fraction.from_float مقدار ذخیره‌شده را حفظ می‌کند، نه اندازه‌گیری اعشاریِ پیش از ورود به برنامه. ریشهٔ گنگ همچنان تقریب است؛ این مرجع، اثبات گردشدن درست همهٔ ورودی‌ها نیست.

هجده گروه بررسی اجرا شده‌اند. مرجع کسری روی نه fixture مشخص، پاسخ دستی، ثابت منفی، صفر، دامنهٔ بزرگ و کوچک، علامت مخالفِ بیشینهٔ float، تغییر مقیاس، جابه‌جایی نمونه‌ها و خطاهای نوع پوشش داده می‌شوند. تلورانس نسبی دو ضرب‌در ده به توان منفی پانزده و تلورانس مطلق صفر برای این مقایسه‌های محدود است؛ عدم قطعیت ابزار یا خطای مجاز یک پروژهٔ واقعی نیست.

۴. نتیجهٔ اجرا و تفسیر مرزهای عددی

مرجع مستقل کسری و نمونه‌های دستی برای آزمون RMS
تصویر مفهومی: مرجع مستقل کسری و نمونه‌های دستی برای آزمون RMS؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

خروجی اصلی با پاسخ دستی سازگار است. برای زوج مثبت و منفیِ ده به توان دویست، مربع مستقیم بی‌نهایت می‌شود ولی RMS مقیاس‌بندی‌شده همان ده به توان دویست باقی می‌ماند. برای زوج ده به توان منفی دویست، مربع مستقیم صفر می‌شود ولی RMS این fixture غیرصفر است. این اعداد آزمایش محدودهٔ نمایش‌اند؛ ولتاژ ساخته‌شده یا اعمال‌شده به تجهیزات نیستند.

{
  "python_version": "3.14.5",
  "runtime": "Python standard library only",
  "units": "All input and output amplitudes in V; equally weighted finite samples, no timestamps.",
  "main_window": [
    1.0,
    3.0,
    1.0,
    3.0
  ],
  "main_summary": {
    "count": 4,
    "unit": "V",
    "total_rms_V": 2.23606797749979,
    "dc_mean_V": 2.0,
    "centered_rms_V": 1.0,
    "centering": "shifted_offsets"
  },
  "huge_summary": {
    "count": 2,
    "unit": "V",
    "total_rms_V": 1e+200,
    "dc_mean_V": 0.0,
    "centered_rms_V": 1e+200,
    "centering": "shifted_offsets"
  },
  "tiny_summary": {
    "count": 2,
    "unit": "V",
    "total_rms_V": 1e-200,
    "dc_mean_V": 0.0,
    "centered_rms_V": 1e-200,
    "centering": "shifted_offsets"
  },
  "near_dc_summary": {
    "count": 2,
    "unit": "V",
    "total_rms_V": 1e+16,
    "dc_mean_V": 1e+16,
    "centered_rms_V": 1.0,
    "centering": "shifted_offsets"
  },
  "maximum_summary": {
    "count": 2,
    "unit": "V",
    "total_rms_V": 1.7976931348623157e+308,
    "dc_mean_V": 0.0,
    "centered_rms_V": 1.7976931348623157e+308,
    "centering": "global_scaled_fallback"
  },
  "checks": {
    "hand_total_mean_and_centered": true,
    "independent_exact_fraction_fixture_moments": true,
    "all_zero_and_single_sample": true,
    "negative_constant_has_nonzero_total_zero_centered": true,
    "huge_samples_avoid_naive_square_overflow": true,
    "tiny_samples_avoid_naive_square_underflow": true,
    "opposite_maximum_values_take_finite_fallback": true,
    "shifted_offsets_preserve_small_represented_ripple": true,
    "homogeneity_on_authored_binary_scales": true,
    "permutation_on_symmetric_fixture": true,
    "duplicating_window_preserves_summaries": true,
    "nonnegative_bounds_and_energy_identity_safe_range": true,
    "rejects_bool_string_none_complex_and_nested": true,
    "rejects_nonfinite_and_integer_float_overflow": true,
    "nonempty_list_tuple_only": true,
    "unit_exactly_volts_no_inference": true,
    "integer_conversion_uses_represented_float": true,
    "repeatable_without_input_mutation": true
  },
  "unique_checks": 18,
  "reference": "Exact Fraction moments of represented floats, then Decimal sqrt with precision100 for finite authored fixtures.",
  "limitations": [
    "No measured waveform, sampling clock, MATLAB execution, power-meter or calibrated instrument.",
    "Centered RMS uses the arithmetic mean of this same finite equally weighted window; it is not frequency-selective AC coupling.",
    "Scaling/rounding can lose tiny ratios and differences; no universal exactness or arbitrary dynamic-range relative-error guarantee.",
    "Integer-to-float conversion may round; values represent floats, not exact original engineering measurements.",
    "Moment identity checked only at safe magnitudes to avoid squaring huge/tiny outputs again."
  ]
}

در زوج نزدیک DC، مؤلفهٔ حول میانگین یک ولت است، ولی میانگین خروجی به ده به توان شانزده گرد می‌شود. این تفاوت نشان می‌دهد سه خروجی گرد‌شده لزوماً هویت مربع‌ها را دقیقاً برآورده نمی‌کنند. همچنین آزمونِ هویت را فقط در دامنهٔ امن اجرا می‌کنیم؛ مربع‌کردن دوبارهٔ خروجی عظیم، همان خطای سرریزِ حذف‌شده را به بررسی بازمی‌گرداند.

وزن برابر برای این پنجره فرض شده است. با زمان‌های نامنظم، میانگین زمانی به وزن‌گذاری و قرارداد دیگری نیاز دارد. آموزش واریانس جریانی Welford دربارهٔ به‌روزرسانی آماره است؛ اینجا روی مربع دامنه و مقیاس تمرکز داریم. معیار پذیرش دامنهٔ FFT نیز مسئلهٔ دامنهٔ فرکانسی و پنجره را جدا بررسی می‌کند.

۵. چهار تمرین پاسخ‌دار برای استفادهٔ دقیق

مرز آمارهٔ پنجرهٔ نمونه‌ها با اندازه‌گیری واقعی برق
تصویر مفهومی: مرز آمارهٔ پنجرهٔ نمونه‌ها با اندازه‌گیری واقعی برق؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

تمرین اول: اگر علامت همهٔ نمونه‌های مثال عوض شود، چه تغییر می‌کند؟ پاسخ: میانگین منفی دو ولت می‌شود؛ RMS کل رادیکال پنج و RMS حول میانگین یک ولت باقی می‌مانند، چون مربع به علامت وابسته نیست.

تمرین دوم: برای نمونهٔ ثابت منفی چهار ولت چه داریم؟ پاسخ: میانگین منفی چهار، RMS کل چهار و RMS حول میانگین صفر ولت است. غیرصفر بودن DC، وجود تغییر حول میانگین را ثابت نمی‌کند.

تمرین سوم: داده با واحد میلی‌ولت آمده؛ می‌توان برچسب را V گذاشت؟ پاسخ: خیر؛ ابتدا هر مقدار را بر هزار تقسیم و سپس با واحد V فراخوانی می‌کنیم. تغییر نام واحد به‌تنهایی، تبدیل دامنه نیست و کد mV را رد می‌کند.

تمرین چهارم: آیا fsum تغییر گم‌شده هنگام ورود عدد را بازیابی می‌کند؟ پاسخ: خیر؛ راهنمای ممیز شناور پایتون محدودیت نمایش را توضیح می‌دهد. نمونهٔ گرد‌شده شاهد اندازه‌گیری اولیه نیست. منابع در ۲۴ مهر ۱۴۰۵ بازخوانی شده‌اند؛ هیچ اجرای MATLAB، سنجش سخت‌افزار یا تضمین عمومی دقت از این بررسی‌ها نتیجه نمی‌شود.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۲۴ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.