From 3e53e3268ad46d65c3c9bfae30c6b6eac1e04d6f Mon Sep 17 00:00:00 2001 From: Vitaliy Lyudvichenko Date: Wed, 11 Jan 2017 18:28:41 +0300 Subject: [PATCH] Add test for accurate numbers comparisons. [#CLICKHOUSE-194] --- .../DB/Functions/FunctionsComparison.h | 18 +-- .../accurate_comparisons.sh | 25 +++ ...00411_accurate_number_comparison.reference | 1 + .../00411_accurate_number_comparison.sh | 143 ++++++++++++++++++ 4 files changed, 178 insertions(+), 9 deletions(-) create mode 100755 dbms/tests/perf_drafts/accurate_comparisons/accurate_comparisons.sh create mode 100644 dbms/tests/queries/0_stateless/00411_accurate_number_comparison.reference create mode 100755 dbms/tests/queries/0_stateless/00411_accurate_number_comparison.sh diff --git a/dbms/include/DB/Functions/FunctionsComparison.h b/dbms/include/DB/Functions/FunctionsComparison.h index d2928f9786a..6e56b5b9db0 100644 --- a/dbms/include/DB/Functions/FunctionsComparison.h +++ b/dbms/include/DB/Functions/FunctionsComparison.h @@ -44,16 +44,16 @@ namespace DB * TODO Массивы. */ -template struct EqualsOp { static UInt8 apply(A a, B b) { return accurate::equalsOp(a, b); } }; -template struct NotEqualsOp { static UInt8 apply(A a, B b) { return accurate::notEqualsOp(a, b); } }; -template struct LessOp { static UInt8 apply(A a, B b) { return accurate::lessOp(a, b); } }; -template struct GreaterOp { static UInt8 apply(A a, B b) { return accurate::greaterOp(a, b); } }; -template struct LessOrEqualsOp { static UInt8 apply(A a, B b) { return accurate::lessOrEqualsOp(a, b); } }; -template struct GreaterOrEqualsOp { static UInt8 apply(A a, B b) { return accurate::greaterOrEqualsOp(a, b); } }; +template struct EqualsOp { static UInt8 apply(A a, B b) { return accurate::equalsOp(a, b); } }; +template struct NotEqualsOp { static UInt8 apply(A a, B b) { return accurate::notEqualsOp(a, b); } }; +template struct LessOp { static UInt8 apply(A a, B b) { return accurate::lessOp(a, b); } }; +template struct GreaterOp { static UInt8 apply(A a, B b) { return accurate::greaterOp(a, b); } }; +template struct LessOrEqualsOp { static UInt8 apply(A a, B b) { return accurate::lessOrEqualsOp(a, b); } }; +template struct GreaterOrEqualsOp { static UInt8 apply(A a, B b) { return accurate::greaterOrEqualsOp(a, b); } }; -/** Игнорируем warning о сравнении signed и unsigned. - * (Результат может быть некорректным.) - */ +// /** Игнорируем warning о сравнении signed и unsigned. +// * (Результат может быть некорректным.) +// */ // #pragma GCC diagnostic push // #pragma GCC diagnostic ignored "-Wsign-compare" // diff --git a/dbms/tests/perf_drafts/accurate_comparisons/accurate_comparisons.sh b/dbms/tests/perf_drafts/accurate_comparisons/accurate_comparisons.sh new file mode 100755 index 00000000000..6600fe43afa --- /dev/null +++ b/dbms/tests/perf_drafts/accurate_comparisons/accurate_comparisons.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +clickhouse-client -q "DROP TABLE IF EXISTS test.comparisons" +clickhouse-client -q "CREATE TABLE test.comparisons (i64 Int64, u64 UInt64, f64 Float64) ENGINE = Memory" +clickhouse-client -q "INSERT INTO test.comparisons SELECT toInt64(rand64()) + number AS i64, number AS u64, reinterpretAsFloat64(reinterpretAsString(rand64())) AS f64 FROM system.numbers LIMIT 90000000" + +function test_cmp { + echo -n "$1 : " + echo -n $(clickhouse-client --max_threads=1 --time -q "SELECT sum(ignore($i)) FROM test.comparisons" 1>/dev/null) +} + +test_cmp "u64 = i64" +test_cmp "u64 >= i64" + +test_cmp "i64 > -1 " +test_cmp "i64 = 0 " +test_cmp "u64 != 0 " + +test_cmp "i64 = f64" +test_cmp "i64 < f64" + +test_cmp "f64 >= 0 " + +clickhouse-client -q "DROP TABLE IF EXISTS test.comparisons" \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00411_accurate_number_comparison.reference b/dbms/tests/queries/0_stateless/00411_accurate_number_comparison.reference new file mode 100644 index 00000000000..53cdf1e9393 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00411_accurate_number_comparison.reference @@ -0,0 +1 @@ +PASSED diff --git a/dbms/tests/queries/0_stateless/00411_accurate_number_comparison.sh b/dbms/tests/queries/0_stateless/00411_accurate_number_comparison.sh new file mode 100755 index 00000000000..59d5ebcdd7e --- /dev/null +++ b/dbms/tests/queries/0_stateless/00411_accurate_number_comparison.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env python +from __future__ import print_function +import os, itertools, urllib + +def get_ch_answer(query): + return urllib.urlopen('http://127.0.0.1:8123', data=query).read() + +def check_answers(query, answer): + ch_answer = get_ch_answer(query) + if ch_answer.strip() != answer.strip(): + print("FAIL on query:", query) + print("Expected answer:", answer) + print("Fetched answer :", ch_answer) + exit(-1) + +def get_values(): + values = [0, 1, -1] + for bits in [8, 16, 32, 64]: + values += [2**bits, 2**bits - 1] + values += [2**(bits-1) - 1, 2**(bits-1), 2**(bits-1) + 1] + values += [-2**(bits-1) - 1, -2**(bits-1), -2**(bits-1) + 1] + return values + +def is_valid_integer(x): + return -2**63 <= x and x <= 2**64-1 + + +TEST_WITH_CASTING=True +GENERATE_TEST_FILES=False + +TYPES = { + "UInt8" : { "bits" : 8, "sign" : False, "float" : False }, + "Int8" : { "bits" : 8, "sign" : True, "float" : False }, + + "UInt16": { "bits" : 16, "sign" : False, "float" : False }, + "Int16" : { "bits" : 16, "sign" : True, "float" : False }, + + "UInt32": { "bits" : 32, "sign" : False, "float" : False }, + "Int32" : { "bits" : 32, "sign" : True, "float" : False }, + + "UInt64": { "bits" : 64, "sign" : False, "float" : False }, + "Int64" : { "bits" : 64, "sign" : True, "float" : False } + + #"Float32" : { "bits" : 32, "sign" : True, "float" : True }, + #"Float64" : { "bits" : 64, "sign" : True, "float" : True } +} + + +def inside_range(value, type_name): + bits = TYPES[type_name]["bits"] + signed = TYPES[type_name]["sign"] + is_float = TYPES[type_name]["float"] + + if is_float: + return True + + if signed: + return -2**(bits-1) <= value and value <= 2**(bits-1) - 1 + else: + return 0 <= value and value <= 2**bits - 1 + + +def test_operators(v1, v2, v1_passed, v2_passed): + query_str = "{v1} = {v2}, {v1} != {v2}, {v1} < {v2}, {v1} <= {v2}, {v1} > {v2}, {v1} >= {v2},\t".format(v1=v1_passed, v2=v2_passed) + query_str += "{v1} = {v2}, {v1} != {v2}, {v1} < {v2}, {v1} <= {v2}, {v1} > {v2}, {v1} >= {v2} ".format(v1=v2_passed, v2=v1_passed) + + answers = [v1 == v2, v1 != v2, v1 < v2, v1 <= v2, v1 > v2, v1 >= v2] + answers += [v2 == v1, v2 != v1, v2 < v1, v2 <= v1, v2 > v1, v2 >= v1] + + answers_str = "\t".join([str(int(x)) for x in answers]) + + return (query_str, answers_str) + + +VALUES = [x for x in get_values() if is_valid_integer(x)] + +def test_pair(v1, v2): + query = "SELECT {}, {}, ".format(v1, v2) + answers = "{}\t{}\t".format(v1, v2) + + q, a = test_operators(v1, v2, str(v1), str(v2)) + query += q + answers += a + + if TEST_WITH_CASTING: + for t1 in TYPES.iterkeys(): + if inside_range(v1, t1): + for t2 in TYPES.iterkeys(): + if inside_range(v2, t2): + q, a = test_operators(v1, v2, 'to{}({})'.format(t1, v1), 'to{}({})'.format(t2, v2)) + query += ', ' + q + answers += "\t" + a + + check_answers(query, answers) + return query, answers + + +VALUES_INT = [0, -1, 1, 2**64-1, 2**63, -2**63, 2**63-1, 2**51, 2**52, 2**53-1, 2**53, 2**53+1, 2**53+2, -2**53+1, -2**53, -2**53-1, -2**53-2, 2*52, -2**52] +VALUES_FLOAT = [float(x) for x in VALUES_INT + [-0.5, 0.5, -1.5, 1.5, 2**53, 2**51 - 0.5, 2**51 + 0.5, 2**60, -2**60, -2**63 - 10000, 2**63 + 10000]] + +def test_float_pair(i, f): + f_str = ("%.9f" % f) + query = "SELECT '{}', '{}', ".format(i, f_str) + answers = "{}\t{}\t".format(i, f_str) + + q, a = test_operators(i, f, i, f_str) + query += q + answers += a + + if TEST_WITH_CASTING: + for t1 in TYPES.iterkeys(): + if inside_range(i, t1): + q, a = test_operators(i, f, 'to{}({})'.format(t1, i), f_str) + query += ', ' + q + answers += "\t" + a + + check_answers(query, answers) + return query, answers + + +def main(): + if GENERATE_TEST_FILES: + base_name = '00411_accurate_number_comparison' + sql_file = open(base_name + '.sql', 'wt') + ref_file = open(base_name + '.reference', 'wt') + + for (v1, v2) in itertools.combinations(VALUES, 2): + q, a = test_pair(v1, v2) + if GENERATE_TEST_FILES: + sql_file.write(q + ";\n") + ref_file.write(a + "\n") + + for (i, f) in itertools.product(VALUES_INT, VALUES_FLOAT): + q, a = test_float_pair(i, f) + if GENERATE_TEST_FILES: + sql_file.write(q + ";\n") + ref_file.write(a + "\n") + + print("PASSED") + + +if __name__ == "__main__": + main()