[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [sbws/master] Make sbws round to 3 significant figures in torflow rounding mode
commit 44a588eeb22bfe6291c3e897723e0524993da396
Author: teor <teor@xxxxxxxxxxxxxx>
Date: Wed Nov 14 20:30:40 2018 +1000
Make sbws round to 3 significant figures in torflow rounding mode
And add unit tests for rounding and rounding error.
Bugfix on 27337 in sbws 1.0.
Part of 28442.
---
sbws/lib/v3bwfile.py | 33 ++++++++++--
tests/unit/lib/test_v3bwfile.py | 113 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 142 insertions(+), 4 deletions(-)
diff --git a/sbws/lib/v3bwfile.py b/sbws/lib/v3bwfile.py
index cae8418..f2077d9 100644
--- a/sbws/lib/v3bwfile.py
+++ b/sbws/lib/v3bwfile.py
@@ -4,6 +4,7 @@
import copy
import logging
+import math
import os
from itertools import combinations
from statistics import median, mean
@@ -57,10 +58,36 @@ BW_KEYVALUES_INT = ['bw', 'rtt', 'success', 'error_stream',
BW_KEYVALUES = BW_KEYVALUES_BASIC + BW_KEYVALUES_EXTRA
+def round_sig_dig(n, digits=TORFLOW_ROUND_DIG):
+ """Round n to 'digits' significant digits in front of the decimal point.
+ Results less than or equal to 1 are rounded to 1.
+ Returns an integer.
+
+ digits must be greater than 0.
+ n must be less than or equal to 2**73, to avoid floating point errors.
+ """
+ assert digits >= 1
+ if n <= 1:
+ return 1
+ digits = int(digits)
+ digits_in_n = int(math.log10(n)) + 1
+ round_digits = max(digits_in_n - digits, 0)
+ rounded_n = round(n, -round_digits)
+ return int(rounded_n)
+
+
def kb_round_x_sig_dig(bw_bs, digits=TORFLOW_ROUND_DIG):
- """Convert bw to KB and round to x most significat digits."""
- bw_kb = bw_bs / 1000
- return max(int(round(bw_kb, -digits)), 1)
+ """Convert bw_bs from bytes to kilobytes, and round the result to
+ 'digits' significant digits.
+ Results less than or equal to 1 are rounded up to 1.
+ Returns an integer.
+
+ digits must be greater than 0.
+ n must be less than or equal to 2**82, to avoid floating point errors.
+ """
+ # avoid double-rounding by using floating-point
+ bw_kb = bw_bs / 1000.0
+ return round_sig_dig(bw_kb, digits=digits)
def num_results_of_type(results, type_str):
diff --git a/tests/unit/lib/test_v3bwfile.py b/tests/unit/lib/test_v3bwfile.py
index a299d05..db82a6c 100644
--- a/tests/unit/lib/test_v3bwfile.py
+++ b/tests/unit/lib/test_v3bwfile.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""Test generation of bandwidth measurements document (v3bw)"""
import json
+import math
import os.path
from sbws import __version__ as version
@@ -9,7 +10,7 @@ from sbws.globals import (SPEC_VERSION, SBWS_SCALING, TORFLOW_SCALING,
from sbws.lib.resultdump import Result, load_result_file, ResultSuccess
from sbws.lib.v3bwfile import (V3BWHeader, V3BWLine, TERMINATOR, LINE_SEP,
KEYVALUE_SEP_V1, num_results_of_type,
- V3BWFile)
+ V3BWFile, round_sig_dig)
from sbws.util.timestamp import now_fname, now_isodt_str, now_unixts
timestamp = 1523974147
@@ -90,6 +91,116 @@ def test_num_results_of_type(result_success, result_error_stream):
assert num_results_of_type([result_error_stream], 'error-stream') == 1
+def assert_round_sig_dig_any_digits(n, result):
+ """Test that rounding n to any reasonable number of significant digits
+ produces result."""
+ max_digits_int64 = int(math.ceil(math.log10(2**64 - 1))) + 1
+ for d in range(1, max_digits_int64 + 1):
+ assert(round_sig_dig(n, digits=d) == result)
+
+
+def assert_round_sig_dig_any_digits_error(n, elp_fraction=0.5):
+ """Test that rounding n to any reasonable number of significant digits
+ produces a result within elp_fraction * 10.0 ** -(digits - 1)."""
+ max_digits_int64 = int(math.ceil(math.log10(2**64 - 1))) + 1
+ for d in range(1, max_digits_int64 + 1):
+ error_fraction = elp_fraction * (10.0 ** -(d - 1))
+ # use ceil rather than round, to work around floating-point inaccuracy
+ e = int(math.ceil(n * error_fraction))
+ assert(round_sig_dig(n, digits=d) >= n - e)
+ assert(round_sig_dig(n, digits=d) <= n + e)
+
+
+def test_round_sig_dig():
+ """Test rounding to a number of significant digits."""
+ # Expected values
+ assert(round_sig_dig(11, 1) == 10)
+ assert(round_sig_dig(11, 2) == 11)
+
+ assert(round_sig_dig(15, 1) == 20)
+ assert(round_sig_dig(15, 2) == 15)
+
+ assert(round_sig_dig(54, 1) == 50)
+ assert(round_sig_dig(54, 2) == 54)
+
+ assert(round_sig_dig(96, 1) == 100)
+ assert(round_sig_dig(96, 2) == 96)
+
+ assert(round_sig_dig(839, 1) == 800)
+ assert(round_sig_dig(839, 2) == 840)
+ assert(round_sig_dig(839, 3) == 839)
+
+ assert(round_sig_dig(5789, 1) == 6000)
+ assert(round_sig_dig(5789, 2) == 5800)
+ assert(round_sig_dig(5789, 3) == 5790)
+ assert(round_sig_dig(5789, 4) == 5789)
+
+ assert(round_sig_dig(24103, 1) == 20000)
+ assert(round_sig_dig(24103, 2) == 24000)
+ assert(round_sig_dig(24103, 3) == 24100)
+ assert(round_sig_dig(24103, 4) == 24100)
+ assert(round_sig_dig(24103, 5) == 24103)
+
+ # Floating-point values
+
+ # Must round based on fractions, must not double-round
+ assert(round_sig_dig(14, 1) == 10)
+ assert(round_sig_dig(14.0, 1) == 10)
+ assert(round_sig_dig(14.9, 1) == 10)
+ assert(round_sig_dig(15.0, 1) == 20)
+ assert(round_sig_dig(15.1, 1) == 20)
+
+ assert(round_sig_dig(14, 2) == 14)
+ assert(round_sig_dig(14.0, 2) == 14)
+ assert(round_sig_dig(14.9, 2) == 15)
+ assert(round_sig_dig(15.0, 2) == 15)
+ assert(round_sig_dig(15.1, 2) == 15)
+
+ # Must round to integer
+ assert(round_sig_dig(14, 3) == 14)
+ assert(round_sig_dig(14.0, 3) == 14)
+ assert(round_sig_dig(14.9, 3) == 15)
+ assert(round_sig_dig(15.0, 3) == 15)
+ assert(round_sig_dig(15.1, 3) == 15)
+
+ # Small integers
+ assert_round_sig_dig_any_digits(0, 1)
+ assert_round_sig_dig_any_digits(1, 1)
+ assert_round_sig_dig_any_digits(2, 2)
+ assert_round_sig_dig_any_digits(9, 9)
+ assert_round_sig_dig_any_digits(10, 10)
+
+ # Large values
+ assert_round_sig_dig_any_digits_error(2**30)
+ assert_round_sig_dig_any_digits_error(2**31)
+ assert_round_sig_dig_any_digits_error(2**32)
+
+ # the floating-point accuracy limit for this function is 2**73
+ # on some machines
+ assert_round_sig_dig_any_digits_error(2**62)
+ assert_round_sig_dig_any_digits_error(2**63)
+ assert_round_sig_dig_any_digits_error(2**64)
+
+ # Out of range values: must round to 1
+ assert_round_sig_dig_any_digits(-0.01, 1)
+ assert_round_sig_dig_any_digits(-1, 1)
+ assert_round_sig_dig_any_digits(-10.5, 1)
+ assert_round_sig_dig_any_digits(-(2**31), 1)
+
+ # test the transition points in the supported range
+ # testing the entire range up to 1 million takes 100s
+ for n in range(1, 20000):
+ assert_round_sig_dig_any_digits_error(n)
+
+ # use a step that is relatively prime, to increase the chance of
+ # detecting errors
+ for n in range(90000, 200000, 9):
+ assert_round_sig_dig_any_digits_error(n)
+
+ for n in range(900000, 2000000, 99):
+ assert_round_sig_dig_any_digits_error(n)
+
+
def test_v3bwline_from_results_file(datadir):
lines = datadir.readlines('results.txt')
d = dict()
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits