فهرست مقاله
در بازبینیِ دو نسخه از تنظیماتِ نام، بزرگتر بودنِ عدد همیشه به معنای تازهتر بودن نیست. شمارهٔ سریالِ SOA فضای محدودی دارد و پس از انتهای نمایش به صفر برمیگردد. این درس، رابطهٔ دو نسخه را از زمانِ تقویمی و مرتبسازیِ یک تاریخچه جدا میکند. مثالها ساختگیاند؛ هیچ پرسوجوی DNS، تغییرِ ناحیه یا انتقالِ واقعی انجام نمیشود. هدف، قراردادی کوچک و قابلآزمون است که بهجای پنهانکردنِ ابهام، آن را در خروجی نشان دهد.
۱. سریالِ نسخه، زمانِ تقویمی نیست

در تعریفِ SERIAL در SOA، بخش ۳٫۳٫۱۳ از RFC 1035، این فیلد یک شمارهٔ نسخهٔ بدون علامتِ ۳۲بیتی است. بنابراین مقدارهای عددیِ قابلنمایش از صفر تا ۴۲۹۴۹۶۷۲۹۵ هستند. این فیلد، ساعتِ دیواری، تعدادِ ثانیه یا تاریخِ شمسی نیست؛ واحدِ فیزیکی ندارد. حتی اگر مدیری عددی شبیه تاریخ انتخاب کند، شکلِ ظاهریِ آن، قواعدِ مقایسهٔ سریال را عوض نمیکند.
بخش ۸ از RFC 1982 توضیحِ قدیمیِ «حسابِ فضای توالی» را با تعریفِ دقیقِ حسابِ سریال روشن میکند. برای فضای دارای b بیت، اندازهٔ فضا M=۲ به توان b است. نمایشِ عددی، ابتدا و انتها دارد؛ ولی در معنای سریال، هر مقدار جانشین و پیشینی دارد. پس صفر میتواند جانشینِ ۴۲۹۴۹۶۷۲۹۵ باشد، بدون آنکه یک بازگشتِ زمانی یا کاهشِ نسخه از روی مقایسهٔ معمولِ اعداد نتیجه شود.
این پرسش با خواندنِ نامِ فشردهٔ DNS فرق دارد: آن درس بایت و اشارهگر را رمزگشایی میکرد؛ اینجا دو عددِ صحیحِ ازپیشدادهشده داریم. صحتِ قالبِ پیام، هویتِ سرور، اعتبارِ پاسخ یا سیاستِ بهروزرسانیِ یک ناحیه از این دو عدد ثابت نمیشود. پیش از اتصالِ چنین تابعی به ابزارِ واقعی، زمینهٔ عملیاتی باید جدا تعریف شود.
۲. مقایسهٔ چهارتایی و مرزِ نیمدور

در رابطِ ما، نخست current و سپس candidate میآید؛ پاسخ همیشه نسبتِ نامزد به مقدارِ جاری است. فاصلهٔ رو به جلو را d=(candidate−current) modulo M میگیریم. اگر d صفر باشد، پاسخ equal است. اگر بین صفر و H=M/۲ باشد، newer میدهیم؛ اگر از H بزرگتر باشد، older میدهیم. برای d دقیقاً برابر H، خروجی undefined است. این بازنویسیِ تعریفِ مقایسه در بخش ۳٫۲ از RFC 1982 است؛ جهتِ دو آرگومان را نباید جابهجا تفسیر کرد.
در فضای واقعیِ ۳۲بیتی، مقایسهٔ جاریِ ۴۲۹۴۹۶۷۲۹۵ با نامزدِ صفر، newer میشود؛ مقایسهٔ معکوس older است. در مقابل، جاریِ صفر و نامزدِ ۲۱۴۷۴۸۳۶۴۸ دقیقاً نیمدور فاصله دارند. با تعویضِ دو مقدار نیز ابهام برطرف نمیشود. تبدیلِ اجباریِ این حالت به «قدیمیتر»، اطلاعاتی اختراع میکند که قرارداد در اختیارمان نگذاشته است. ما خروجیِ نامعین را صریح نگه میداریم؛ استاندارد وابستهشدن به یک نتیجهٔ دلخواه در این مرز را توصیه نمیکند.
همسایههای مرز هم اهمیت دارند: نسبت به صفر، مقدارِ ۲۱۴۷۴۸۳۶۴۷ تازهتر و مقدارِ ۲۱۴۷۴۸۳۶۴۹ قدیمیتر است. هر سه مورد در اجرای کد کنترل میشوند. این حساب فقط با اعدادِ صحیح انجام میشود و تلورانسِ شناور ندارد. در بازکردنِ فاز و مرزِ نیمدور نیز محدودیتِ اطلاعات مهم بود، اما آن تابع شاخهٔ یک دنبالهٔ رادیانی را انتخاب میکرد؛ این تابع نه مسیرِ پیوسته میسازد و نه شمارِ دورهای گذشته را بازیابی میکند.
۳. جمعِ مجاز با مرتبسازیِ عمومی فرق دارد

طبق بخش ۳٫۱ از RFC 1982، افزایشِ تعریفشده باید عددِ صحیحِ نامنفی و کوچکتر از نیمِ فضا باشد؛ صفر نیز داخلِ بازه است. نتیجه، باقیماندهٔ جمع بر M است. در SOA، بیشترین افزایشِ منفردِ تعریفشده ۲۱۴۷۴۸۳۶۴۷ است. تابعِ serial_add افزایشِ منفی، نیمدور و بزرگتر از آن را رد میکند؛ گرفتنِ باقیماندهٔ یک ورودیِ نامجاز، آن افزایش را مجاز نمیسازد.
برای دیدنِ مسئله، فضای آموزشیِ چهاربیتی با مقدارهای صفر تا پانزده را در نظر بگیریم. در زنجیرهٔ صفر، پنج، ده، صفر، هر گامِ بعدی نسبت به قبلی newer است: فاصلهها پنج، پنج و ششاند و همگی از هشت کوچکترند. اما ده نسبت به صفر older است. این شاهدِ مستقل، نامتعدیبودنِ رابطه را نشان میدهد؛ بنابراین رابطهٔ عمومیِ حاضر حتی یک ترتیبِ جزئیِ ریاضی هم نیست و مقایسهگری مناسب برای مرتبسازیِ دلخواهِ تمامِ تاریخچه نمیسازد.
مجازبودنِ تکتکِ جمعها نیز کافی نیست: در همان فضای چهاربیتی، صفر بهعلاوهٔ هفت، هفت میشود؛ افزودنِ هفتِ دیگر، چهارده میدهد. هر افزایش جداگانه مجاز است، ولی چهارده نسبت به صفر older است. بخش ۷ از RFC 1982 برای افزایشِ تجمعی در بازهٔ SOA.expire هم احتیاطِ عملیاتی دارد. این برنامه زمان، وضعیتِ سرورهای ثانویه یا آن بازه را مدل نمیکند؛ از شاهدِ عددی، دستورالعملِ تغییرِ سریالِ سامانهٔ واقعی استخراج نمیکنیم.
۴. کدِ کامل و مرجعِ مستقلِ دایرهای

دو تابعِ عمومی فقط int دقیق میپذیرند؛ bool، اعشاری، رشته، مقدارِ منفی و سریالِ خارج از فضا رد میشوند. پارامترِ bits بین دو و ۳۲ است؛ این سقف، نمایهٔ آموزشیِ خودمان است و SOA واقعی همیشه ۳۲ بیت دارد. تابعهای مرجع، برای دادههای کوچکِ معتبرِ نوشتهشده در همین برنامهاند و رابطِ اعتبارسنجیِ عمومی معرفی نمیشوند. کد، فایل یا شبکه را تغییر نمیدهد و وابستگیِ بیرونی ندارد.
"""Bounded serial arithmetic lesson; no DNS packets or server operations."""
import json
import platform
def _space(bits):
if type(bits) is not int or not 2 <= bits <= 32:
raise ValueError("bits_contract")
return 1 << bits
def _serial(value, modulus):
if type(value) is not int or not 0 <= value < modulus:
raise ValueError("serial_contract")
def serial_relation(current, candidate, *, bits=32):
"""Return candidate's relation to current, including undefined half range."""
modulus = _space(bits)
_serial(current, modulus)
_serial(candidate, modulus)
delta = (candidate - current) % modulus
if delta == 0:
return "equal"
if delta == modulus // 2:
return "undefined"
return "newer" if delta < modulus // 2 else "older"
def serial_add(serial, increment, *, bits=32):
"""Only defined nonnegative increments smaller than half the space."""
modulus = _space(bits)
_serial(serial, modulus)
if type(increment) is not int or not 0 <= increment < modulus // 2:
raise ValueError("increment_contract")
return (serial + increment) % modulus
def walk_relation_oracle(current, candidate, bits):
"""Independent small valid fixtures only: visit successor positions."""
assert 2 <= bits <= 6
modulus = 2 ** bits
if candidate == current:
return "equal"
cursor = current
forward = set()
for _ in range(modulus // 2 - 1):
cursor += 1
if cursor == modulus:
cursor = 0
forward.add(cursor)
if candidate in forward:
return "newer"
cursor += 1
if cursor == modulus:
cursor = 0
return "undefined" if candidate == cursor else "older"
def walk_add_oracle(serial, increment, bits):
"""Independent repeated successor; only bounded small authored inputs."""
assert 2 <= bits <= 6
cursor = serial
for _ in range(increment):
cursor += 1
if cursor == 2 ** bits:
cursor = 0
return cursor
def rejected(call, expected):
try:
call()
except ValueError as error:
return str(error) == expected
return False
def run_example():
checks = {}
def check(name, value):
assert type(value) is bool
checks[name] = value
if not value:
raise AssertionError(name)
maximum, half = 4294967295, 2147483648
check("ordinary_direction_equality_and_zero_are_explicit", (
serial_relation(10, 11), serial_relation(11, 10),
serial_relation(0, 0), serial_relation(maximum, maximum)
) == ("newer", "older", "equal", "equal"))
check("actual_32_bit_wrap_is_forward_while_integer_order_is_not", (
serial_relation(maximum, 0), serial_relation(0, maximum),
serial_add(maximum, 1)
) == ("newer", "older", 0) and not (0 > maximum))
half_results = [serial_relation(0, n) for n in (half - 1, half, half + 1)]
check("half_range_and_both_adjacent_values_have_distinct_results",
half_results == ["newer", "undefined", "older"]
and serial_relation(half, 0) == "undefined"
and serial_relation(maximum, half - 1) == "undefined")
check("defined_addition_includes_zero_and_maximum_increment",
serial_add(maximum, 0) == maximum
and serial_add(0, half - 1) == half - 1
and serial_add(maximum, half - 1) == half - 2)
bad_increments = (-1, half, half + 1, 1 << 80, True, False, 1.0, "1", None)
check("undefined_or_noninteger_increments_are_rejected", all(
rejected(lambda value=value: serial_add(0, value), "increment_contract")
for value in bad_increments))
bad_serials = (-1, 1 << 32, 1 << 80, True, False, 0.0, "0", None)
check("both_serial_operands_and_addition_reject_invalid_types_or_range", all(
rejected(lambda value=value: serial_relation(value, 0), "serial_contract")
and rejected(lambda value=value: serial_relation(0, value), "serial_contract")
and rejected(lambda value=value: serial_add(value, 0), "serial_contract")
for value in bad_serials))
bad_widths = (1, 33, -1, True, False, 4.0, "4", None)
check("teaching_width_contract_accepts_two_and_32_only_within_bounds",
serial_relation(3, 0, bits=2) == "newer"
and serial_relation(maximum, 0, bits=32) == "newer"
and all(rejected(lambda value=value: serial_relation(0, 0, bits=value), "bits_contract")
and rejected(lambda value=value: serial_add(0, 0, bits=value), "bits_contract")
for value in bad_widths)
and rejected(lambda: serial_relation(0, 4, bits=2), "serial_contract"))
pair_count, pair_ok, inverse_ok = 0, True, True
inverse = {"equal": "equal", "undefined": "undefined", "newer": "older", "older": "newer"}
for bits in range(2, 7):
for current in range(1 << bits):
for candidate in range(1 << bits):
actual = serial_relation(current, candidate, bits=bits)
pair_ok &= actual == walk_relation_oracle(current, candidate, bits)
inverse_ok &= serial_relation(candidate, current, bits=bits) == inverse[actual]
pair_count += 1
check("all_5456_small_pairs_match_independent_successor_walk", pair_count == 5456 and bool(pair_ok))
add_count, add_ok = 0, True
for bits in range(2, 7):
for current in range(1 << bits):
for increment in range(1 << (bits - 1)):
add_ok &= serial_add(current, increment, bits=bits) == walk_add_oracle(current, increment, bits)
add_count += 1
check("all_2728_small_defined_additions_match_repeated_successor", add_count == 2728 and bool(add_ok))
check("swapping_operands_reverses_defined_direction_not_ambiguity", bool(inverse_ok))
rotation_count, rotation_ok = 0, True
for current in range(16):
for candidate in range(16):
for offset in range(16):
rotation_ok &= serial_relation((current + offset) % 16, (candidate + offset) % 16, bits=4) == serial_relation(current, candidate, bits=4)
rotation_count += 1
check("4096_rotations_preserve_the_four_way_relation", rotation_count == 4096 and bool(rotation_ok))
cycle = (0, 5, 10, 0)
cycle_relations = [serial_relation(a, b, bits=4) for a, b in zip(cycle, cycle[1:])]
dns_cycle = (0, 1431655765, 2863311530, 0)
check("nontransitive_cycle_exists_in_teaching_and_actual_32_bit_spaces",
cycle_relations == ["newer"] * 3
and serial_relation(0, 10, bits=4) == "older"
and all(serial_relation(a, b) == "newer" for a, b in zip(dns_cycle, dns_cycle[1:])))
repeated = (0, serial_add(0, 7, bits=4), serial_add(serial_add(0, 7, bits=4), 7, bits=4))
check("legal_steps_do_not_guarantee_endpoint_newer_than_original",
repeated == (0, 7, 14)
and serial_relation(0, repeated[-1], bits=4) == "older"
and serial_relation(0, serial_add(serial_add(0, 4, bits=4), 4, bits=4), bits=4) == "undefined"
and serial_add(repeated[-1], 2, bits=4) == 0)
lifted = (14, 15, 16, 17, 18)
window_ok = all(
serial_relation(a % 16, b % 16, bits=4) == ("equal" if a == b else "newer" if b > a else "older")
for a in lifted for b in lifted)
check("explicit_common_window_shorter_than_half_preserves_local_order",
max(lifted) - min(lifted) < 8 and window_ok)
assert len(checks) == 14 and all(checks.values())
return {"runtime": {"python": platform.python_version(), "dependencies": "standard library only"},
"contract": "Exact int excluding bool; serials0..2**bits-1; own teaching bits2..32; increments0..2**(bits-1)-1; DNS SOA uses32bits. No packets/server/network/time/history recovery.",
"results": {"dns_max_to_zero_relation": serial_relation(maximum, 0),
"dns_half_boundary_relations": half_results,
"teaching_four_bit_cycle": list(cycle), "cycle_relations": cycle_relations,
"teaching_repeated_seven": list(repeated),
"repeated_endpoint_vs_zero": serial_relation(0, repeated[-1], bits=4),
"explicit_local_lifted_window": list(lifted),
"local_serials": [v % 16 for v in lifted]},
"case_counts_inside_groups": {"independent_pair_oracle": pair_count,
"independent_addition_oracle": add_count,
"rotation_invariance": rotation_count},
"checks": checks}
if __name__ == "__main__":
print(json.dumps(run_example(), ensure_ascii=False, indent=2))
مرجعِ مقایسه، فرمولِ تفریق و باقیمانده را تکرار نمیکند: از مقدارِ جاری، جانشینها را یکییکی طی میکند، جایگاههای پیش از نیمدور را جمع میکند و جایگاهِ روبهرو را جدا تشخیص میدهد. مرجعِ جمع هم افزایش را با گامهای تکواحدی میسازد. برای عرضهای دو تا شش، تمامِ ۵۴۵۶ جفت و ۲۷۲۸ جمعِ مجاز بررسی شدهاند؛ هر خانواده فقط یک گروه است. ۴۰۹۶ چرخشِ چهاربیتی نیز ناورداییِ رابطه با تغییرِ مبدأ را میسنجد.
{
"runtime": {
"python": "3.14.5",
"dependencies": "standard library only"
},
"contract": "Exact int excluding bool; serials0..2**bits-1; own teaching bits2..32; increments0..2**(bits-1)-1; DNS SOA uses32bits. No packets/server/network/time/history recovery.",
"results": {
"dns_max_to_zero_relation": "newer",
"dns_half_boundary_relations": [
"newer",
"undefined",
"older"
],
"teaching_four_bit_cycle": [
0,
5,
10,
0
],
"cycle_relations": [
"newer",
"newer",
"newer"
],
"teaching_repeated_seven": [
0,
7,
14
],
"repeated_endpoint_vs_zero": "older",
"explicit_local_lifted_window": [
14,
15,
16,
17,
18
],
"local_serials": [
14,
15,
0,
1,
2
]
},
"case_counts_inside_groups": {
"independent_pair_oracle": 5456,
"independent_addition_oracle": 2728,
"rotation_invariance": 4096
},
"checks": {
"ordinary_direction_equality_and_zero_are_explicit": true,
"actual_32_bit_wrap_is_forward_while_integer_order_is_not": true,
"half_range_and_both_adjacent_values_have_distinct_results": true,
"defined_addition_includes_zero_and_maximum_increment": true,
"undefined_or_noninteger_increments_are_rejected": true,
"both_serial_operands_and_addition_reject_invalid_types_or_range": true,
"teaching_width_contract_accepts_two_and_32_only_within_bounds": true,
"all_5456_small_pairs_match_independent_successor_walk": true,
"all_2728_small_defined_additions_match_repeated_successor": true,
"swapping_operands_reverses_defined_direction_not_ambiguity": true,
"4096_rotations_preserve_the_four_way_relation": true,
"nontransitive_cycle_exists_in_teaching_and_actual_32_bit_spaces": true,
"legal_steps_do_not_guarantee_endpoint_newer_than_original": true,
"explicit_common_window_shorter_than_half_preserves_local_order": true
}
}
اجرای واقعی با Python 3.14.5، چهارده گروهِ نامدارِ موفق داشته است. مثالِ دوری در فضای ۳۲بیتی هم جدا کنترل شده و قراردادهای نوع، مرزِ افزایش و دو سوی نیمدور آزموده شدهاند. این پوشش، اثباتِ رسمیِ برنامه یا آزمونِ همهٔ جفتهای ۳۲بیتی نیست. منابعِ اوت ۱۹۹۶ و نوامبر ۱۹۸۷ در ۲۷ مهر ۱۴۰۵ خوانده شدهاند؛ تاریخِ دسترسی با تاریخِ انتشارِ سند و نسخهٔ پایتون یکی نیست.
۵. چهار پرسش دربارهٔ صفر، ابهام و ترتیب

پرسش اول: آیا صفر یعنی نسخهای وجود ندارد؟ پاسخ: خیر؛ در حسابِ سریال، صفر یک مقدارِ معتبر و همارز با بقیهٔ مقدارهاست. نبودِ نسخه باید بیرون از این دامنه مدل شود. بخش ۷ از RFC 1982 دربارهٔ رفتارِ نادرستِ برخی پیادهسازیهای قدیمی با صفر هشدار داده است؛ معتبربودنِ ریاضی، توصیه به تنظیمِ صفر در یک سرورِ ناشناخته نیست.
پرسش دوم: با جاریِ صفر و نامزدِ ۲۱۴۷۴۸۳۶۴۸ چه تصمیمی بگیریم؟ پاسخ: تابع undefined میدهد و ترتیب را حدس نمیزند. اگر برنامهٔ بالادستی به تصمیم نیاز دارد، باید اطلاعاتِ اضافی، تاریخچه یا سیاستِ صریحِ حلِ ابهام داشته باشد. انتخابِ خودکارِ older یا newer، نتیجهٔ این درس و اطلاعاتِ همین دو مقدار نیست.
پرسش سوم: چرا دو افزایشِ هفت، پایانِ قدیمیتر میسازند؟ پاسخ: در فضای شانزدهتایی، مجموعِ پیشروی چهارده است و از نیمدور عبور کرده است. چهارده نسبت به صفر، فاصلهٔ رو به جلوی چهارده و فاصلهٔ معکوسِ دو دارد؛ پس older میشود. اگر دو افزایشِ چهار بدهیم، پایان هشت و رابطه نامعین است؛ مجازبودنِ هر گام، رابطهٔ پایان با آغاز را تضمین نمیکند.
پرسش چهارم: آیا هیچ مجموعهای از سریالها را نمیتوان مرتب کرد؟ پاسخ: با فرضِ اضافی میتوان. اگر مقدارهای واقعیِ گسترشیافتهٔ ۱۴ تا ۱۸ را مستقل بدانیم، گسترهٔ چهار از نیمِ فضای چهاربیتی کوچکتر است و نمایشهای ۱۴، ۱۵، صفر، یک، دو همان ترتیبِ محلی را دارند. برنامه این شاهد را میآزماید؛ اما آن تاریخچهٔ گسترشیافته را از سریالها کشف نمیکند. بدون چنین زمینهای، چرخهٔ مثال مانعِ یک ترتیبِ سراسریِ عمومی است.
منابع و مطالعهٔ بیشتر
- RFC 1982؛ حسابِ سریال، جمع، مقایسه و محدودیتِ SOA
- RFC 1035؛ تعریفِ SERIAL در ساختارِ SOA، بخش ۳٫۳٫۱۳
بررسی منابع: ۲۷ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

