aboutsummaryrefslogtreecommitdiffstats
path: root/yql/essentials/types/dynumber/cast.h
blob: 2625ec446fcc20993f07cb237340f362d5461c64 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#pragma once

#include "dynumber.h"

#include <util/generic/ymath.h>

#include <limits>

namespace NKikimr::NDyNumber {

template <typename T>
TMaybe<T> TryFromDyNumber(TStringBuf buffer) {
    using traits = std::numeric_limits<T>;

    if (!traits::is_specialized || !IsValidDyNumber(buffer)) {
        return Nothing();
    }

    auto result = T();

    auto s = buffer.data();
    if (*s == '\x01') {
        return result;
    }

    const bool negative = !*s++;
    if (negative && !traits::is_signed) {
        return Nothing();
    }

    auto power = ui8(*s++);
    if (negative) {
        power = '\xFF' - power;
    }

    std::function<TMaybe<T>(T, i16)> pow = [&pow](T base, i16 exp) -> TMaybe<T> {
        if (exp > 0) {
            const auto p = Power(base, exp - 1);
            if (!p || p > traits::max()) {
                return Nothing();
            }

            return base * p;
        } else if (exp < 0) {
            if (const auto p = pow(base, abs(exp))) {
                return 1 / *p;
            }

            return Nothing();
        } else {
            return T(1);
        }
    };

    auto apply = [&negative, &result, &pow](ui8 digit, i16 exp) -> bool {
        if (traits::is_integer && (exp < 0 || exp > traits::digits10)) {
            return false;
        }

        if (digit == '\x00') {
            return true;
        }

        if (const auto p = pow(10, exp)) {
            const T mul = digit * *p;

            if (!mul || mul < *p || mul > traits::max()) {
                return false;
            }

            if (negative) {
                if (traits::lowest() - result > -mul) {
                    return false;
                }

                result -= mul;
            } else {
                if (traits::max() - result < mul) {
                    return false;
                }

                result += mul;
            }

            return true;
        }

        return false;
    };

    const auto digits = negative ? "FEDCBA9876543210" : "0123456789ABCDEF";

    auto e = power - 129;
    auto l = buffer.size() - 1;
    while (--l) {
        const auto c = *s++;
        const auto hi = (c >> '\x04') & '\x0F';
        const auto lo = c & '\x0F';

        if (!apply(digits[hi] - '0', --e)) {
            return Nothing();
        }

        if (lo != (negative ? '\x0F' : '\x00') || l > 1U) {
            if (!apply(digits[lo] - '0', --e)) {
                return Nothing();
            }
        }
    }

    return result;
}

}