mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Merge branch 'dev' into usd-json
This commit is contained in:
@@ -403,6 +403,7 @@ set(TINYUSDZ_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/src/value-pprint.cc
|
||||
${PROJECT_SOURCE_DIR}/src/value-types.cc
|
||||
${PROJECT_SOURCE_DIR}/src/tiny-format.cc
|
||||
${PROJECT_SOURCE_DIR}/src/tiny-string.cc
|
||||
${PROJECT_SOURCE_DIR}/src/io-util.cc
|
||||
${PROJECT_SOURCE_DIR}/src/image-loader.cc
|
||||
${PROJECT_SOURCE_DIR}/src/image-writer.cc
|
||||
|
||||
@@ -533,6 +533,7 @@ Some helper code is licensed under MIT license.
|
||||
* SDL2 : zlib license. https://www.libsdl.org/index.php
|
||||
* optional-lite: BSL 1.0 license. https://github.com/martinmoene/optional-lite
|
||||
* expected-lite: BSL 1.0 license. https://github.com/martinmoene/expected-lite
|
||||
* string-view-lite: BSL 1.0 license. https://github.com/martinmoene/string-view-lite
|
||||
* mapbox/earcut.hpp: ISC license. https://github.com/mapbox/earcut.hpp
|
||||
* par_shapes.h generate parametric surfaces and other simple shapes: MIT license https://github.com/prideout/par
|
||||
* MaterialX: Apache 2.0 license. https://github.com/AcademySoftwareFoundation/MaterialX
|
||||
@@ -567,6 +568,7 @@ Some helper code is licensed under MIT license.
|
||||
* pugixml: MIT license. https://github.com/zeux/pugixml
|
||||
* nanoflann: 2-clause BSD license. https://github.com/jlblancoc/nanoflann
|
||||
* tinymeshutils: MIT license. https://github.com/syoyo/tinymeshutils
|
||||
* dragonbox : Apache 2.0 or Boost 1.0 license(tinyusdz prefer Boost 1.0 license) https://github.com/jk-jeon/dragonbox
|
||||
* criterion(for benchmark): MIT license. https://github.com/p-ranav/criterion
|
||||
* yyjson: MIT license. https://github.com/ibireme/yyjson
|
||||
* civetweb: MIT license. https://github.com/civetweb/civetweb
|
||||
|
||||
2
sandbox/parse_fp/Makefile
Normal file
2
sandbox/parse_fp/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
all:
|
||||
g++ -O2 -g -I../../src/external/fast_float/include parse_fp.cc -o parse_fp -pthread
|
||||
9
sandbox/parse_fp/README.md
Normal file
9
sandbox/parse_fp/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Ryzen 3900X
|
||||
-O2 -g
|
||||
|
||||
1024*1024*32(32M floats) : roughly 870 msecs to lex.
|
||||
|
||||
# TODO
|
||||
|
||||
multithreading?
|
||||
|
||||
1683
sandbox/parse_fp/parse_fp.cc
Normal file
1683
sandbox/parse_fp/parse_fp.cc
Normal file
File diff suppressed because it is too large
Load Diff
12
sandbox/parse_int/Makefile
Normal file
12
sandbox/parse_int/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
all:
|
||||
clang++ -O2 -g -stdlib=libc++ parse_int.cc -o parse_int ../../src/tiny-string.cc
|
||||
|
||||
clean:
|
||||
rm -f parse_int a.out
|
||||
|
||||
test: all
|
||||
./parse_int 1000000 1 1
|
||||
./parse_int 1000000 1 4
|
||||
./parse_int 1000000 1 8
|
||||
|
||||
.PHONY: all clean test
|
||||
64
sandbox/parse_int/README.md
Normal file
64
sandbox/parse_int/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Efficient Integer Array Parser
|
||||
|
||||
Based on the efficient float parsing implementation in `../parse_fp`, this is an optimized integer array parser that can handle large arrays with multithreading support.
|
||||
|
||||
## Features
|
||||
|
||||
- **Fast lexing**: Efficient tokenization of integer arrays in `[1,2,3,...]` format
|
||||
- **Multithreaded parsing**: Uses `std::from_chars` with thread pool for large arrays
|
||||
- **Memory efficient**: Zero-copy lexing using spans pointing to original input
|
||||
- **Robust error handling**: Comprehensive validation and error reporting
|
||||
- **Configurable**: Support for trailing delimiters and custom separators
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
make
|
||||
./parse_int [num_elements] [delim_at_end] [num_threads]
|
||||
```
|
||||
|
||||
### Parameters
|
||||
- `num_elements`: Number of integers to generate and parse (default: 33554432)
|
||||
- `delim_at_end`: Allow trailing comma (1=yes, 0=no, default: 1)
|
||||
- `num_threads`: Number of threads for parsing (default: 1)
|
||||
|
||||
### Examples
|
||||
```bash
|
||||
# Parse 1M integers with 4 threads
|
||||
./parse_int 1000000 1 4
|
||||
|
||||
# Parse 10M integers, no trailing comma, single-threaded
|
||||
./parse_int 10000000 0 1
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Two-Phase Parsing
|
||||
1. **Lexing Phase**: Fast scan through input to identify integer boundaries
|
||||
- Returns `int_lex_span` objects with pointer + length
|
||||
- Handles whitespace, delimiters, and validation
|
||||
- O(n) single pass through input
|
||||
|
||||
2. **Parsing Phase**: Convert lexed spans to actual integers
|
||||
- Uses fast `std::from_chars` for conversion
|
||||
- Automatic multithreading for arrays > 128K elements
|
||||
- Thread-safe with atomic counters
|
||||
|
||||
### Key Data Structures
|
||||
- `int_lex_span`: Zero-copy span representing an integer token
|
||||
- `Lexer`: Stateful lexer with position tracking and error reporting
|
||||
- Thread pool with work stealing for parsing phase
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- Optimized for large integer arrays (millions of elements)
|
||||
- Multithreading kicks in automatically for arrays > 131,072 elements
|
||||
- Uses `std::from_chars` which is typically faster than `std::stoi` or `atoi`
|
||||
- Memory usage scales linearly with input size
|
||||
|
||||
## TODO
|
||||
|
||||
- Add support for different integer types (int32, uint64, etc.)
|
||||
- Implement vector parsing (e.g., `[(1,2), (3,4)]`)
|
||||
- Add SIMD optimizations for lexing phase
|
||||
- Support for hexadecimal and binary integer formats
|
||||
421
sandbox/parse_int/parse_int.cc
Normal file
421
sandbox/parse_int/parse_int.cc
Normal file
@@ -0,0 +1,421 @@
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <random>
|
||||
#include <charconv>
|
||||
|
||||
#include "../../src/tiny-string.hh"
|
||||
|
||||
std::string gen_intarray(size_t n, bool delim_at_end) {
|
||||
std::stringstream ss;
|
||||
std::random_device rd;
|
||||
std::mt19937 engine(rd());
|
||||
std::uniform_int_distribution<int64_t> dist(-1000000, 1000000);
|
||||
|
||||
ss << "[";
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
int64_t val = dist(engine);
|
||||
ss << std::to_string(val);
|
||||
if (delim_at_end) {
|
||||
ss << ",";
|
||||
} else if (i < (n-1)) {
|
||||
ss << ",";
|
||||
}
|
||||
}
|
||||
ss << "]";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
struct Lexer {
|
||||
void init(const char *_p_begin, const char *_p_end, size_t row = 0, size_t column = 0) {
|
||||
p_begin = _p_begin;
|
||||
p_end = _p_end;
|
||||
curr = p_begin;
|
||||
row_ = row;
|
||||
column_ = column;
|
||||
}
|
||||
|
||||
void skip_whitespaces() {
|
||||
while (!eof()) {
|
||||
char s = *curr;
|
||||
if ((s == ' ') || (s == '\t') || (s == '\f') || (s == '\n') || (s == '\r') || (s == '\v')) {
|
||||
curr++;
|
||||
column_++;
|
||||
|
||||
if (s == '\r') {
|
||||
if (!eof()) {
|
||||
char c{'\0'};
|
||||
look_char1(&c);
|
||||
if (c == '\n') {
|
||||
curr++;
|
||||
}
|
||||
}
|
||||
row_++;
|
||||
column_ = 0;
|
||||
} else if (s == '\n') {
|
||||
row_++;
|
||||
column_ = 0;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool skip_until_delim_or_close_paren(const char delim, const char close_paren) {
|
||||
while (!eof()) {
|
||||
char s = *curr;
|
||||
if ((s == delim) || (s == close_paren)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
curr++;
|
||||
column_++;
|
||||
|
||||
if (s == '\r') {
|
||||
if (!eof()) {
|
||||
char c{'\0'};
|
||||
look_char1(&c);
|
||||
if (c == '\n') {
|
||||
curr++;
|
||||
}
|
||||
}
|
||||
row_++;
|
||||
column_ = 0;
|
||||
} else if (s == '\n') {
|
||||
row_++;
|
||||
column_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool char1(char *result) {
|
||||
if (eof()) {
|
||||
return false;
|
||||
}
|
||||
*result = *curr;
|
||||
curr++;
|
||||
column_++;
|
||||
|
||||
if ((*result == '\r') || (*result == '\n')) {
|
||||
row_++;
|
||||
column_ = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool look_char1(char *result) {
|
||||
if (eof()) {
|
||||
return false;
|
||||
}
|
||||
*result = *curr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool consume_char1() {
|
||||
if (eof()) {
|
||||
return false;
|
||||
}
|
||||
char c = *curr;
|
||||
curr++;
|
||||
|
||||
if ((c == '\r') || (c == '\n')) {
|
||||
row_++;
|
||||
column_ = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool eof() const {
|
||||
return (curr >= p_end);
|
||||
}
|
||||
|
||||
bool lex_int(uint16_t &len, bool &truncated) {
|
||||
constexpr size_t n_trunc_chars = 256;
|
||||
|
||||
size_t n = 0;
|
||||
bool has_sign = false;
|
||||
bool found_digit = false;
|
||||
|
||||
while (!eof() && (n < n_trunc_chars)) {
|
||||
char c;
|
||||
look_char1(&c);
|
||||
|
||||
if ((c == '-') || (c == '+')) {
|
||||
if (has_sign || found_digit) {
|
||||
break;
|
||||
}
|
||||
has_sign = true;
|
||||
} else if ((c >= '0') && (c <= '9')) {
|
||||
found_digit = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
consume_char1();
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n == 0 || !found_digit) {
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
truncated = (n >= n_trunc_chars);
|
||||
len = uint16_t(n);
|
||||
return true;
|
||||
}
|
||||
|
||||
void push_error(const std::string &msg) {
|
||||
err_ += msg + " (near line " + std::to_string(row_) + ", column " + std::to_string(column_) + ")\n";
|
||||
}
|
||||
|
||||
std::string get_error() const {
|
||||
return err_;
|
||||
}
|
||||
|
||||
const char *p_begin{nullptr};
|
||||
const char *p_end{nullptr};
|
||||
const char *curr{nullptr};
|
||||
size_t row_{0};
|
||||
size_t column_{0};
|
||||
|
||||
private:
|
||||
std::string err_;
|
||||
};
|
||||
|
||||
struct int_lex_span {
|
||||
const char *p_begin{nullptr};
|
||||
uint16_t length{0};
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct vec_lex_span {
|
||||
int_lex_span vspans[N];
|
||||
};
|
||||
|
||||
bool lex_int_array(
|
||||
const char *p_begin,
|
||||
const char *p_end,
|
||||
std::vector<int_lex_span> &result,
|
||||
std::string &err,
|
||||
const bool allow_delim_at_last = true,
|
||||
const char delim = ',',
|
||||
const char open_paren = '[',
|
||||
const char close_paren = ']') {
|
||||
|
||||
if (p_begin >= p_end) {
|
||||
err = "Invalid input\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
Lexer lexer;
|
||||
lexer.p_begin = p_begin;
|
||||
lexer.p_end = p_end;
|
||||
lexer.curr = p_begin;
|
||||
|
||||
char c;
|
||||
if (!lexer.char1(&c)) {
|
||||
err = "Input too short.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c != open_paren) {
|
||||
err = "Input does not begin with open parenthesis character.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
lexer.skip_whitespaces();
|
||||
|
||||
while (!lexer.eof()) {
|
||||
bool prev_is_delim = false;
|
||||
|
||||
{
|
||||
char c;
|
||||
if (!lexer.look_char1(&c)) {
|
||||
lexer.push_error("Invalid character found.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == delim) {
|
||||
if (result.empty()) {
|
||||
lexer.push_error("Array element starts with the delimiter character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
prev_is_delim = true;
|
||||
lexer.consume_char1();
|
||||
}
|
||||
|
||||
lexer.skip_whitespaces();
|
||||
}
|
||||
|
||||
{
|
||||
char c;
|
||||
if (!lexer.look_char1(&c)) {
|
||||
lexer.push_error("Failed to read a character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == close_paren) {
|
||||
if (prev_is_delim) {
|
||||
if (allow_delim_at_last) {
|
||||
return true;
|
||||
} else {
|
||||
lexer.push_error("Delimiter character is not allowed before the closing parenthesis character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int_lex_span sp;
|
||||
sp.p_begin = lexer.curr;
|
||||
|
||||
uint16_t length{0};
|
||||
bool truncated{false};
|
||||
|
||||
if (!lexer.lex_int(length, truncated)) {
|
||||
lexer.push_error("Input is not an integer literal.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
sp.length = length;
|
||||
|
||||
if (truncated) {
|
||||
if (!lexer.skip_until_delim_or_close_paren(delim, close_paren)) {
|
||||
lexer.push_error("Failed to seek to delimiter or closing parenthesis character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(sp));
|
||||
lexer.skip_whitespaces();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool do_parse(
|
||||
uint32_t nthreads,
|
||||
const std::vector<int_lex_span> &spans,
|
||||
std::vector<int64_t> &results) {
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
results.resize(spans.size());
|
||||
|
||||
if (spans.size() > (1024*128)) {
|
||||
nthreads = (std::min)((std::max)(1u, nthreads), 256u);
|
||||
|
||||
std::mutex mutex;
|
||||
std::atomic<size_t> cnt(0);
|
||||
std::atomic<bool> parse_failed{false};
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
for (uint32_t i = 0; i < nthreads; i++) {
|
||||
threads.emplace_back(std::thread([&] {
|
||||
size_t j;
|
||||
|
||||
while ((j = cnt++) < results.size()) {
|
||||
int64_t val;
|
||||
tinyusdz::tstring_view ts(spans[j].p_begin, size_t(spans[j].length));
|
||||
if (!tinyusdz::str::parse_int64(ts, &val)) {
|
||||
parse_failed = true;
|
||||
}
|
||||
|
||||
results[j] = val;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for (auto &&th : threads) {
|
||||
th.join();
|
||||
}
|
||||
|
||||
if (parse_failed) {
|
||||
std::cerr << "parsing failure\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
for (size_t i = 0; i < spans.size(); i++) {
|
||||
int64_t val;
|
||||
tinyusdz::tstring_view ts(spans[i].p_begin, size_t(spans[i].length));
|
||||
if (!tinyusdz::str::parse_int64(ts, &val)) {
|
||||
std::cerr << "parsing failure\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
results[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::cout << "n threads: " << nthreads << "\n";
|
||||
std::cout << "n elems: " << spans.size() << "\n";
|
||||
std::cout << "parse time: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " [ms]\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::vector<int_lex_span> lex_results;
|
||||
|
||||
uint32_t nthreads = 1;
|
||||
bool delim_at_end = true;
|
||||
size_t n = 1024*1024*32;
|
||||
|
||||
if (argc > 1) {
|
||||
n = std::stoi(argv[1]);
|
||||
}
|
||||
if (argc > 2) {
|
||||
delim_at_end = std::stoi(argv[2]) > 0;
|
||||
}
|
||||
if (argc > 3) {
|
||||
nthreads = std::stoi(argv[3]);
|
||||
}
|
||||
|
||||
lex_results.reserve(n);
|
||||
|
||||
std::string input = gen_intarray(n, delim_at_end);
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
std::string err;
|
||||
if (!lex_int_array(input.c_str(), input.c_str() + input.size(), lex_results, err)) {
|
||||
std::cerr << "parse error\n";
|
||||
std::cerr << err << "\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
std::cout << "n elems " << lex_results.size() << "\n";
|
||||
std::cout << "size " << input.size() << "\n";
|
||||
std::cout << "lex time: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " [ms]\n";
|
||||
|
||||
std::vector<int64_t> parse_results;
|
||||
parse_results.reserve(n);
|
||||
|
||||
do_parse(nthreads, lex_results, parse_results);
|
||||
|
||||
return 0;
|
||||
}
|
||||
4
sandbox/print_fp/Makefile
Normal file
4
sandbox/print_fp/Makefile
Normal file
@@ -0,0 +1,4 @@
|
||||
all:
|
||||
g++ -O2 -g -std=c++14 print_fp.cc -I ../../src/external/dragonbox/ ../../src/external/dragonbox/dragonbox_to_chars.cpp -I../../src/external -o print_fp
|
||||
#clang++ -O2 -g print_fp.cc -I ../../src/external/dragonbox/ ../../src/external/dragonbox/dragonbox_to_chars.cpp -I../../src/external
|
||||
#clang++ -O2 -g print_fp.cc -I ../../src/external/
|
||||
581
sandbox/print_fp/print_fp.cc
Normal file
581
sandbox/print_fp/print_fp.cc
Normal file
@@ -0,0 +1,581 @@
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "dragonbox_to_chars.h"
|
||||
#include "dtoa_milo.h"
|
||||
|
||||
using float2 = std::array<float, 2>;
|
||||
using float3 = std::array<float, 3>;
|
||||
using float4 = std::array<float, 4>;
|
||||
using double2 = std::array<double, 2>;
|
||||
using double3 = std::array<double, 3>;
|
||||
using double4 = std::array<double, 4>;
|
||||
|
||||
std::vector<float> gen_floats(size_t n) {
|
||||
std::vector<float> dst;
|
||||
dst.resize(n);
|
||||
|
||||
std::random_device rd;
|
||||
|
||||
std::mt19937 engine(rd());
|
||||
std::uniform_real_distribution<> dist(-0.1, 0.1);
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
double f = dist(engine);
|
||||
dst[i] = float(f);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// based on fmtlib
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||
// MIT license.
|
||||
//
|
||||
|
||||
namespace internal {
|
||||
|
||||
// TOOD: Use builtin_clz insturction?
|
||||
// T = uint32 or uint64
|
||||
template <typename T>
|
||||
inline int count_digits(T n) {
|
||||
int count = 1;
|
||||
for (;;) {
|
||||
// Integer division is slow so do it for a group of four digits instead
|
||||
// of for every digit. The idea comes from the talk by Alexandrescu
|
||||
// "Three Optimization Tips for C++". See speed-test for a comparison.
|
||||
if (n < 10) return count;
|
||||
if (n < 100) return count + 1;
|
||||
if (n < 1000) return count + 2;
|
||||
if (n < 10000) return count + 3;
|
||||
n /= 10000u;
|
||||
count += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts value in the range [0, 100) to a string.
|
||||
// GCC generates slightly better code when value is pointer-size.
|
||||
inline auto digits2(size_t value) -> const char* {
|
||||
// Align data since unaligned access may be slower when crossing a
|
||||
// hardware-specific boundary.
|
||||
alignas(2) static const char data[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
return &data[value * 2];
|
||||
}
|
||||
|
||||
// Writes a two-digit value to out.
|
||||
inline void write2digits(char* out, size_t value) {
|
||||
// if (!is_constant_evaluated() && std::is_same<Char, char>::value &&
|
||||
// !FMT_OPTIMIZE_SIZE) {
|
||||
// memcpy(out, digits2(value), 2);
|
||||
// return;
|
||||
// }
|
||||
*out++ = static_cast<char>('0' + value / 10);
|
||||
*out = static_cast<char>('0' + value % 10);
|
||||
}
|
||||
|
||||
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
|
||||
char* write_exponent(int exp, char* out) {
|
||||
// FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
|
||||
if (exp < 0) {
|
||||
*out++ = '-';
|
||||
exp = -exp;
|
||||
} else {
|
||||
*out++ = '+';
|
||||
}
|
||||
auto uexp = static_cast<uint32_t>(exp);
|
||||
// if (is_constant_evaluated()) {
|
||||
// if (uexp < 10) *out++ = '0';
|
||||
// return format_decimal<Char>(out, uexp, count_digits(uexp));
|
||||
// }
|
||||
if (uexp >= 100u) {
|
||||
const char* top = digits2(uexp / 100);
|
||||
if (uexp >= 1000u) *out++ = top[0];
|
||||
*out++ = static_cast<char>(top[1]);
|
||||
uexp %= 100;
|
||||
}
|
||||
const char* d = digits2(uexp);
|
||||
*out++ = static_cast<char>(d[0]);
|
||||
*out++ = static_cast<char>(d[1]);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline char* fill_n(char* p, int n, char c) {
|
||||
for (int i = 0; i < n; i++, p++) {
|
||||
*p = c;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
inline void format_decimal_impl(char* out, uint64_t value, uint32_t size) {
|
||||
// FMT_ASSERT(size >= count_digits(value), "invalid digit count");
|
||||
unsigned n = size;
|
||||
while (value >= 100) {
|
||||
// Integer division is slow so do it for a group of two digits instead
|
||||
// of for every digit. The idea comes from the talk by Alexandrescu
|
||||
// "Three Optimization Tips for C++". See speed-test for a comparison.
|
||||
n -= 2;
|
||||
write2digits(out + n, static_cast<unsigned>(value % 100));
|
||||
value /= 100;
|
||||
}
|
||||
if (value >= 10) {
|
||||
n -= 2;
|
||||
write2digits(out + n, static_cast<unsigned>(value));
|
||||
} else {
|
||||
out[--n] = static_cast<char>('0' + value);
|
||||
}
|
||||
//return out + n;
|
||||
}
|
||||
|
||||
inline char* format_decimal(char* out, uint64_t value, uint32_t num_digits) {
|
||||
format_decimal_impl(out, value, num_digits);
|
||||
return out + num_digits;
|
||||
}
|
||||
|
||||
inline char* write_significand_e(char* out, uint64_t significand,
|
||||
int significand_size, int exponent) {
|
||||
out = format_decimal(out, significand, significand_size);
|
||||
return fill_n(out, exponent, '0');
|
||||
}
|
||||
|
||||
inline char* write_significand(char* out, uint64_t significand,
|
||||
int significand_size, int integral_size,
|
||||
char decimal_point) {
|
||||
if (!decimal_point) return format_decimal(out, significand, significand_size);
|
||||
out += significand_size + 1;
|
||||
char* end = out;
|
||||
int floating_size = significand_size - integral_size;
|
||||
for (int i = floating_size / 2; i > 0; --i) {
|
||||
out -= 2;
|
||||
write2digits(out, static_cast<std::size_t>(significand % 100));
|
||||
significand /= 100;
|
||||
}
|
||||
if (floating_size % 2 != 0) {
|
||||
*--out = static_cast<char>('0' + significand % 10);
|
||||
significand /= 10;
|
||||
}
|
||||
*--out = decimal_point;
|
||||
format_decimal(out - integral_size, significand, integral_size);
|
||||
return end;
|
||||
}
|
||||
|
||||
// Use dragonbox algorithm to print floating point value.
|
||||
// Use to_deciamal and do human-readable pretty printing for some value range(e.g. print 1e-3 as 0.001)
|
||||
//
|
||||
// exp_upper: (15 + 1) for double, (6+1) for float
|
||||
char* dtoa_dragonbox(const double f, char* buf, int exp_upper = 16) {
|
||||
const int spec_precision = -1; // unlimited
|
||||
|
||||
bool is_negative = std::signbit(f);
|
||||
|
||||
auto ret = jkj::dragonbox::to_decimal(f);
|
||||
|
||||
// print human-readable float for the value in range [1e-exp_lower, 1e+exp_upper]
|
||||
const int exp_lower = -4;
|
||||
char exp_char = 'e';
|
||||
char zero_char = '0';
|
||||
|
||||
auto significand = ret.significand;
|
||||
int significand_size = count_digits(significand);
|
||||
|
||||
size_t size = size_t(significand_size) + (is_negative ? 1u : 0u);
|
||||
|
||||
int output_exp = ret.exponent + significand_size - 1;
|
||||
bool use_exp_format = (output_exp < exp_lower) || (output_exp >= exp_upper);
|
||||
|
||||
char decimal_point = '.';
|
||||
if (use_exp_format) {
|
||||
int num_zeros = 0;
|
||||
if (significand_size == 1) {
|
||||
decimal_point = '\0';
|
||||
}
|
||||
auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp;
|
||||
int exp_digits = 2;
|
||||
if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3;
|
||||
|
||||
size += (decimal_point ? 1u : 0u) + 2u + size_t(exp_digits);
|
||||
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
buf =
|
||||
write_significand(buf, significand, significand_size, 1, decimal_point);
|
||||
|
||||
if (num_zeros > 0) buf = fill_n(buf, num_zeros, zero_char);
|
||||
*buf++ = exp_char;
|
||||
return write_exponent(output_exp, buf);
|
||||
}
|
||||
|
||||
int exp = ret.exponent + significand_size;
|
||||
if (ret.exponent >= 0) {
|
||||
// 1234e5 -> 123400000[.0+]
|
||||
size += static_cast<size_t>(ret.exponent);
|
||||
int num_zeros = spec_precision - exp;
|
||||
// abort_fuzzing_if(num_zeros > 5000);
|
||||
// if (specs.alt()) {
|
||||
// ++size;
|
||||
// if (num_zeros <= 0 && specs.type() != presentation_type::fixed)
|
||||
// num_zeros = 0;
|
||||
// if (num_zeros > 0) size += size_t(num_zeros);
|
||||
// }
|
||||
// auto grouping = Grouping(loc, specs.localized());
|
||||
// size += size_t(grouping.count_separators(exp));
|
||||
// return write_padded<Char, align::right>(out, specs, size, [&](iterator
|
||||
// it) {
|
||||
// if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// it = write_significand<Char>(it, significand, significand_size,
|
||||
// f.exponent, grouping);
|
||||
// if (!specs.alt()) return it;
|
||||
// *it++ = decimal_point;
|
||||
// return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
|
||||
// });
|
||||
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
return write_significand_e(buf, significand, significand_size,
|
||||
ret.exponent);
|
||||
|
||||
} else if (exp > 0) {
|
||||
// 1234e-2 -> 12.34[0+]
|
||||
// int num_zeros = specs.alt() ? spec_precision - significand_size : 0;
|
||||
// size += 1 + static_cast<unsigned>(max_of(num_zeros, 0));
|
||||
size += 1;
|
||||
// auto grouping = Grouping(loc, specs.localized());
|
||||
// size += size_t(grouping.count_separators(exp));
|
||||
// return write_padded<Char, align::right>(out, specs, size, [&](iterator
|
||||
// it) {
|
||||
// if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// it = write_significand(it, significand, significand_size, exp,
|
||||
// decimal_point, grouping);
|
||||
// return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
|
||||
// });
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
return write_significand(buf, significand, significand_size, exp,
|
||||
decimal_point);
|
||||
}
|
||||
// 1234e-6 -> 0.001234
|
||||
int num_zeros = -exp;
|
||||
// if (significand_size == 0 && specs.precision >= 0 &&
|
||||
// specs.precision < num_zeros) {
|
||||
// num_zeros = spec_precision;
|
||||
// }
|
||||
bool pointy = num_zeros != 0 || significand_size != 0; // || specs.alt();
|
||||
size += 1u + (pointy ? 1u : 0u) + size_t(num_zeros);
|
||||
// return write_padded<Char, align::right>(out, specs, size, [&](iterator it)
|
||||
// {
|
||||
// if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// *it++ = zero;
|
||||
// if (!pointy) return it;
|
||||
// *it++ = decimal_point;
|
||||
// it = detail::fill_n(it, num_zeros, zero);
|
||||
// return write_significand<Char>(it, significand, significand_size);
|
||||
// });
|
||||
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
*buf++ = zero_char;
|
||||
|
||||
if (!pointy) return buf;
|
||||
*buf++ = decimal_point;
|
||||
buf = fill_n(buf, num_zeros, zero_char);
|
||||
|
||||
return format_decimal(buf, significand, significand_size);
|
||||
}
|
||||
|
||||
char* dtoa_dragonbox(const float f, char* buf) {
|
||||
return dtoa_dragonbox(double(f), buf, 7);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// -------------------------------------------------------------
|
||||
|
||||
std::string print_floats(const std::vector<float> &v) {
|
||||
|
||||
char buffer[40]; // 25 should be enough
|
||||
|
||||
size_t n = v.size();
|
||||
std::vector<char> dst;
|
||||
dst.reserve(n * 10); // 10 : heuristics.
|
||||
|
||||
size_t curr = 0;
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
|
||||
if (i > 0) {
|
||||
dst[curr] = ',';
|
||||
dst[curr+1] = ' ';
|
||||
curr += 2;
|
||||
}
|
||||
|
||||
char *e = internal::dtoa_dragonbox(v[i], buffer);
|
||||
size_t len = e - buffer; // includes '\0'
|
||||
|
||||
// +2 for ', '
|
||||
if ((curr + len + 2) >= dst.size()) {
|
||||
dst.resize((curr + len) + 2);
|
||||
}
|
||||
|
||||
memcpy(dst.data() + curr, buffer, len);
|
||||
|
||||
curr += len;
|
||||
}
|
||||
dst[curr] = '\n';
|
||||
std::string s(dst.data(), curr);
|
||||
return s;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
std::string print_float_array(const std::vector<std::array<float, N>> &v) {
|
||||
std::ostringstream oss;
|
||||
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
if (i > 0) {
|
||||
oss << ", ";
|
||||
}
|
||||
|
||||
oss << "(";
|
||||
|
||||
for (size_t j = 0; j < N; j++) {
|
||||
if (j > 0) {
|
||||
oss << ", ";
|
||||
}
|
||||
|
||||
char buffer[40];
|
||||
// Handle special cases to avoid dragonbox assertion
|
||||
if (!std::isfinite(v[i][j]) || v[i][j] == 0.0f) {
|
||||
if (v[i][j] == 0.0f) {
|
||||
oss << "0";
|
||||
} else if (std::isnan(v[i][j])) {
|
||||
oss << "nan";
|
||||
} else if (std::isinf(v[i][j])) {
|
||||
oss << (v[i][j] > 0 ? "inf" : "-inf");
|
||||
}
|
||||
} else {
|
||||
char *e = internal::dtoa_dragonbox(v[i][j], buffer);
|
||||
*e = '\0';
|
||||
oss << buffer;
|
||||
}
|
||||
}
|
||||
|
||||
oss << ")";
|
||||
}
|
||||
|
||||
oss << "\n";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
std::string print_double_array(const std::vector<std::array<double, N>> &v) {
|
||||
std::ostringstream oss;
|
||||
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
if (i > 0) {
|
||||
oss << ", ";
|
||||
}
|
||||
|
||||
oss << "(";
|
||||
|
||||
for (size_t j = 0; j < N; j++) {
|
||||
if (j > 0) {
|
||||
oss << ", ";
|
||||
}
|
||||
|
||||
char buffer[40];
|
||||
// Handle special cases to avoid dragonbox assertion
|
||||
if (!std::isfinite(v[i][j]) || v[i][j] == 0.0) {
|
||||
if (v[i][j] == 0.0) {
|
||||
oss << "0";
|
||||
} else if (std::isnan(v[i][j])) {
|
||||
oss << "nan";
|
||||
} else if (std::isinf(v[i][j])) {
|
||||
oss << (v[i][j] > 0 ? "inf" : "-inf");
|
||||
}
|
||||
} else {
|
||||
char *e = internal::dtoa_dragonbox(v[i][j], buffer);
|
||||
*e = '\0';
|
||||
oss << buffer;
|
||||
}
|
||||
}
|
||||
|
||||
oss << ")";
|
||||
}
|
||||
|
||||
oss << "\n";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string print_float2_array(const std::vector<float2> &v) {
|
||||
return print_float_array<2>(v);
|
||||
}
|
||||
|
||||
std::string print_float3_array(const std::vector<float3> &v) {
|
||||
return print_float_array<3>(v);
|
||||
}
|
||||
|
||||
std::string print_float4_array(const std::vector<float4> &v) {
|
||||
return print_float_array<4>(v);
|
||||
}
|
||||
|
||||
std::string print_double2_array(const std::vector<double2> &v) {
|
||||
return print_double_array<2>(v);
|
||||
}
|
||||
|
||||
std::string print_double3_array(const std::vector<double3> &v) {
|
||||
return print_double_array<3>(v);
|
||||
}
|
||||
|
||||
std::string print_double4_array(const std::vector<double4> &v) {
|
||||
return print_double_array<4>(v);
|
||||
}
|
||||
|
||||
#if 0
|
||||
std::string print_floats(const std::vector<float> &v) {
|
||||
|
||||
char buffer[25];
|
||||
|
||||
size_t n = v.size();
|
||||
std::vector<char> dst;
|
||||
dst.reserve(n * 10); // 10 : heuristics.
|
||||
|
||||
size_t curr = 0;
|
||||
for (size_t i = 0; i < v.size(); i++) {
|
||||
|
||||
if (i > 0) {
|
||||
dst[curr] = ',';
|
||||
dst[curr+1] = ' ';
|
||||
curr += 2;
|
||||
}
|
||||
|
||||
//char *e = dtoa_milo(v[i], buffer);
|
||||
//size_t len = e - buffer; // includes position of '\0'
|
||||
|
||||
// +2 for ', '
|
||||
//if ((curr + len + 2) >= dst.size()) {
|
||||
// dst.resize((curr + len) + 2);
|
||||
//}
|
||||
|
||||
//memcpy(dst.data() + curr, buffer, len);
|
||||
|
||||
curr += len;
|
||||
}
|
||||
dst[curr] = '\n';
|
||||
std::string s(dst.data(), curr);
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
bool delim_at_end = true;
|
||||
size_t n = 1024 * 1024 * 16;
|
||||
if (argc > 1) {
|
||||
n = std::stoi(argv[1]);
|
||||
}
|
||||
if (argc > 2) {
|
||||
delim_at_end = std::stoi(argv[2]) > 0;
|
||||
}
|
||||
|
||||
// Skip original dragonbox test loop - has issues
|
||||
// double d = 1.0;
|
||||
// for (size_t i = 0; i < 32; i++) {
|
||||
// char buf[25];
|
||||
// char *p = internal::dtoa_dragonbox(d, buf);
|
||||
// *p = '\0';
|
||||
// std::cout << "db " << buf << "\n";
|
||||
// {
|
||||
// auto ret = jkj::dragonbox::to_decimal(d);
|
||||
// std::cout << "to_decimal " << ret.significand << "\n";
|
||||
// std::cout << "to_decimal " << ret.exponent << "\n";
|
||||
// }
|
||||
// {
|
||||
// char db_buf[40];
|
||||
// auto ret = jkj::dragonbox::to_chars(d, db_buf);
|
||||
// std::cout << "to_chars " << db_buf << "\n";
|
||||
// }
|
||||
// {
|
||||
// char buffer[25];
|
||||
// int length, K;
|
||||
// Grisu2(d, buffer, &length, &K);
|
||||
// std::cout << "grisu len " << length << "\n";
|
||||
// std::cout << "grisu K " << K << "\n";
|
||||
// std::cout << "grisu " << buffer << "\n";
|
||||
// }
|
||||
// d = d * 10.0;
|
||||
// }
|
||||
|
||||
// Skip the performance test for now - has issues with dragonbox assertion
|
||||
// std::vector<float> arr = gen_floats(n);
|
||||
// auto start = std::chrono::steady_clock::now();
|
||||
// std::string s = print_floats(arr);
|
||||
// auto end = std::chrono::steady_clock::now();
|
||||
// std::cout << "n elems " << arr.size() << "\n";
|
||||
// std::cout << "print : " <<
|
||||
// std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
|
||||
// << " [ms]\n";
|
||||
|
||||
// Test vector array printers
|
||||
std::cout << "\n=== Testing vector array printers ===\n";
|
||||
|
||||
// Test float2 arrays
|
||||
std::vector<float2> float2_test = {
|
||||
{1.0f, 2.0f},
|
||||
{3.14159f, -2.71828f},
|
||||
{0.0001f, 1000000.0f}
|
||||
};
|
||||
std::cout << "float2 array: " << print_float2_array(float2_test);
|
||||
|
||||
// Test float3 arrays
|
||||
std::vector<float3> float3_test = {
|
||||
{1.0f, 2.0f, 3.0f},
|
||||
{0.577f, 0.577f, 0.577f},
|
||||
{-1.0f, 0.0f, 1.0f}
|
||||
};
|
||||
std::cout << "float3 array: " << print_float3_array(float3_test);
|
||||
|
||||
// Test float4 arrays
|
||||
std::vector<float4> float4_test = {
|
||||
{1.0f, 0.0f, 0.0f, 1.0f},
|
||||
{0.5f, 0.5f, 0.5f, 0.8f}
|
||||
};
|
||||
std::cout << "float4 array: " << print_float4_array(float4_test);
|
||||
|
||||
// Test double2 arrays
|
||||
std::vector<double2> double2_test = {
|
||||
{1.0, 2.0},
|
||||
{3.14, -2.71}
|
||||
};
|
||||
std::cout << "double2 array: " << print_double2_array(double2_test);
|
||||
|
||||
// Test double3 arrays
|
||||
std::vector<double3> double3_test = {
|
||||
{1.0, 2.0, 3.0},
|
||||
{0.577, 0.577, 0.577}
|
||||
};
|
||||
std::cout << "double3 array: " << print_double3_array(double3_test);
|
||||
|
||||
// Test double4 arrays
|
||||
std::vector<double4> double4_test = {
|
||||
{1.0, 2.0, 3.0, 4.0},
|
||||
{0.707, 0.707, 1.0, 2.0}
|
||||
};
|
||||
std::cout << "double4 array: " << print_double4_array(double4_test);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -48,6 +48,21 @@
|
||||
// external
|
||||
|
||||
#include "external/fast_float/include/fast_float/fast_float.h"
|
||||
|
||||
#define CHECK_MEMORY_USAGE(__nbytes) do { \
|
||||
_memory_usage += (__nbytes); \
|
||||
if (_memory_usage > _max_memory_limit_bytes) { \
|
||||
PushError(fmt::format("Memory limit exceeded. Limit: {} MB, Current usage: {} MB", \
|
||||
_max_memory_limit_bytes / (1024*1024), _memory_usage / (1024*1024))); \
|
||||
return false; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define REDUCE_MEMORY_USAGE(__nbytes) do { \
|
||||
if (_memory_usage >= (__nbytes)) { \
|
||||
_memory_usage -= (__nbytes); \
|
||||
} \
|
||||
} while(0)
|
||||
#include "external/jsteemann/atoi.h"
|
||||
//#include "external/simple_match/include/simple_match/simple_match.hpp"
|
||||
#include "nonstd/expected.hpp"
|
||||
@@ -75,6 +90,7 @@
|
||||
#include "value-types.hh"
|
||||
|
||||
#include "common-macros.inc"
|
||||
#include "tiny-string.hh"
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
@@ -1549,6 +1565,7 @@ bool AsciiParser::SepBy1BasicType(const char sep,
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1578,6 +1595,7 @@ bool AsciiParser::SepBy1BasicType(const char sep,
|
||||
break;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1608,6 +1626,7 @@ bool AsciiParser::SepBy1BasicType(const char sep, std::vector<T> *result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1637,6 +1656,7 @@ bool AsciiParser::SepBy1BasicType(const char sep, std::vector<T> *result) {
|
||||
break;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1668,6 +1688,7 @@ bool AsciiParser::SepBy1BasicType(const char sep, const char end_symbol, std::ve
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1714,6 +1735,7 @@ bool AsciiParser::SepBy1BasicType(const char sep, const char end_symbol, std::ve
|
||||
break;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
|
||||
|
||||
@@ -1749,6 +1771,7 @@ bool AsciiParser::SepBy1TupleType(
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1779,7 +1802,8 @@ bool AsciiParser::SepBy1TupleType(
|
||||
if (!ParseBasicTypeTuple<T, N>(&value)) {
|
||||
break;
|
||||
}
|
||||
result->push_back(value);
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1811,6 +1835,7 @@ bool AsciiParser::SepBy1TupleType(const char sep,
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1839,6 +1864,7 @@ bool AsciiParser::SepBy1TupleType(const char sep,
|
||||
break;
|
||||
}
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(nonstd::optional<T>) + sizeof(T));
|
||||
result->push_back(value);
|
||||
}
|
||||
|
||||
@@ -1962,6 +1988,7 @@ bool AsciiParser::SepBy1BasicType(const char sep,
|
||||
|
||||
(void)triple_deliminated;
|
||||
|
||||
CHECK_MEMORY_USAGE(sizeof(Reference));
|
||||
result->push_back(ref);
|
||||
}
|
||||
|
||||
@@ -2010,6 +2037,7 @@ bool AsciiParser::SepBy1BasicType(const char sep,
|
||||
}
|
||||
|
||||
(void)triple_deliminated;
|
||||
CHECK_MEMORY_USAGE(sizeof(Reference));
|
||||
result->push_back(ref);
|
||||
}
|
||||
|
||||
@@ -2144,6 +2172,7 @@ bool AsciiParser::ParseBasicTypeArray(std::vector<Reference> *result) {
|
||||
|
||||
(void)triple_deliminated;
|
||||
result->clear();
|
||||
CHECK_MEMORY_USAGE(sizeof(Reference));
|
||||
result->push_back(ref);
|
||||
|
||||
} else {
|
||||
@@ -3180,6 +3209,141 @@ bool AsciiParser::ReadBasicType(nonstd::optional<std::vector<T>> *value) {
|
||||
|
||||
// -- end basic
|
||||
|
||||
//
|
||||
// Optimized array parsing using tiny-string
|
||||
//
|
||||
|
||||
bool AsciiParser::ParseFloatArrayOptimized(std::vector<float> *result) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the end of the array by matching brackets
|
||||
if (!Expect('[')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bracket_depth = 1;
|
||||
std::string array_str = "[";
|
||||
|
||||
while (bracket_depth > 0) {
|
||||
char c;
|
||||
if (!Char1(&c)) {
|
||||
PushError("Unexpected end of input while parsing float array");
|
||||
return false;
|
||||
}
|
||||
|
||||
array_str += c;
|
||||
|
||||
if (c == '[') {
|
||||
bracket_depth++;
|
||||
} else if (c == ']') {
|
||||
bracket_depth--;
|
||||
}
|
||||
}
|
||||
|
||||
// Use tiny-string optimized parsing
|
||||
tstring_view sv(array_str.c_str());
|
||||
if (!str::parse_float_arary(sv, result)) {
|
||||
PushError("Failed to parse float array with tiny-string");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsciiParser::ParseDoubleArrayOptimized(std::vector<double> *result) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the end of the array by matching brackets
|
||||
if (!Expect('[')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bracket_depth = 1;
|
||||
std::string array_str = "[";
|
||||
|
||||
while (bracket_depth > 0) {
|
||||
char c;
|
||||
if (!Char1(&c)) {
|
||||
PushError("Unexpected end of input while parsing double array");
|
||||
return false;
|
||||
}
|
||||
|
||||
array_str += c;
|
||||
|
||||
if (c == '[') {
|
||||
bracket_depth++;
|
||||
} else if (c == ']') {
|
||||
bracket_depth--;
|
||||
}
|
||||
}
|
||||
|
||||
// Use tiny-string optimized parsing
|
||||
tstring_view sv(array_str.c_str());
|
||||
if (!str::parse_double_arary(sv, result)) {
|
||||
PushError("Failed to parse double array with tiny-string");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsciiParser::ParseIntArrayOptimized(std::vector<int32_t> *result) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the end of the array by matching brackets
|
||||
if (!Expect('[')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bracket_depth = 1;
|
||||
std::string array_str = "[";
|
||||
|
||||
while (bracket_depth > 0) {
|
||||
char c;
|
||||
if (!Char1(&c)) {
|
||||
PushError("Unexpected end of input while parsing int array");
|
||||
return false;
|
||||
}
|
||||
|
||||
array_str += c;
|
||||
|
||||
if (c == '[') {
|
||||
bracket_depth++;
|
||||
} else if (c == ']') {
|
||||
bracket_depth--;
|
||||
}
|
||||
}
|
||||
|
||||
// Use tiny-string optimized parsing
|
||||
tstring_view sv(array_str.c_str());
|
||||
if (!str::parse_int_arary(sv, result)) {
|
||||
PushError("Failed to parse int array with tiny-string");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Template specializations for optimized parsing
|
||||
//
|
||||
|
||||
template <>
|
||||
bool AsciiParser::ParseBasicTypeArray(std::vector<float> *result) {
|
||||
return ParseFloatArrayOptimized(result);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool AsciiParser::ParseBasicTypeArray(std::vector<double> *result) {
|
||||
return ParseDoubleArrayOptimized(result);
|
||||
}
|
||||
|
||||
//
|
||||
// Explicit template instanciations
|
||||
//
|
||||
@@ -3250,11 +3414,12 @@ template bool AsciiParser::ParseBasicTypeArray(std::vector<value::half> *result)
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::half2> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::half3> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::half4> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<float> *result);
|
||||
// Note: float and double arrays now use optimized implementations
|
||||
// template bool AsciiParser::ParseBasicTypeArray(std::vector<float> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::float2> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::float3> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::float4> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<double> *result);
|
||||
// template bool AsciiParser::ParseBasicTypeArray(std::vector<double> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::double2> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::double3> *result);
|
||||
template bool AsciiParser::ParseBasicTypeArray(std::vector<value::double4> *result);
|
||||
|
||||
@@ -58,6 +58,23 @@
|
||||
//
|
||||
|
||||
#include "common-macros.inc"
|
||||
|
||||
#define CHECK_MEMORY_USAGE(__nbytes) do { \
|
||||
_memory_usage += (__nbytes); \
|
||||
if (_memory_usage > _max_memory_limit_bytes) { \
|
||||
PushError(fmt::format("Memory limit exceeded. Limit: {} MB, Current usage: {} MB", \
|
||||
_max_memory_limit_bytes / (1024*1024), _memory_usage / (1024*1024))); \
|
||||
return false; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#if 0
|
||||
#define REDUCE_MEMORY_USAGE(__nbytes) do { \
|
||||
if (_memory_usage >= (__nbytes)) { \
|
||||
_memory_usage -= (__nbytes); \
|
||||
} \
|
||||
} while(0)
|
||||
#endif
|
||||
#include "io-util.hh"
|
||||
#include "pprinter.hh"
|
||||
#include "prim-types.hh"
|
||||
@@ -650,15 +667,42 @@ std::string AsciiParser::GetError() {
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
// Track unique error messages to avoid duplicates
|
||||
std::set<std::string> seen_errors;
|
||||
std::vector<ErrorDiagnostic> errors;
|
||||
|
||||
// Collect all errors
|
||||
while (!err_stack.empty()) {
|
||||
ErrorDiagnostic diag = err_stack.top();
|
||||
|
||||
ss << "err_stack[" << (err_stack.size() - 1) << "] USDA source near line "
|
||||
<< (diag.cursor.row + 1) << ", col " << (diag.cursor.col + 1) << ": ";
|
||||
ss << diag.err; // assume message contains newline.
|
||||
|
||||
errors.push_back(err_stack.top());
|
||||
err_stack.pop();
|
||||
}
|
||||
|
||||
// Process errors in reverse order (oldest first)
|
||||
for (auto it = errors.rbegin(); it != errors.rend(); ++it) {
|
||||
const ErrorDiagnostic& diag = *it;
|
||||
|
||||
// Create a unique key for this error location and message
|
||||
std::stringstream error_key;
|
||||
error_key << diag.cursor.row << ":" << diag.cursor.col << ":" << diag.err;
|
||||
|
||||
// Skip duplicate errors
|
||||
if (seen_errors.count(error_key.str()) > 0) {
|
||||
continue;
|
||||
}
|
||||
seen_errors.insert(error_key.str());
|
||||
|
||||
// Format error with precise location
|
||||
ss << "USDA error at line " << (diag.cursor.row + 1)
|
||||
<< ", column " << (diag.cursor.col + 1) << ": ";
|
||||
|
||||
// Remove redundant newlines from error message
|
||||
std::string clean_err = diag.err;
|
||||
if (!clean_err.empty() && clean_err.back() == '\n') {
|
||||
clean_err.pop_back();
|
||||
}
|
||||
ss << clean_err << "\n";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
@@ -669,15 +713,42 @@ std::string AsciiParser::GetWarning() {
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
// Track unique warning messages to avoid duplicates
|
||||
std::set<std::string> seen_warnings;
|
||||
std::vector<ErrorDiagnostic> warnings;
|
||||
|
||||
// Collect all warnings
|
||||
while (!warn_stack.empty()) {
|
||||
ErrorDiagnostic diag = warn_stack.top();
|
||||
|
||||
ss << "USDA source near line " << (diag.cursor.row + 1) << ", col "
|
||||
<< (diag.cursor.col + 1) << ": ";
|
||||
ss << diag.err; // assume message contains newline.
|
||||
|
||||
warnings.push_back(warn_stack.top());
|
||||
warn_stack.pop();
|
||||
}
|
||||
|
||||
// Process warnings in reverse order (oldest first)
|
||||
for (auto it = warnings.rbegin(); it != warnings.rend(); ++it) {
|
||||
const ErrorDiagnostic& diag = *it;
|
||||
|
||||
// Create a unique key for this warning location and message
|
||||
std::stringstream warning_key;
|
||||
warning_key << diag.cursor.row << ":" << diag.cursor.col << ":" << diag.err;
|
||||
|
||||
// Skip duplicate warnings
|
||||
if (seen_warnings.count(warning_key.str()) > 0) {
|
||||
continue;
|
||||
}
|
||||
seen_warnings.insert(warning_key.str());
|
||||
|
||||
// Format warning with precise location
|
||||
ss << "USDA warning at line " << (diag.cursor.row + 1)
|
||||
<< ", column " << (diag.cursor.col + 1) << ": ";
|
||||
|
||||
// Remove redundant newlines from warning message
|
||||
std::string clean_warn = diag.err;
|
||||
if (!clean_warn.empty() && clean_warn.back() == '\n') {
|
||||
clean_warn.pop_back();
|
||||
}
|
||||
ss << clean_warn << "\n";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
@@ -1838,6 +1909,7 @@ bool AsciiParser::ParseStageMetaOpt() {
|
||||
if (var.get_value(&paths)) {
|
||||
DCOUT("subLayers = " << paths);
|
||||
for (const auto &item : paths) {
|
||||
CHECK_MEMORY_USAGE(sizeof(value::AssetPath) + item.GetAssetPath().length());
|
||||
_stage_metas.subLayers.push_back(item);
|
||||
}
|
||||
} else {
|
||||
@@ -3266,6 +3338,7 @@ bool AsciiParser::ParseAttrMeta(AttrMeta *out_meta) {
|
||||
{
|
||||
value::StringData sdata;
|
||||
if (MaybeTripleQuotedString(&sdata)) {
|
||||
CHECK_MEMORY_USAGE(sizeof(value::StringData) + sdata.value.length());
|
||||
out_meta->stringData.push_back(sdata);
|
||||
|
||||
DCOUT("Add triple-quoted string to attr meta:" << to_string(sdata));
|
||||
@@ -3274,6 +3347,7 @@ bool AsciiParser::ParseAttrMeta(AttrMeta *out_meta) {
|
||||
}
|
||||
continue;
|
||||
} else if (MaybeString(&sdata)) {
|
||||
CHECK_MEMORY_USAGE(sizeof(value::StringData) + sdata.value.length());
|
||||
out_meta->stringData.push_back(sdata);
|
||||
|
||||
DCOUT("Add string to attr meta:" << to_string(sdata));
|
||||
@@ -4756,6 +4830,7 @@ bool AsciiParser::ParseVariantSet(
|
||||
DCOUT(fmt::format("Done parse `{}` block.", to_string(child_spec)));
|
||||
|
||||
DCOUT(fmt::format("Add primIdx {} to variant {}", idx, variantName));
|
||||
CHECK_MEMORY_USAGE(sizeof(int64_t));
|
||||
variantContent.primIndices.push_back(idx);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -327,6 +327,13 @@ class AsciiParser {
|
||||
///
|
||||
void SetStream(tinyusdz::StreamReader *sr);
|
||||
|
||||
///
|
||||
/// Set memory limit in MB
|
||||
///
|
||||
void SetMaxMemoryLimit(size_t limit_mb) {
|
||||
_max_memory_limit_bytes = limit_mb * 1024ull * 1024ull;
|
||||
}
|
||||
|
||||
///
|
||||
/// Check if header data is USDA
|
||||
///
|
||||
@@ -576,6 +583,13 @@ class AsciiParser {
|
||||
template <typename T>
|
||||
bool ParseBasicTypeArray(std::vector<T> *result);
|
||||
|
||||
///
|
||||
/// Optimized float array parsing using tiny-string
|
||||
///
|
||||
bool ParseFloatArrayOptimized(std::vector<float> *result);
|
||||
bool ParseDoubleArrayOptimized(std::vector<double> *result);
|
||||
bool ParseIntArrayOptimized(std::vector<int32_t> *result);
|
||||
|
||||
///
|
||||
/// Parses 1 or more occurences of value with basic type 'T', separated by
|
||||
/// `sep`
|
||||
@@ -884,6 +898,10 @@ class AsciiParser {
|
||||
|
||||
StageMetas _stage_metas;
|
||||
|
||||
// Memory tracking
|
||||
uint64_t _max_memory_limit_bytes{128ull * 1024ull * 1024ull * 1024ull}; // Default 128GB
|
||||
uint64_t _memory_usage{0};
|
||||
|
||||
//
|
||||
// Callbacks
|
||||
//
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
#define PUSH_ERROR_AND_RETURN(s) \
|
||||
do { \
|
||||
std::ostringstream ss_e; \
|
||||
ss_e << "[error]" \
|
||||
<< ":" << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_e << s << "\n"; \
|
||||
ss_e << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_e << s; \
|
||||
PushError(ss_e.str()); \
|
||||
return false; \
|
||||
} while (0)
|
||||
@@ -22,8 +21,8 @@
|
||||
#define PUSH_ERROR_AND_RETURN_TAG(tag, s) \
|
||||
do { \
|
||||
std::ostringstream ss_e; \
|
||||
ss_e << "[error]" << tag << ":" << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_e << s << "\n"; \
|
||||
ss_e << tag << ":" << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_e << s; \
|
||||
PushError(ss_e.str()); \
|
||||
return false; \
|
||||
} while (0)
|
||||
@@ -31,18 +30,16 @@
|
||||
#define PUSH_ERROR(s) \
|
||||
do { \
|
||||
std::ostringstream ss_e; \
|
||||
ss_e << "[error]" \
|
||||
<< ":" << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_e << s << "\n"; \
|
||||
ss_e << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_e << s; \
|
||||
PushError(ss_e.str()); \
|
||||
} while (0)
|
||||
|
||||
#define PUSH_WARN(s) \
|
||||
do { \
|
||||
std::ostringstream ss_w; \
|
||||
ss_w << "[warn]" \
|
||||
<< ":" << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_w << s << "\n"; \
|
||||
ss_w << __func__ << "():" << __LINE__ << " "; \
|
||||
ss_w << s; \
|
||||
PushWarn(ss_w.str()); \
|
||||
} while (0)
|
||||
|
||||
@@ -51,9 +48,9 @@
|
||||
#define PUSH_ERROR_AND_RETURN(s) \
|
||||
do { \
|
||||
std::ostringstream ss_e; \
|
||||
ss_e << "[error]" << __FILE__ << ":" << __func__ << "():" << __LINE__ \
|
||||
ss_e << __FILE__ << ":" << __func__ << "():" << __LINE__ \
|
||||
<< " "; \
|
||||
ss_e << s << "\n"; \
|
||||
ss_e << s; \
|
||||
PushError(ss_e.str()); \
|
||||
return false; \
|
||||
} while (0)
|
||||
@@ -61,9 +58,9 @@
|
||||
#define PUSH_ERROR_AND_RETURN_TAG(tag, s) \
|
||||
do { \
|
||||
std::ostringstream ss_e; \
|
||||
ss_e << "[error]" << __FILE__ << tag << ":" << __func__ \
|
||||
ss_e << __FILE__ << tag << ":" << __func__ \
|
||||
<< "():" << __LINE__ << " "; \
|
||||
ss_e << s << "\n"; \
|
||||
ss_e << s; \
|
||||
PushError(ss_e.str()); \
|
||||
return false; \
|
||||
} while (0)
|
||||
@@ -71,18 +68,18 @@
|
||||
#define PUSH_ERROR(s) \
|
||||
do { \
|
||||
std::ostringstream ss_e; \
|
||||
ss_e << "[error]" << __FILE__ << ":" << __func__ << "():" << __LINE__ \
|
||||
ss_e << __FILE__ << ":" << __func__ << "():" << __LINE__ \
|
||||
<< " "; \
|
||||
ss_e << s << "\n"; \
|
||||
ss_e << s; \
|
||||
PushError(ss_e.str()); \
|
||||
} while (0)
|
||||
|
||||
#define PUSH_WARN(s) \
|
||||
do { \
|
||||
std::ostringstream ss_w; \
|
||||
ss_w << "[warn]" << __FILE__ << ":" << __func__ << "():" << __LINE__ \
|
||||
ss_w << __FILE__ << ":" << __func__ << "():" << __LINE__ \
|
||||
<< " "; \
|
||||
ss_w << s << "\n"; \
|
||||
ss_w << s; \
|
||||
PushWarn(ss_w.str()); \
|
||||
} while (0)
|
||||
|
||||
|
||||
@@ -5603,6 +5603,7 @@ bool CrateReader::ReadTokens() {
|
||||
}
|
||||
|
||||
value::token tok(str);
|
||||
CHECK_MEMORY_USAGE(sizeof(value::token) + str.size());
|
||||
|
||||
DCOUT("token[" << i << "] = " << tok);
|
||||
_tokens.push_back(tok);
|
||||
|
||||
218
src/external/dragonbox/LICENSE-Apache2-LLVM
vendored
Normal file
218
src/external/dragonbox/LICENSE-Apache2-LLVM
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
23
src/external/dragonbox/LICENSE-Boost
vendored
Normal file
23
src/external/dragonbox/LICENSE-Boost
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
263
src/external/dragonbox/README.md
vendored
Normal file
263
src/external/dragonbox/README.md
vendored
Normal file
@@ -0,0 +1,263 @@
|
||||
# Dragonbox
|
||||
This library is a reference implementation of [Dragonbox](other_files/Dragonbox.pdf) in C++.
|
||||
|
||||
Dragonbox is a float-to-string conversion algorithm based on a beautiful algorithm [Schubfach](https://drive.google.com/file/d/1IEeATSVnEE6TkrHlCYNY2GjaraBjOT4f/edit), developed by Raffaello Giulietti in 2017-2018. Dragonbox is further inspired by [Grisu](https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf) and [Grisu-Exact](https://github.com/jk-jeon/Grisu-Exact).
|
||||
|
||||
# Introduction
|
||||
Dragonbox generates a pair of integers from a floating-point number: the decimal significand and the decimal exponent of the input floating-point number. These integers can then be used for string generation of decimal representation of the input floating-point number, the procedure commonly called ````ftoa```` or ````dtoa````.
|
||||
|
||||
The algorithm guarantees three things:
|
||||
|
||||
1) It has the roundtrip guarantee; that is, a correct parser interprets the generated output string as the original input floating-point number. (See [here](https://github.com/jk-jeon/dragonbox/blob/master/README.md#precise-meaning-of-roundtrip-gurantee) for some explanation on this.)
|
||||
|
||||
2) The output is of the shortest length; that is, no other output strings that are interpreted as the input number can contain less number of significand digits than the output of Dragonbox.
|
||||
|
||||
3) The output is correctly rounded: the number generated by Dragonbox is the closest to the actual value of the input number among possible outputs of minimum number of digits.
|
||||
|
||||
# About the Name "Dragonbox"
|
||||
The core idea of Schubfach, which Dragonbox is based on, is a continuous analogue of discrete [pigeonhole principle](https://en.wikipedia.org/wiki/Pigeonhole_principle). The name *Schubfach* is coming from the German name of the pigeonhole principle, *Schubfachprinzip*, meaning "drawer principle". Since another name of the pigeonhole principle is *Dirichlet's box principle*, I decided to call my algorithm "Dragonbox" to honor its origins: Schubfach (box) and Grisu (dragon).
|
||||
|
||||
# How to Use
|
||||
Although Dragonbox is intended for float-to-string conversion routines, the actual string generation is not officially a part of the algorithm. Dragonbox just outputs two integers (the decimal significand/exponent) that can be consumed by a string generation procedure. The header file [`include/dragonbox/dragonbox.h`](include/dragonbox/dragonbox.h) includes everything needed for this (it is header-only). Nevertheless, a string generation procedure is included in the library. There are two additional files needed for that: [`include/dragonbox/dragonbox_to_chars.h`](include/dragonbox/dragonbox_to_chars.h) and [`source/dragonbox_to_chars.cpp`](source/dragonbox_to_chars.cpp). Since there are only three files, it should be not difficult to set up this library manually if you want, but you can also use it via CMake as explained below. If you are not familiar with CMake, I recommend you to have a look at [this](https://cliutils.gitlab.io/modern-cmake/) wonderful introduction.
|
||||
|
||||
## Installing Dragonbox
|
||||
The following will create platform-specific build files on your directory:
|
||||
```
|
||||
git clone https://github.com/jk-jeon/dragonbox
|
||||
cd dragonbox
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
```
|
||||
If you only want [`dragonbox.h`](include/dragonbox/dragonbox.h) but not [`dragonbox_to_chars.h`](include/dragonbox/dragonbox_to_chars.h)/[`.cpp`](source/dragonbox_to_chars.cpp), you can do the following to install [`dragonbox.h`](include/dragonbox/dragonbox.h) into your system:
|
||||
```
|
||||
cmake .. -DDRAGONBOX_INSTALL_TO_CHARS=OFF
|
||||
cmake --install .
|
||||
```
|
||||
If you want the string generation part as well, build the generated files using platform-specific build tools (`make` or Visual Studio for example) and then perform
|
||||
```
|
||||
cmake --install .
|
||||
```
|
||||
on the `build` directory.
|
||||
|
||||
## Including Dragonbox into CMake project
|
||||
The easiest way to include Dragonbox in a CMake project is to do the following:
|
||||
```cmake
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
dragonbox
|
||||
GIT_REPOSITORY https://github.com/jk-jeon/dragonbox
|
||||
)
|
||||
FetchContent_MakeAvailable(dragonbox)
|
||||
target_link_libraries(my_target dragonbox::dragonbox) # or dragonbox::dragonbox_to_chars
|
||||
```
|
||||
Or, if you already have installed Dragonbox in your system, you can include it with:
|
||||
```cmake
|
||||
find_package(dragonbox)
|
||||
target_link_libraries(my_target dragonbox::dragonbox) # or dragonbox::dragonbox_to_chars
|
||||
```
|
||||
|
||||
# Language Standard
|
||||
The library requires C++11 or higher. Since C++20, every function provided is `constexpr`.
|
||||
|
||||
# Usage Examples
|
||||
(Simple string generation from `float/double`)
|
||||
```cpp
|
||||
#include "dragonbox/dragonbox_to_chars.h"
|
||||
constexpr int buffer_length = 1 + // for '\0'
|
||||
jkj::dragonbox::max_output_string_length<jkj::dragonbox::ieee754_binary64>;
|
||||
double x = 1.234; // Also works for float
|
||||
char buffer[buffer_length];
|
||||
|
||||
// Null-terminate the buffer and return the pointer to the null character
|
||||
// Hence, the length of the string is (end_ptr - buffer)
|
||||
// buffer is now { '1', '.', '2', '3', '4', 'E', '0', '\0', (garbages) }
|
||||
char* end_ptr = jkj::dragonbox::to_chars(x, buffer);
|
||||
|
||||
// Does not null-terminate the buffer; returns the next-to-end pointer
|
||||
// buffer is now { '1', '.', '2', '3', '4', 'E', '0', (garbages) }
|
||||
// you can wrap the buffer with things like std::string_view
|
||||
end_ptr = jkj::dragonbox::to_chars_n(x, buffer);
|
||||
```
|
||||
|
||||
(Direct use of `jkj::dragonbox::to_decimal`)
|
||||
```cpp
|
||||
#include "dragonbox/dragonbox.h"
|
||||
double x = 1.234; // Also works for float
|
||||
|
||||
// Here, x should be a nonzero finite number!
|
||||
// The return value v is a struct with three members:
|
||||
// significand : decimal significand (1234 in this case);
|
||||
// it is of type std::uint64_t for double, std::uint32_t for float
|
||||
// exponent : decimal exponent (-3 in this case); it is of type int
|
||||
// is_negative : as the name suggests; it is of type bool
|
||||
auto v = jkj::dragonbox::to_decimal(x);
|
||||
```
|
||||
|
||||
By default, `jkj::dragonbox::to_decimal` returns a struct with three members (`significand`, `exponent`, and `is_negative`). But the return type and the return value can change if you specify policy parameters. See [below](https://github.com/jk-jeon/dragonbox#policies).
|
||||
|
||||
***Important.*** `jkj::dragonbox::to_decimal` is designed to ***work only with finite nonzero*** inputs. The behavior of it when given with infinities/NaN's/`+0`/`-0` is undefined. `jkj::dragonbox::to_chars` and `jkj::dragonbox::to_chars_n` work fine for any inputs.
|
||||
|
||||
# To people wanting to port the algorithm
|
||||
Those who want to port the algorithm into other languages or re-implement it from scratch are recommended to look at the [simpler implementation](https://github.com/jk-jeon/dragonbox/tree/master/subproject/simple) first rather than the main implementation, since the main implementation is riddled with template indirections obscuring the core logic of the algorithm. The simpler implementation offers less flexibility and somewhat slower performance, but is much more straightforward so it should be easier to understand.
|
||||
|
||||
# Policies
|
||||
Dragonbox provides several policies that the user can select. Most of the time the default policies will be sufficient, but for some situation this customizability might be useful. There are currently five different kinds of policies that you can specify: sign policy, trailing zero policy, decimal-to-binary (parsing) rounding policy, binary-to-decimal (formatting) rounding policy, and cache policy. Those policies live in the namespace `jkj::dragonbox::policy`. You can provide the policies as additional parameters to `jkj::dragonbox::to_decimal` or `jkj::dragonbox::to_chars` or `jkj::dragonbox::to_chars_n`. Here is an example usage:
|
||||
```cpp
|
||||
#include "dragonbox/dragonbox.h"
|
||||
auto v = jkj::dragonbox::to_decimal(x,
|
||||
jkj::dragonbox::policy::sign::ignore,
|
||||
jkj::dragonbox::policy::cache::compact);
|
||||
```
|
||||
In this example, the `ignore` sign policy and the `compact` cache policy are specified. The return value will not include the member `is_negative`, and `jkj::dragonbox::to_decimal` will internally use the compressed cache for the computation, rather than the full cache. There is no particular order for policy parameters; you can give them in any order. Default policies will be chosen if you do not explicitly specify any. In the above example, for instance, `nearest_to_even` decimal-to-binary rounding mode policy is chosen, which is the default decimal-to-binary rounding mode policy. If you provide two or more policies of the same kind, or if you provide an invalid policy parameter, then the compliation will fail.
|
||||
|
||||
Policy parameters (e.g., `jkj::dragonbox::policy::sign::ignore` in the above example) are of different types, so different combinations of policies generally result in separate template instantiations, which might cause binary bloat. (However, it is only the combination that matters; giving the same parameter combination in a different order will usually not generate a separate binary.)
|
||||
|
||||
## Sign policy
|
||||
Determines whether or not `jkj::dragonbox::to_decimal` will extract and return the sign of the input parameter.
|
||||
|
||||
- `jkj::dragonbox::policy::sign::ignore`: There is no `is_negative` member in the returned struct and the sign of the input is not returned. A string generation routine might anyway need to deal with the sign by itself, so often this member will not be needed. In that case, omitting `is_negative` member can reduce some overhead. `jkj::dragonbox::to_chars` and `jkj::dragonbox::to_chars_n` use this policy internally. In the implementation of `jkj::dragonbox::to_decimal`, the sign of the input is relevant only for deciding the rounding interval under certain rounding mode policies. Under the default rounding mode policies, the sign is completely ignored.
|
||||
- `jkj::dragonbox::policy::sign::return_sign`: **This is the default policy.** The sign of the input will be written in the `is_negative` member of the returned struct.
|
||||
|
||||
You cannot specify sign policy to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`.
|
||||
|
||||
## Trailing zero policy
|
||||
Determines what `jkj::dragonbox::to_decimal` will do with possible trailing decimal zeros.
|
||||
|
||||
- `jkj::dragonbox::policy::trailing_zero::ignore`: Do not care about trailing zeros; the output significand may contain trailing zeros. Since trailing zero removal is a relatively heavy operation involving lots of divisions, and a string generation routine will need to perform divisions anyway, it would be possible to get a better overall performance by omitting trailing zero removal from `jkj::dragonbox::to_decimal` and taking care of that in other places.
|
||||
- `jkj::dragonbox::policy::trailing_zero::remove`: **This is the default policy.** Remove all trailing zeros in the output. `jkj::dragonbox::to_chars` and `jkj::dragonbox::to_chars_n` use this policy internally for IEEE-754 binary32 format (aka `float`).
|
||||
- `jkj::dragonbox::policy::trailing_zero::report`: The output significand may contain trailing zeros, but such possibility will be reported in the additional member `may_have_trailing_zeros` of the returned struct. This member will be set to `true` if there might be trailing zeros, and it will be set to `false` if there should be no trailing zero. By how the algorithm works, it is guaranteed that whenever there might be trailing zeros, the maximum number of trailing zeros is 7 for binary32 and 15 for binary64.
|
||||
|
||||
You cannot specify trailing zero policy to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`.
|
||||
|
||||
## Decimal-to-binary rounding policy
|
||||
Dragonbox provides a roundtrip guarantee. This means that if we convert the output of Dragonbox back to IEEE-754 binary floating-point format, the result should be equal to the original input to Dragonbox. However, converting the decimal output of Dragonbox back into binary floating-point number requires a rounding, so in order to ensure the roundtrip guarantee, Dragonbox must assume which kind of rounding will be performed for *the inverse, decimal-to-binary conversion*.
|
||||
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_even`: **This is the default policy.** Use *round-to-nearest, tie-to-even* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_odd`: Use *round-to-nearest, tie-to-odd* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_plus_infinity`: Use *round-to-nearest, tie-toward-plus-infinity* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_minus_infinity`: Use *round-to-nearest, tie-toward-minus-infinity* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_zero`: Use *round-to-nearest, tie-toward-zero* rounding mode. This will produce the fastest code among all *round-to-nearest* rounding modes.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_away_from_zero`: Use *round-to-nearest, tie-away-from-zero* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_even_static_boundary`: Use *round-to-nearest, tie-to-even* rounding mode, but there will be completely independent code paths for even inputs and odd inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_even` for some situation.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_odd_static_boundary`: Use *round-to-nearest, tie-to-odd* rounding mode, but there will be completely independent code paths for even inputs and odd inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_odd` for some situation.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_plus_infinity_static_boundary`: Use *round-to-nearest, tie-toward-plus-infinity* rounding mode, but there will be completely independent code paths for positive inputs and negative inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_plus_infinity` for some situation.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_minus_infinity_static_boundary`: Use *round-to-nearest, tie-toward-plus-infinity* rounding mode, but there will be completely independent code paths for positive inputs and negative inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_minus_infinity` for some situation.
|
||||
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::toward_plus_infinity`: Use *round-toward-plus-infinity* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::toward_minus_infinity`: Use *round-toward-minus-infinity* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::toward_zero`: Use *round-toward-zero* rounding mode.
|
||||
- `jkj::dragonbox::policy::decimal_to_binary_rounding::away_from_zero`: Use *away-from-zero* rounding mode.
|
||||
|
||||
All of these policies can be specified also to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`.
|
||||
|
||||
## Binary-to-decimal rounding policy
|
||||
Determines what `jkj::dragonbox::to_decimal` will do when rounding tie occurs while obtaining the decimal significand. This policy will be completely ignored if the specified binary-to-decimal rounding policy is not one of the round-to-nearest policies (because for other policies rounding tie simply doesn't exist).
|
||||
|
||||
- `jkj::dragonbox::policy::binary_to_decimal_rounding::do_not_care`: Do not care about correct rounding at all and just find any shortest output with the correct roundtrip. It will produce a faster code, but the performance difference will not be big.
|
||||
- `jkj::dragonbox::policy::binary_to_decimal_rounding::to_even`: **This is the default policy.** Choose the even number when rounding tie occurs.
|
||||
- `jkj::dragonbox::policy::binary_to_decimal_rounding::to_odd`: Choose the odd number when rounding tie occurs.
|
||||
- `jkj::dragonbox::policy::binary_to_decimal_rounding::away_from_zero`: Choose the number with the bigger absolute value when rounding tie occurs.
|
||||
- `jkj::dragonbox::policy::binary_to_decimal_rounding::toward_zero`: Choose the number with the smaller absolute value when rounding tie occurs.
|
||||
|
||||
All of these policies can be specified also to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`.
|
||||
|
||||
## Cache policy
|
||||
Choose between the full cache table and the compressed one. Using the compressed cache will result in about 20% slower code, but it can significantly reduce the amount of required static data. It currently has no effect for binary32 (`float`) inputs. For binary64 (`double`) inputs, `jkj::dragonbox::cache_policy::full` will cause `jkj::dragonbox::to_decimal` to use `619*16 = 9904` bytes of static data table, while the corresponding amount for `jkj::dragonbox::cache_policy::compact` is `23*16 + 27*8 = 584` bytes.
|
||||
|
||||
- `jkj::dragonbox::policy::cache::full`: **This is the default policy.** Use the full table.
|
||||
- `jkj::dragonbox::policy::cache::compact`: Use the compressed table.
|
||||
|
||||
All of these policies can be specified also to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`.
|
||||
|
||||
|
||||
# Performance
|
||||
In my machine (Intel Core i7-7700HQ 2.80GHz, Windows 10), it defeats or is on par with other contemporary algorithms including Grisu-Exact, Ryu, and Schubfach.
|
||||
|
||||
The following benchmark result (performed on 03/30/2024) is obtained using Milo's dtoa benchmark framework ([https://github.com/miloyip/dtoa-benchmark](https://github.com/miloyip/dtoa-benchmark)). The complete source code for the benchmark below is available [here](https://github.com/jk-jeon/dtoa-benchmark).
|
||||
|
||||

|
||||

|
||||
|
||||
Note 1: `dragonbox` is the performance of Dragonbox with the full cache table, and `dragonbox_comp` is the performance of Dragonbox with the compact cache table.
|
||||
|
||||
Note 2: [`fmt`](https://github.com/fmtlib/fmt) internally uses Dragonbox with an implementation almost identical to that in this repository.
|
||||
|
||||
There is also a benchmark done by myself (also performed on 03/30/2024):
|
||||
|
||||
(top: benchmark for ````float```` data, bottom: benchmark for ````double```` data; solid lines are the averages, dashed lines are the medians, and the shaded regions show 30%, 50%, and 70% percentiles):
|
||||
|
||||
(Clang)
|
||||

|
||||

|
||||
|
||||
(MSVC)
|
||||

|
||||

|
||||
|
||||
Here is another performance plot with uniformly randomly generated ````float````(top) or ````double````(bottom) data:
|
||||
|
||||
(Clang)
|
||||

|
||||

|
||||
|
||||
(MSVC)
|
||||

|
||||

|
||||
|
||||
(Note: the comparison with Schubfach is not completely fair, since the implementation I benchmarked against uses a digit generation procedure with a different set of constraints. More fair comparison is available in [this repository](https://github.com/abolz/Drachennest).)
|
||||
|
||||
# Comprehensive Explanation of the Algorithm
|
||||
Please see [this](other_files/Dragonbox.pdf) paper.
|
||||
|
||||
# How to Run Tests, Benchmark, and Others
|
||||
There are four subprojects contained in this repository:
|
||||
1. [`common`](subproject/common): The subproject that other subprojects depend on.
|
||||
2. [`benchmark`](subproject/benchmark): Runs benchmark.
|
||||
3. [`test`](subproject/test): Runs tests.
|
||||
4. [`meta`](subproject/meta): Generates static data that the main library uses.
|
||||
|
||||
## Build each subproject independently
|
||||
All subprojects including tests and benchmark are standalone, which means that you can build and run each of them independently. For example, you can do the following to run tests:
|
||||
```
|
||||
git clone https://github.com/jk-jeon/dragonbox
|
||||
cd dragonbox
|
||||
mkdir -p build/subproject/test
|
||||
cd build/subproject/test
|
||||
cmake ../../../subproject/test
|
||||
cmake --build .
|
||||
ctest .
|
||||
```
|
||||
(You might need to pass the configuration option to `cmake` and `ctest` if you use multi-configuration generators like Visual Studio.)
|
||||
|
||||
## Build all subprojects from the root directory
|
||||
It is also possible to build all subprojects from the root directory by passing the option `-DDRAGONBOX_ENABLE_SUBPROJECT=On` to `cmake`:
|
||||
```
|
||||
git clone https://github.com/jk-jeon/dragonbox
|
||||
cd dragonbox
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DDRAGONBOX_ENABLE_SUBPROJECT=On
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
## Notes on working directory
|
||||
Some executable files require the correct working directory to be set. For example, the executable for [`benchmark`](subproject/benchmark) runs some MATLAB scripts provided in [`subproject/benchmark/matlab`](subproject/benchmark/matlab) directory, which will fail to execute if the working directory is not set to [`subproject/benchmark`](subproject/benchmark). If you use the provided `CMakeLists.txt` files to generate a Visual Studio solution, the debugger's working directory is automatically set to the corresponding source directory. For example, the working directory is set to [`subproject/benchmark`](subproject/benchmark) for the benchmark subproject. However, other generators of cmake are not able to set the debugger's working directory, so in that case you need to manually set the correct working directory when running the executables in order to make them work correctly.
|
||||
|
||||
|
||||
# Notes
|
||||
|
||||
## Correctness of the algorithm
|
||||
|
||||
The [paper](other_files/Dragonbox.pdf) provides a mathematical proof of the correctness of the algorithm, with the aid of verification programs in [`test`](test) and [`meta`](meta) directories. In addition to that, I did a fair amount of uniformly random tests against Ryu (which is extremely heavily tested in its own), and I also ran a joint test of Dragonbox with a binary-to-decimal floating-point conversion routine I developed, and confirmed correct roundtrip for all possible IEEE-754 binary32-encoded floating-point numbers (aka `float`) with the round-to-nearest, tie-to-even rounding mode. Therefore, I am pretty confident about the correctness of both of the algorithms.
|
||||
|
||||
## Precise meaning of roundtrip guarantee
|
||||
|
||||
The precise meaning of roundtrip guarantee might be tricky, as it depends on the notion of "correct parsers". For example, given that `significand` and `exponent` are the outputs of Dragonbox with respect to an input floating-point number `x` of, say, type `double`, then things like `x == significand * pow(10.0, exponent)` might or might not be the case, because each of the floating-point operations in the expression `significand * pow(10.0, exponent)` can introduce rounding errors that can accumulate to a bigger error. What a correct parser should do is to precisely compute the floating-point number from the given expression according to the assumed rounding rule, and the result must be "correctly rounded" in the sense that only the minimum possible rounding error is allowed. Implementing a correct parser is indeed a very nontrivial job, so you may need additional libraries (like [Ryu](https://github.com/ulfjack/ryu) or [double-conversion](https://github.com/google/double-conversion)) if you want to check this roundtrip guarantee by yourself.
|
||||
|
||||
# License
|
||||
All code, except for those belong to third-party libraries (code in [`subproject/3rdparty`](subproject/3rdparty)), is licensed under either of
|
||||
|
||||
* Apache License Version 2.0 with LLVM Exceptions ([LICENSE-Apache2-LLVM](LICENSE-Apache2-LLVM) or https://llvm.org/foundation/relicensing/LICENSE.txt) or
|
||||
* Boost Software License Version 1.0 ([LICENSE-Boost](LICENSE-Boost) or https://www.boost.org/LICENSE_1_0.txt).
|
||||
|
||||
4205
src/external/dragonbox/dragonbox.h
vendored
Normal file
4205
src/external/dragonbox/dragonbox.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
545
src/external/dragonbox/dragonbox_to_chars.cpp
vendored
Normal file
545
src/external/dragonbox/dragonbox_to_chars.cpp
vendored
Normal file
@@ -0,0 +1,545 @@
|
||||
// Copyright 2020-2024 Junekey Jeon
|
||||
//
|
||||
// The contents of this file may be used under the terms of
|
||||
// the Apache License v2.0 with LLVM Exceptions.
|
||||
//
|
||||
// (See accompanying file LICENSE-Apache or copy at
|
||||
// https://llvm.org/foundation/relicensing/LICENSE.txt)
|
||||
//
|
||||
// Alternatively, the contents of this file may be used under the terms of
|
||||
// the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE-Boost or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, this software
|
||||
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied.
|
||||
|
||||
|
||||
//#include "dragonbox/dragonbox_to_chars.h"
|
||||
#include "dragonbox_to_chars.h"
|
||||
|
||||
#ifndef JKJ_STATIC_DATA_SECTION
|
||||
#define JKJ_STATIC_DATA_SECTION
|
||||
#endif
|
||||
|
||||
// C++17 if constexpr
|
||||
#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L
|
||||
#define JKJ_HAS_IF_CONSTEXPR 1
|
||||
#elif __cplusplus >= 201703L
|
||||
#define JKJ_HAS_IF_CONSTEXPR 1
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L
|
||||
#define JKJ_HAS_IF_CONSTEXPR 1
|
||||
#else
|
||||
#define JKJ_HAS_IF_CONSTEXPR 0
|
||||
#endif
|
||||
|
||||
#if JKJ_HAS_IF_CONSTEXPR
|
||||
#define JKJ_IF_CONSTEXPR if constexpr
|
||||
#else
|
||||
#define JKJ_IF_CONSTEXPR if
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define JKJ_FORCEINLINE inline __attribute__((always_inline))
|
||||
#elif defined(_MSC_VER)
|
||||
#define JKJ_FORCEINLINE __forceinline
|
||||
#else
|
||||
#define JKJ_FORCEINLINE inline
|
||||
#endif
|
||||
|
||||
namespace jkj {
|
||||
namespace dragonbox {
|
||||
namespace detail {
|
||||
// These "//"'s are to prevent clang-format to ruin this nice alignment.
|
||||
// Thanks to reddit user u/mcmcc:
|
||||
// https://www.reddit.com/r/cpp/comments/so3wx9/dragonbox_110_is_released_a_fast_floattostring/hw8z26r/?context=3
|
||||
static constexpr char radix_100_table[200] JKJ_STATIC_DATA_SECTION = {
|
||||
'0', '0', '0', '1', '0', '2', '0', '3', '0', '4', //
|
||||
'0', '5', '0', '6', '0', '7', '0', '8', '0', '9', //
|
||||
'1', '0', '1', '1', '1', '2', '1', '3', '1', '4', //
|
||||
'1', '5', '1', '6', '1', '7', '1', '8', '1', '9', //
|
||||
'2', '0', '2', '1', '2', '2', '2', '3', '2', '4', //
|
||||
'2', '5', '2', '6', '2', '7', '2', '8', '2', '9', //
|
||||
'3', '0', '3', '1', '3', '2', '3', '3', '3', '4', //
|
||||
'3', '5', '3', '6', '3', '7', '3', '8', '3', '9', //
|
||||
'4', '0', '4', '1', '4', '2', '4', '3', '4', '4', //
|
||||
'4', '5', '4', '6', '4', '7', '4', '8', '4', '9', //
|
||||
'5', '0', '5', '1', '5', '2', '5', '3', '5', '4', //
|
||||
'5', '5', '5', '6', '5', '7', '5', '8', '5', '9', //
|
||||
'6', '0', '6', '1', '6', '2', '6', '3', '6', '4', //
|
||||
'6', '5', '6', '6', '6', '7', '6', '8', '6', '9', //
|
||||
'7', '0', '7', '1', '7', '2', '7', '3', '7', '4', //
|
||||
'7', '5', '7', '6', '7', '7', '7', '8', '7', '9', //
|
||||
'8', '0', '8', '1', '8', '2', '8', '3', '8', '4', //
|
||||
'8', '5', '8', '6', '8', '7', '8', '8', '8', '9', //
|
||||
'9', '0', '9', '1', '9', '2', '9', '3', '9', '4', //
|
||||
'9', '5', '9', '6', '9', '7', '9', '8', '9', '9' //
|
||||
};
|
||||
static constexpr char radix_100_head_table[200] JKJ_STATIC_DATA_SECTION = {
|
||||
'0', '.', '1', '.', '2', '.', '3', '.', '4', '.', //
|
||||
'5', '.', '6', '.', '7', '.', '8', '.', '9', '.', //
|
||||
'1', '.', '1', '.', '1', '.', '1', '.', '1', '.', //
|
||||
'1', '.', '1', '.', '1', '.', '1', '.', '1', '.', //
|
||||
'2', '.', '2', '.', '2', '.', '2', '.', '2', '.', //
|
||||
'2', '.', '2', '.', '2', '.', '2', '.', '2', '.', //
|
||||
'3', '.', '3', '.', '3', '.', '3', '.', '3', '.', //
|
||||
'3', '.', '3', '.', '3', '.', '3', '.', '3', '.', //
|
||||
'4', '.', '4', '.', '4', '.', '4', '.', '4', '.', //
|
||||
'4', '.', '4', '.', '4', '.', '4', '.', '4', '.', //
|
||||
'5', '.', '5', '.', '5', '.', '5', '.', '5', '.', //
|
||||
'5', '.', '5', '.', '5', '.', '5', '.', '5', '.', //
|
||||
'6', '.', '6', '.', '6', '.', '6', '.', '6', '.', //
|
||||
'6', '.', '6', '.', '6', '.', '6', '.', '6', '.', //
|
||||
'7', '.', '7', '.', '7', '.', '7', '.', '7', '.', //
|
||||
'7', '.', '7', '.', '7', '.', '7', '.', '7', '.', //
|
||||
'8', '.', '8', '.', '8', '.', '8', '.', '8', '.', //
|
||||
'8', '.', '8', '.', '8', '.', '8', '.', '8', '.', //
|
||||
'9', '.', '9', '.', '9', '.', '9', '.', '9', '.', //
|
||||
'9', '.', '9', '.', '9', '.', '9', '.', '9', '.' //
|
||||
};
|
||||
|
||||
static void print_1_digit(int n, char* buffer) noexcept {
|
||||
JKJ_IF_CONSTEXPR(('0' & 0xf) == 0) { *buffer = char('0' | n); }
|
||||
else {
|
||||
*buffer = char('0' + n);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_2_digits(int n, char* buffer) noexcept {
|
||||
stdr::memcpy(buffer, radix_100_table + n * 2, 2);
|
||||
}
|
||||
|
||||
// These digit generation routines are inspired by James Anhalt's itoa algorithm:
|
||||
// https://github.com/jeaiii/itoa
|
||||
// The main idea is for given n, find y such that floor(10^k * y / 2^32) = n holds,
|
||||
// where k is an appropriate integer depending on the length of n.
|
||||
// For example, if n = 1234567, we set k = 6. In this case, we have
|
||||
// floor(y / 2^32) = 1,
|
||||
// floor(10^2 * ((10^0 * y) mod 2^32) / 2^32) = 23,
|
||||
// floor(10^2 * ((10^2 * y) mod 2^32) / 2^32) = 45, and
|
||||
// floor(10^2 * ((10^4 * y) mod 2^32) / 2^32) = 67.
|
||||
// See https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ for more explanation.
|
||||
|
||||
JKJ_FORCEINLINE static void print_9_digits(stdr::uint_least32_t s32, int& exponent,
|
||||
char*& buffer) noexcept {
|
||||
// -- IEEE-754 binary32
|
||||
// Since we do not cut trailing zeros in advance, s32 must be of 6~9 digits
|
||||
// unless the original input was subnormal.
|
||||
// In particular, when it is of 9 digits it shouldn't have any trailing zeros.
|
||||
// -- IEEE-754 binary64
|
||||
// In this case, s32 must be of 7~9 digits unless the input is subnormal,
|
||||
// and it shouldn't have any trailing zeros if it is of 9 digits.
|
||||
if (s32 >= UINT32_C(100000000)) {
|
||||
// 9 digits.
|
||||
// 1441151882 = ceil(2^57 / 1'0000'0000) + 1
|
||||
auto prod = s32 * UINT64_C(1441151882);
|
||||
prod >>= 25;
|
||||
stdr::memcpy(buffer, radix_100_head_table + int(prod >> 32) * 2, 2);
|
||||
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 4);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 6);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 8);
|
||||
|
||||
exponent += 8;
|
||||
buffer += 10;
|
||||
}
|
||||
else if (s32 >= UINT32_C(1000000)) {
|
||||
// 7 or 8 digits.
|
||||
// 281474978 = ceil(2^48 / 100'0000) + 1
|
||||
auto prod = s32 * UINT64_C(281474978);
|
||||
prod >>= 16;
|
||||
auto const head_digits = int(prod >> 32);
|
||||
// If s32 is of 8 digits, increase the exponent by 7.
|
||||
// Otherwise, increase it by 6.
|
||||
exponent += (6 + int(head_digits >= 10));
|
||||
|
||||
// Write the first digit and the decimal point.
|
||||
stdr::memcpy(buffer, radix_100_head_table + head_digits * 2, 2);
|
||||
// This third character may be overwritten later but we don't care.
|
||||
buffer[2] = radix_100_table[head_digits * 2 + 1];
|
||||
|
||||
// Remaining 6 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / UINT32_C(1000000))) {
|
||||
// The number of characters actually need to be written is:
|
||||
// 1, if only the first digit is nonzero, which means that either s32 is of 7
|
||||
// digits or it is of 8 digits but the second digit is zero, or
|
||||
// 3, otherwise.
|
||||
// Note that buffer[2] is never '0' if s32 is of 7 digits, because the input is
|
||||
// never zero.
|
||||
buffer += (1 + (int(head_digits >= 10) & int(buffer[2] > '0')) * 2);
|
||||
}
|
||||
else {
|
||||
// At least one of the remaining 6 digits are nonzero.
|
||||
// After this adjustment, now the first destination becomes buffer + 2.
|
||||
buffer += int(head_digits >= 10);
|
||||
|
||||
// Obtain the next two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
|
||||
// Remaining 4 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / 10000)) {
|
||||
buffer += (3 + int(buffer[3] > '0'));
|
||||
}
|
||||
else {
|
||||
// At least one of the remaining 4 digits are nonzero.
|
||||
|
||||
// Obtain the next two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 4);
|
||||
|
||||
// Remaining 2 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / 100)) {
|
||||
buffer += (5 + int(buffer[5] > '0'));
|
||||
}
|
||||
else {
|
||||
// Obtain the last two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 6);
|
||||
|
||||
buffer += (7 + int(buffer[7] > '0'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (s32 >= 10000) {
|
||||
// 5 or 6 digits.
|
||||
// 429497 = ceil(2^32 / 1'0000)
|
||||
auto prod = s32 * UINT64_C(429497);
|
||||
auto const head_digits = int(prod >> 32);
|
||||
|
||||
// If s32 is of 6 digits, increase the exponent by 5.
|
||||
// Otherwise, increase it by 4.
|
||||
exponent += (4 + int(head_digits >= 10));
|
||||
|
||||
// Write the first digit and the decimal point.
|
||||
stdr::memcpy(buffer, radix_100_head_table + head_digits * 2, 2);
|
||||
// This third character may be overwritten later but we don't care.
|
||||
buffer[2] = radix_100_table[head_digits * 2 + 1];
|
||||
|
||||
// Remaining 4 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / 10000)) {
|
||||
// The number of characters actually written is 1 or 3, similarly to the case of
|
||||
// 7 or 8 digits.
|
||||
buffer += (1 + (int(head_digits >= 10) & int(buffer[2] > '0')) * 2);
|
||||
}
|
||||
else {
|
||||
// At least one of the remaining 4 digits are nonzero.
|
||||
// After this adjustment, now the first destination becomes buffer + 2.
|
||||
buffer += int(head_digits >= 10);
|
||||
|
||||
// Obtain the next two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
|
||||
// Remaining 2 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / 100)) {
|
||||
buffer += (3 + int(buffer[3] > '0'));
|
||||
}
|
||||
else {
|
||||
// Obtain the last two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 4);
|
||||
|
||||
buffer += (5 + int(buffer[5] > '0'));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (s32 >= 100) {
|
||||
// 3 or 4 digits.
|
||||
// 42949673 = ceil(2^32 / 100)
|
||||
auto prod = s32 * UINT64_C(42949673);
|
||||
auto const head_digits = int(prod >> 32);
|
||||
|
||||
// If s32 is of 4 digits, increase the exponent by 3.
|
||||
// Otherwise, increase it by 2.
|
||||
exponent += (2 + int(head_digits >= 10));
|
||||
|
||||
// Write the first digit and the decimal point.
|
||||
stdr::memcpy(buffer, radix_100_head_table + head_digits * 2, 2);
|
||||
// This third character may be overwritten later but we don't care.
|
||||
buffer[2] = radix_100_table[head_digits * 2 + 1];
|
||||
|
||||
// Remaining 2 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / 100)) {
|
||||
// The number of characters actually written is 1 or 3, similarly to the case of
|
||||
// 7 or 8 digits.
|
||||
buffer += (1 + (int(head_digits >= 10) & int(buffer[2] > '0')) * 2);
|
||||
}
|
||||
else {
|
||||
// At least one of the remaining 2 digits are nonzero.
|
||||
// After this adjustment, now the first destination becomes buffer + 2.
|
||||
buffer += int(head_digits >= 10);
|
||||
|
||||
// Obtain the last two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
|
||||
buffer += (3 + int(buffer[3] > '0'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 1 or 2 digits.
|
||||
// If s32 is of 2 digits, increase the exponent by 1.
|
||||
exponent += int(s32 >= 10);
|
||||
|
||||
// Write the first digit and the decimal point.
|
||||
stdr::memcpy(buffer, radix_100_head_table + s32 * 2, 2);
|
||||
// This third character may be overwritten later but we don't care.
|
||||
buffer[2] = radix_100_table[s32 * 2 + 1];
|
||||
|
||||
// The number of characters actually written is 1 or 3, similarly to the case of
|
||||
// 7 or 8 digits.
|
||||
buffer += (1 + (int(s32 >= 10) & int(buffer[2] > '0')) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
char* to_chars<ieee754_binary32, stdr::uint_least32_t>(stdr::uint_least32_t s32,
|
||||
int exponent,
|
||||
char* buffer) noexcept {
|
||||
// Print significand.
|
||||
print_9_digits(s32, exponent, buffer);
|
||||
|
||||
// Print exponent and return
|
||||
if (exponent < 0) {
|
||||
stdr::memcpy(buffer, "E-", 2);
|
||||
buffer += 2;
|
||||
exponent = -exponent;
|
||||
}
|
||||
else {
|
||||
buffer[0] = 'E';
|
||||
buffer += 1;
|
||||
}
|
||||
|
||||
if (exponent >= 10) {
|
||||
print_2_digits(exponent, buffer);
|
||||
buffer += 2;
|
||||
}
|
||||
else {
|
||||
print_1_digit(exponent, buffer);
|
||||
buffer += 1;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
template <>
|
||||
char*
|
||||
to_chars<ieee754_binary64, stdr::uint_least64_t>(stdr::uint_least64_t const significand,
|
||||
int exponent, char* buffer) noexcept {
|
||||
// Print significand by decomposing it into a 9-digit block and a 8-digit block.
|
||||
stdr::uint_least32_t first_block, second_block;
|
||||
bool no_second_block;
|
||||
|
||||
if (significand >= UINT64_C(100000000)) {
|
||||
first_block = stdr::uint_least32_t(significand / UINT64_C(100000000));
|
||||
second_block =
|
||||
stdr::uint_least32_t(significand) - first_block * UINT32_C(100000000);
|
||||
exponent += 8;
|
||||
no_second_block = (second_block == 0);
|
||||
}
|
||||
else {
|
||||
first_block = stdr::uint_least32_t(significand);
|
||||
no_second_block = true;
|
||||
}
|
||||
|
||||
if (no_second_block) {
|
||||
print_9_digits(first_block, exponent, buffer);
|
||||
}
|
||||
else {
|
||||
// We proceed similarly to print_9_digits(), but since we do not need to remove
|
||||
// trailing zeros, the procedure is a bit simpler.
|
||||
if (first_block >= UINT32_C(100000000)) {
|
||||
// The input is of 17 digits, thus there should be no trailing zero at all.
|
||||
// The first block is of 9 digits.
|
||||
// 1441151882 = ceil(2^57 / 1'0000'0000) + 1
|
||||
auto prod = first_block * UINT64_C(1441151882);
|
||||
prod >>= 25;
|
||||
stdr::memcpy(buffer, radix_100_head_table + int(prod >> 32) * 2, 2);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 4);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 6);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 8);
|
||||
|
||||
// The second block is of 8 digits.
|
||||
// 281474978 = ceil(2^48 / 100'0000) + 1
|
||||
prod = second_block * UINT64_C(281474978);
|
||||
prod >>= 16;
|
||||
prod += 1;
|
||||
print_2_digits(int(prod >> 32), buffer + 10);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 12);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 14);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 16);
|
||||
|
||||
exponent += 8;
|
||||
buffer += 18;
|
||||
}
|
||||
else {
|
||||
if (first_block >= UINT32_C(1000000)) {
|
||||
// 7 or 8 digits.
|
||||
// 281474978 = ceil(2^48 / 100'0000) + 1
|
||||
auto prod = first_block * UINT64_C(281474978);
|
||||
prod >>= 16;
|
||||
auto const head_digits = int(prod >> 32);
|
||||
|
||||
stdr::memcpy(buffer, radix_100_head_table + head_digits * 2, 2);
|
||||
buffer[2] = radix_100_table[head_digits * 2 + 1];
|
||||
|
||||
exponent += (6 + int(head_digits >= 10));
|
||||
buffer += int(head_digits >= 10);
|
||||
|
||||
// Print remaining 6 digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 4);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 6);
|
||||
|
||||
buffer += 8;
|
||||
}
|
||||
else if (first_block >= 10000) {
|
||||
// 5 or 6 digits.
|
||||
// 429497 = ceil(2^32 / 1'0000)
|
||||
auto prod = first_block * UINT64_C(429497);
|
||||
auto const head_digits = int(prod >> 32);
|
||||
|
||||
stdr::memcpy(buffer, radix_100_head_table + head_digits * 2, 2);
|
||||
buffer[2] = radix_100_table[head_digits * 2 + 1];
|
||||
|
||||
exponent += (4 + int(head_digits >= 10));
|
||||
buffer += int(head_digits >= 10);
|
||||
|
||||
// Print remaining 4 digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 4);
|
||||
|
||||
buffer += 6;
|
||||
}
|
||||
else if (first_block >= 100) {
|
||||
// 3 or 4 digits.
|
||||
// 42949673 = ceil(2^32 / 100)
|
||||
auto prod = first_block * UINT64_C(42949673);
|
||||
auto const head_digits = int(prod >> 32);
|
||||
|
||||
stdr::memcpy(buffer, radix_100_head_table + head_digits * 2, 2);
|
||||
buffer[2] = radix_100_table[head_digits * 2 + 1];
|
||||
|
||||
exponent += (2 + int(head_digits >= 10));
|
||||
buffer += int(head_digits >= 10);
|
||||
|
||||
// Print remaining 2 digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
|
||||
buffer += 4;
|
||||
}
|
||||
else {
|
||||
// 1 or 2 digits.
|
||||
stdr::memcpy(buffer, radix_100_head_table + first_block * 2, 2);
|
||||
buffer[2] = radix_100_table[first_block * 2 + 1];
|
||||
|
||||
exponent += int(first_block >= 10);
|
||||
buffer += (2 + int(first_block >= 10));
|
||||
}
|
||||
|
||||
// Next, print the second block.
|
||||
// The second block is of 8 digits, but we may have trailing zeros.
|
||||
// 281474978 = ceil(2^48 / 100'0000) + 1
|
||||
auto prod = second_block * UINT64_C(281474978);
|
||||
prod >>= 16;
|
||||
prod += 1;
|
||||
print_2_digits(int(prod >> 32), buffer);
|
||||
|
||||
// Remaining 6 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / UINT64_C(1000000))) {
|
||||
buffer += (1 + int(buffer[1] > '0'));
|
||||
}
|
||||
else {
|
||||
// Obtain the next two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 2);
|
||||
|
||||
// Remaining 4 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / 10000)) {
|
||||
buffer += (3 + int(buffer[3] > '0'));
|
||||
}
|
||||
else {
|
||||
// Obtain the next two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 4);
|
||||
|
||||
// Remaining 2 digits are all zero?
|
||||
if ((prod & UINT32_C(0xffffffff)) <=
|
||||
stdr::uint_least32_t((stdr::uint_least64_t(1) << 32) / 100)) {
|
||||
buffer += (5 + int(buffer[5] > '0'));
|
||||
}
|
||||
else {
|
||||
// Obtain the last two digits.
|
||||
prod = (prod & UINT32_C(0xffffffff)) * 100;
|
||||
print_2_digits(int(prod >> 32), buffer + 6);
|
||||
buffer += (7 + int(buffer[7] > '0'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print exponent and return
|
||||
if (exponent < 0) {
|
||||
stdr::memcpy(buffer, "E-", 2);
|
||||
buffer += 2;
|
||||
exponent = -exponent;
|
||||
}
|
||||
else {
|
||||
buffer[0] = 'E';
|
||||
buffer += 1;
|
||||
}
|
||||
|
||||
if (exponent >= 100) {
|
||||
// d1 = exponent / 10; d2 = exponent % 10;
|
||||
// 6554 = ceil(2^16 / 10)
|
||||
auto d1 = (std::uint_least32_t(exponent) * UINT32_C(6554)) >> 16;
|
||||
auto d2 = std::uint_least32_t(exponent) - UINT32_C(10) * d1;
|
||||
print_2_digits(int(d1), buffer);
|
||||
print_1_digit(int(d2), buffer + 2);
|
||||
buffer += 3;
|
||||
}
|
||||
else if (exponent >= 10) {
|
||||
print_2_digits(exponent, buffer);
|
||||
buffer += 2;
|
||||
}
|
||||
else {
|
||||
print_1_digit(exponent, buffer);
|
||||
buffer += 1;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
388
src/external/dragonbox/dragonbox_to_chars.h
vendored
Normal file
388
src/external/dragonbox/dragonbox_to_chars.h
vendored
Normal file
@@ -0,0 +1,388 @@
|
||||
// Copyright 2020-2024 Junekey Jeon
|
||||
//
|
||||
// The contents of this file may be used under the terms of
|
||||
// the Apache License v2.0 with LLVM Exceptions.
|
||||
//
|
||||
// (See accompanying file LICENSE-Apache or copy at
|
||||
// https://llvm.org/foundation/relicensing/LICENSE.txt)
|
||||
//
|
||||
// Alternatively, the contents of this file may be used under the terms of
|
||||
// the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE-Boost or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, this software
|
||||
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied.
|
||||
|
||||
#ifndef JKJ_HEADER_DRAGONBOX_TO_CHARS
|
||||
#define JKJ_HEADER_DRAGONBOX_TO_CHARS
|
||||
|
||||
#include "dragonbox.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Language feature detections.
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// C++14 constexpr
|
||||
#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304L
|
||||
#define JKJ_HAS_CONSTEXPR14 1
|
||||
#elif __cplusplus >= 201402L
|
||||
#define JKJ_HAS_CONSTEXPR14 1
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1910 && _MSVC_LANG >= 201402L
|
||||
#define JKJ_HAS_CONSTEXPR14 1
|
||||
#else
|
||||
#define JKJ_HAS_CONSTEXPR14 0
|
||||
#endif
|
||||
|
||||
#if JKJ_HAS_CONSTEXPR14
|
||||
#define JKJ_CONSTEXPR14 constexpr
|
||||
#else
|
||||
#define JKJ_CONSTEXPR14
|
||||
#endif
|
||||
|
||||
// C++17 constexpr lambdas
|
||||
#if defined(__cpp_constexpr) && __cpp_constexpr >= 201603L
|
||||
#define JKJ_HAS_CONSTEXPR17 1
|
||||
#elif __cplusplus >= 201703L
|
||||
#define JKJ_HAS_CONSTEXPR17 1
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L
|
||||
#define JKJ_HAS_CONSTEXPR17 1
|
||||
#else
|
||||
#define JKJ_HAS_CONSTEXPR17 0
|
||||
#endif
|
||||
|
||||
// C++17 inline variables
|
||||
#if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L
|
||||
#define JKJ_HAS_INLINE_VARIABLE 1
|
||||
#elif __cplusplus >= 201703L
|
||||
#define JKJ_HAS_INLINE_VARIABLE 1
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1912 && _MSVC_LANG >= 201703L
|
||||
#define JKJ_HAS_INLINE_VARIABLE 1
|
||||
#else
|
||||
#define JKJ_HAS_INLINE_VARIABLE 0
|
||||
#endif
|
||||
|
||||
#if JKJ_HAS_INLINE_VARIABLE
|
||||
#define JKJ_INLINE_VARIABLE inline constexpr
|
||||
#else
|
||||
#define JKJ_INLINE_VARIABLE static constexpr
|
||||
#endif
|
||||
|
||||
// C++17 if constexpr
|
||||
#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L
|
||||
#define JKJ_HAS_IF_CONSTEXPR 1
|
||||
#elif __cplusplus >= 201703L
|
||||
#define JKJ_HAS_IF_CONSTEXPR 1
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L
|
||||
#define JKJ_HAS_IF_CONSTEXPR 1
|
||||
#else
|
||||
#define JKJ_HAS_IF_CONSTEXPR 0
|
||||
#endif
|
||||
|
||||
#if JKJ_HAS_IF_CONSTEXPR
|
||||
#define JKJ_IF_CONSTEXPR if constexpr
|
||||
#else
|
||||
#define JKJ_IF_CONSTEXPR if
|
||||
#endif
|
||||
|
||||
// C++20 std::bit_cast
|
||||
#if JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED
|
||||
#if JKJ_STD_REPLACEMENT_HAS_BIT_CAST
|
||||
#define JKJ_HAS_BIT_CAST 1
|
||||
#else
|
||||
#define JKJ_HAS_BIT_CAST 0
|
||||
#endif
|
||||
#elif defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
|
||||
#include <bit>
|
||||
#define JKJ_HAS_BIT_CAST 1
|
||||
#else
|
||||
#define JKJ_HAS_BIT_CAST 0
|
||||
#endif
|
||||
|
||||
// C++23 if consteval or C++20 std::is_constant_evaluated
|
||||
#if defined(__cpp_if_consteval) && __cpp_is_consteval >= 202106L
|
||||
#define JKJ_IF_CONSTEVAL if consteval
|
||||
#define JKJ_IF_NOT_CONSTEVAL if !consteval
|
||||
#define JKJ_CAN_BRANCH_ON_CONSTEVAL 1
|
||||
#define JKJ_USE_IS_CONSTANT_EVALUATED 0
|
||||
#elif JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED
|
||||
#if JKJ_STD_REPLACEMENT_HAS_IS_CONSTANT_EVALUATED
|
||||
#define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated())
|
||||
#define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated())
|
||||
#define JKJ_CAN_BRANCH_ON_CONSTEVAL 1
|
||||
#define JKJ_USE_IS_CONSTANT_EVALUATED 1
|
||||
#elif JKJ_HAS_IF_CONSTEXPR
|
||||
#define JKJ_IF_CONSTEVAL if constexpr (false)
|
||||
#define JKJ_IF_NOT_CONSTEVAL if constexpr (true)
|
||||
#define JKJ_CAN_BRANCH_ON_CONSTEVAL 0
|
||||
#define JKJ_USE_IS_CONSTANT_EVALUATED 0
|
||||
#else
|
||||
#define JKJ_IF_CONSTEVAL if (false)
|
||||
#define JKJ_IF_NOT_CONSTEVAL if (true)
|
||||
#define JKJ_CAN_BRANCH_ON_CONSTEVAL 0
|
||||
#define JKJ_USE_IS_CONSTANT_EVALUATED 0
|
||||
#endif
|
||||
#else
|
||||
#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L
|
||||
#define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated())
|
||||
#define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated())
|
||||
#define JKJ_CAN_BRANCH_ON_CONSTEVAL 1
|
||||
#define JKJ_USE_IS_CONSTANT_EVALUATED 1
|
||||
#elif JKJ_HAS_IF_CONSTEXPR
|
||||
#define JKJ_IF_CONSTEVAL if constexpr (false)
|
||||
#define JKJ_IF_NOT_CONSTEVAL if constexpr (true)
|
||||
#define JKJ_CAN_BRANCH_ON_CONSTEVAL 0
|
||||
#define JKJ_USE_IS_CONSTANT_EVALUATED 0
|
||||
#else
|
||||
#define JKJ_IF_CONSTEVAL if (false)
|
||||
#define JKJ_IF_NOT_CONSTEVAL if (true)
|
||||
#define JKJ_CAN_BRANCH_ON_CONSTEVAL 0
|
||||
#define JKJ_USE_IS_CONSTANT_EVALUATED 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if JKJ_CAN_BRANCH_ON_CONSTEVAL && JKJ_HAS_BIT_CAST
|
||||
#define JKJ_CONSTEXPR20 constexpr
|
||||
#else
|
||||
#define JKJ_CONSTEXPR20
|
||||
#endif
|
||||
|
||||
namespace jkj {
|
||||
namespace dragonbox {
|
||||
namespace detail {
|
||||
template <class FloatFormat, class CarrierUInt>
|
||||
extern char* to_chars(CarrierUInt significand, int exponent, char* buffer) noexcept;
|
||||
|
||||
template <stdr::size_t max_digits, class UInt>
|
||||
JKJ_CONSTEXPR14 char* print_integer_naive(UInt n, char* buffer) noexcept {
|
||||
char temp[max_digits]{};
|
||||
auto ptr = temp + sizeof(temp) - 1;
|
||||
do {
|
||||
*ptr = char('0' + n % 10);
|
||||
n /= 10;
|
||||
--ptr;
|
||||
} while (n != 0);
|
||||
while (++ptr != temp + sizeof(temp)) {
|
||||
*buffer = *ptr;
|
||||
++buffer;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
template <class FloatFormat, class CarrierUInt>
|
||||
JKJ_CONSTEXPR14 char* to_chars_naive(CarrierUInt significand, int exponent,
|
||||
char* buffer) noexcept {
|
||||
// Print significand.
|
||||
{
|
||||
auto ptr = print_integer_naive<FloatFormat::decimal_significand_digits>(significand,
|
||||
buffer);
|
||||
|
||||
// Insert decimal dot.
|
||||
if (ptr > buffer + 1) {
|
||||
auto next = *++buffer;
|
||||
++exponent;
|
||||
*buffer = '.';
|
||||
while (++buffer != ptr) {
|
||||
auto const temp = *buffer;
|
||||
*buffer = next;
|
||||
next = temp;
|
||||
++exponent;
|
||||
}
|
||||
*buffer = next;
|
||||
}
|
||||
++buffer;
|
||||
}
|
||||
|
||||
// Print exponent.
|
||||
*buffer = 'E';
|
||||
++buffer;
|
||||
if (exponent < 0) {
|
||||
*buffer = '-';
|
||||
++buffer;
|
||||
exponent = -exponent;
|
||||
}
|
||||
return print_integer_naive<FloatFormat::decimal_exponent_digits>(unsigned(exponent),
|
||||
buffer);
|
||||
}
|
||||
}
|
||||
|
||||
namespace policy {
|
||||
namespace digit_generation {
|
||||
JKJ_INLINE_VARIABLE struct fast_t {
|
||||
using digit_generation_policy = fast_t;
|
||||
|
||||
template <class DecimalToBinaryRoundingPolicy, class BinaryToDecimalRoundingPolicy,
|
||||
class CachePolicy, class PreferredIntegerTypesPolicy, class FormatTraits>
|
||||
static char* to_chars(signed_significand_bits<FormatTraits> s,
|
||||
typename FormatTraits::exponent_int exponent_bits,
|
||||
char* buffer) noexcept {
|
||||
auto result = to_decimal_ex(
|
||||
s, exponent_bits, policy::sign::ignore, policy::trailing_zero::ignore,
|
||||
DecimalToBinaryRoundingPolicy{}, BinaryToDecimalRoundingPolicy{},
|
||||
CachePolicy{}, PreferredIntegerTypesPolicy{});
|
||||
|
||||
return detail::to_chars<typename FormatTraits::format>(result.significand,
|
||||
result.exponent, buffer);
|
||||
}
|
||||
} fast = {};
|
||||
|
||||
JKJ_INLINE_VARIABLE struct compact_t {
|
||||
using digit_generation_policy = compact_t;
|
||||
|
||||
template <class DecimalToBinaryRoundingPolicy, class BinaryToDecimalRoundingPolicy,
|
||||
class CachePolicy, class PreferredIntegerTypesPolicy, class FormatTraits>
|
||||
static JKJ_CONSTEXPR20 char*
|
||||
to_chars(signed_significand_bits<FormatTraits> s,
|
||||
typename FormatTraits::exponent_int exponent_bits, char* buffer) noexcept {
|
||||
auto result = to_decimal_ex(s, exponent_bits, policy::sign::ignore,
|
||||
policy::trailing_zero::remove_compact,
|
||||
DecimalToBinaryRoundingPolicy{},
|
||||
BinaryToDecimalRoundingPolicy{}, CachePolicy{},
|
||||
PreferredIntegerTypesPolicy{});
|
||||
|
||||
return detail::to_chars_naive<typename FormatTraits::format>(
|
||||
result.significand, result.exponent, buffer);
|
||||
}
|
||||
} compact = {};
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
struct is_digit_generation_policy {
|
||||
constexpr bool operator()(...) noexcept { return false; }
|
||||
template <class Policy, class = typename Policy::digit_generation_policy>
|
||||
constexpr bool operator()(dummy<Policy>) noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Avoid needless ABI overhead incurred by tag dispatch.
|
||||
template <class DecimalToBinaryRoundingPolicy, class BinaryToDecimalRoundingPolicy,
|
||||
class CachePolicy, class PreferredIntegerTypesPolicy, class DigitGenerationPolicy,
|
||||
class FormatTraits>
|
||||
JKJ_CONSTEXPR20 char* to_chars_n_impl(float_bits<FormatTraits> br, char* buffer) noexcept {
|
||||
auto const exponent_bits = br.extract_exponent_bits();
|
||||
auto const s = br.remove_exponent_bits();
|
||||
|
||||
if (br.is_finite(exponent_bits)) {
|
||||
if (s.is_negative()) {
|
||||
*buffer = '-';
|
||||
++buffer;
|
||||
}
|
||||
if (br.is_nonzero()) {
|
||||
JKJ_IF_CONSTEVAL {
|
||||
return policy::digit_generation::compact_t::to_chars<
|
||||
DecimalToBinaryRoundingPolicy, BinaryToDecimalRoundingPolicy,
|
||||
CachePolicy, PreferredIntegerTypesPolicy>(s, exponent_bits, buffer);
|
||||
}
|
||||
|
||||
return DigitGenerationPolicy::template to_chars<
|
||||
DecimalToBinaryRoundingPolicy, BinaryToDecimalRoundingPolicy, CachePolicy,
|
||||
PreferredIntegerTypesPolicy>(s, exponent_bits, buffer);
|
||||
}
|
||||
else {
|
||||
buffer[0] = '0';
|
||||
buffer[1] = 'E';
|
||||
buffer[2] = '0';
|
||||
return buffer + 3;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (s.has_all_zero_significand_bits()) {
|
||||
if (s.is_negative()) {
|
||||
*buffer = '-';
|
||||
++buffer;
|
||||
}
|
||||
// MSVC generates two mov's for the below, so we guard it inside
|
||||
// JKJ_IF_CONSTEVAL.
|
||||
JKJ_IF_CONSTEVAL {
|
||||
buffer[0] = 'I';
|
||||
buffer[1] = 'n';
|
||||
buffer[2] = 'f';
|
||||
buffer[3] = 'i';
|
||||
buffer[4] = 'n';
|
||||
buffer[5] = 'i';
|
||||
buffer[6] = 't';
|
||||
buffer[7] = 'y';
|
||||
}
|
||||
else {
|
||||
stdr::memcpy(buffer, "Infinity", 8);
|
||||
}
|
||||
return buffer + 8;
|
||||
}
|
||||
else {
|
||||
buffer[0] = 'N';
|
||||
buffer[1] = 'a';
|
||||
buffer[2] = 'N';
|
||||
return buffer + 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the next-to-end position
|
||||
template <class Float,
|
||||
class ConversionTraits = default_float_bit_carrier_conversion_traits<Float>,
|
||||
class FormatTraits = ieee754_binary_traits<typename ConversionTraits::format,
|
||||
typename ConversionTraits::carrier_uint>,
|
||||
class... Policies>
|
||||
JKJ_CONSTEXPR20 char* to_chars_n(Float x, char* buffer, Policies...) noexcept {
|
||||
using policy_holder = detail::make_policy_holder<
|
||||
detail::detector_default_pair_list<
|
||||
detail::detector_default_pair<
|
||||
detail::is_decimal_to_binary_rounding_policy,
|
||||
policy::decimal_to_binary_rounding::nearest_to_even_t>,
|
||||
detail::detector_default_pair<detail::is_binary_to_decimal_rounding_policy,
|
||||
policy::binary_to_decimal_rounding::to_even_t>,
|
||||
detail::detector_default_pair<detail::is_cache_policy, policy::cache::full_t>,
|
||||
detail::detector_default_pair<detail::is_preferred_integer_types_policy,
|
||||
policy::preferred_integer_types::match_t>,
|
||||
detail::detector_default_pair<detail::is_digit_generation_policy,
|
||||
policy::digit_generation::fast_t>>,
|
||||
Policies...>;
|
||||
|
||||
return detail::to_chars_n_impl<typename policy_holder::decimal_to_binary_rounding_policy,
|
||||
typename policy_holder::binary_to_decimal_rounding_policy,
|
||||
typename policy_holder::cache_policy,
|
||||
typename policy_holder::preferred_integer_types_policy,
|
||||
typename policy_holder::digit_generation_policy>(
|
||||
make_float_bits<Float, ConversionTraits, FormatTraits>(x), buffer);
|
||||
}
|
||||
|
||||
// Null-terminate and bypass the return value of to_chars_n
|
||||
template <class Float,
|
||||
class ConversionTraits = default_float_bit_carrier_conversion_traits<Float>,
|
||||
class FormatTraits = ieee754_binary_traits<typename ConversionTraits::format,
|
||||
typename ConversionTraits::carrier_uint>,
|
||||
class... Policies>
|
||||
JKJ_CONSTEXPR20 char* to_chars(Float x, char* buffer, Policies... policies) noexcept {
|
||||
auto ptr = to_chars_n<Float, ConversionTraits, FormatTraits>(x, buffer, policies...);
|
||||
*ptr = '\0';
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Maximum required buffer size (excluding null-terminator)
|
||||
template <class FloatFormat>
|
||||
JKJ_INLINE_VARIABLE detail::stdr::size_t max_output_string_length =
|
||||
// sign(1) + significand + decimal_point(1) + exp_marker(1) + exp_sign(1) + exp
|
||||
1 + FloatFormat::decimal_significand_digits + 1 + 1 + 1 +
|
||||
FloatFormat::decimal_exponent_digits;
|
||||
}
|
||||
}
|
||||
|
||||
#undef JKJ_CONSTEXPR20
|
||||
#undef JKJ_USE_IS_CONSTANT_EVALUATED
|
||||
#undef JKJ_CAN_BRANCH_ON_CONSTEVAL
|
||||
#undef JKJ_IF_NOT_CONSTEVAL
|
||||
#undef JKJ_IF_CONSTEVAL
|
||||
#undef JKJ_HAS_BIT_CAST
|
||||
#undef JKJ_IF_CONSTEXPR
|
||||
#undef JKJ_HAS_IF_CONSTEXPR
|
||||
#undef JKJ_INLINE_VARIABLE
|
||||
#undef JKJ_HAS_INLINE_VARIABLE
|
||||
#undef JKJ_HAS_CONSTEXPR17
|
||||
#undef JKJ_CONSTEXPR14
|
||||
#undef JKJ_HAS_CONSTEXPR14
|
||||
|
||||
#endif
|
||||
446
src/external/dtoa_milo.h
vendored
446
src/external/dtoa_milo.h
vendored
@@ -30,6 +30,58 @@ namespace gcc_ints
|
||||
|
||||
#define UINT64_C2(h, l) ((static_cast<uint64_t>(h) << 32) | static_cast<uint64_t>(l))
|
||||
|
||||
class Double {
|
||||
public:
|
||||
Double() {}
|
||||
Double(double d) : d_(d) {}
|
||||
Double(uint64_t u) : u_(u) {}
|
||||
|
||||
double Value() const { return d_; }
|
||||
uint64_t Uint64Value() const { return u_; }
|
||||
|
||||
double NextPositiveDouble() const {
|
||||
//RAPIDJSON_ASSERT(!Sign());
|
||||
return Double(u_ + 1).Value();
|
||||
}
|
||||
|
||||
bool Sign() const { return (u_ & kSignMask) != 0; }
|
||||
uint64_t Significand() const { return u_ & kSignificandMask; }
|
||||
int Exponent() const { return static_cast<int>(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); }
|
||||
|
||||
bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; }
|
||||
bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; }
|
||||
bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; }
|
||||
bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; }
|
||||
bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; }
|
||||
|
||||
uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); }
|
||||
int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; }
|
||||
uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; }
|
||||
|
||||
static int EffectiveSignificandSize(int order) {
|
||||
if (order >= -1021)
|
||||
return 53;
|
||||
else if (order <= -1074)
|
||||
return 0;
|
||||
else
|
||||
return order + 1074;
|
||||
}
|
||||
|
||||
private:
|
||||
static const int kSignificandSize = 52;
|
||||
static const int kExponentBias = 0x3FF;
|
||||
static const int kDenormalExponent = 1 - kExponentBias;
|
||||
static const uint64_t kSignMask = UINT64_C2(0x80000000, 0x00000000);
|
||||
static const uint64_t kExponentMask = UINT64_C2(0x7FF00000, 0x00000000);
|
||||
static const uint64_t kSignificandMask = UINT64_C2(0x000FFFFF, 0xFFFFFFFF);
|
||||
static const uint64_t kHiddenBit = UINT64_C2(0x00100000, 0x00000000);
|
||||
|
||||
union {
|
||||
double d_;
|
||||
uint64_t u_;
|
||||
};
|
||||
};
|
||||
|
||||
struct DiyFp {
|
||||
DiyFp() {}
|
||||
|
||||
@@ -220,102 +272,6 @@ inline DiyFp GetCachedPower(int e, int* K) {
|
||||
return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]);
|
||||
}
|
||||
|
||||
inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) {
|
||||
while (rest < wp_w && delta - rest >= ten_kappa &&
|
||||
(rest + ten_kappa < wp_w || /// closer
|
||||
wp_w - rest > rest + ten_kappa - wp_w)) {
|
||||
buffer[len - 1]--;
|
||||
rest += ten_kappa;
|
||||
}
|
||||
}
|
||||
|
||||
inline unsigned CountDecimalDigit32(uint32_t n) {
|
||||
// Simple pure C++ implementation was faster than __builtin_clz version in this situation.
|
||||
if (n < 10) return 1;
|
||||
if (n < 100) return 2;
|
||||
if (n < 1000) return 3;
|
||||
if (n < 10000) return 4;
|
||||
if (n < 100000) return 5;
|
||||
if (n < 1000000) return 6;
|
||||
if (n < 10000000) return 7;
|
||||
if (n < 100000000) return 8;
|
||||
if (n < 1000000000) return 9;
|
||||
return 10;
|
||||
}
|
||||
|
||||
inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) {
|
||||
static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
|
||||
const DiyFp one(uint64_t(1) << -Mp.e, Mp.e);
|
||||
const DiyFp wp_w = Mp - W;
|
||||
uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e);
|
||||
uint64_t p2 = Mp.f & (one.f - 1);
|
||||
int kappa = static_cast<int>(CountDecimalDigit32(p1));
|
||||
*len = 0;
|
||||
|
||||
while (kappa > 0) {
|
||||
uint32_t d;
|
||||
switch (kappa) {
|
||||
case 10: d = p1 / 1000000000; p1 %= 1000000000; break;
|
||||
case 9: d = p1 / 100000000; p1 %= 100000000; break;
|
||||
case 8: d = p1 / 10000000; p1 %= 10000000; break;
|
||||
case 7: d = p1 / 1000000; p1 %= 1000000; break;
|
||||
case 6: d = p1 / 100000; p1 %= 100000; break;
|
||||
case 5: d = p1 / 10000; p1 %= 10000; break;
|
||||
case 4: d = p1 / 1000; p1 %= 1000; break;
|
||||
case 3: d = p1 / 100; p1 %= 100; break;
|
||||
case 2: d = p1 / 10; p1 %= 10; break;
|
||||
case 1: d = p1; p1 = 0; break;
|
||||
default:
|
||||
#if defined(_MSC_VER)
|
||||
__assume(0);
|
||||
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
||||
__builtin_unreachable();
|
||||
#else
|
||||
d = 0;
|
||||
#endif
|
||||
}
|
||||
if (d || *len)
|
||||
buffer[(*len)++] = '0' + static_cast<char>(d);
|
||||
kappa--;
|
||||
uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2;
|
||||
if (tmp <= delta) {
|
||||
*K += kappa;
|
||||
GrisuRound(buffer, *len, delta, tmp, static_cast<uint64_t>(kPow10[kappa]) << -one.e, wp_w.f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// kappa = 0
|
||||
for (;;) {
|
||||
p2 *= 10;
|
||||
delta *= 10;
|
||||
char d = static_cast<char>(p2 >> -one.e);
|
||||
if (d || *len)
|
||||
buffer[(*len)++] = '0' + d;
|
||||
p2 &= one.f - 1;
|
||||
kappa--;
|
||||
if (p2 < delta) {
|
||||
*K += kappa;
|
||||
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * kPow10[-kappa]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void Grisu2(double value, char* buffer, int* length, int* K) {
|
||||
const DiyFp v(value);
|
||||
DiyFp w_m, w_p;
|
||||
v.NormalizedBoundaries(&w_m, &w_p);
|
||||
|
||||
const DiyFp c_mk = GetCachedPower(w_p.e, K);
|
||||
const DiyFp W = v.Normalize() * c_mk;
|
||||
DiyFp Wp = w_p * c_mk;
|
||||
DiyFp Wm = w_m * c_mk;
|
||||
Wm.f++;
|
||||
Wp.f--;
|
||||
DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K);
|
||||
}
|
||||
|
||||
inline const char* GetDigitsLut() {
|
||||
static const char cDigitsLut[200] = {
|
||||
'0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9',
|
||||
@@ -332,89 +288,227 @@ inline const char* GetDigitsLut() {
|
||||
return cDigitsLut;
|
||||
}
|
||||
|
||||
inline void WriteExponent(int K, char* buffer) {
|
||||
if (K < 0) {
|
||||
*buffer++ = '-';
|
||||
K = -K;
|
||||
}
|
||||
|
||||
if (K >= 100) {
|
||||
*buffer++ = '0' + static_cast<char>(K / 100);
|
||||
K %= 100;
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
else if (K >= 10) {
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
else
|
||||
*buffer++ = '0' + static_cast<char>(K);
|
||||
|
||||
*buffer = '\0';
|
||||
inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) {
|
||||
while (rest < wp_w && delta - rest >= ten_kappa &&
|
||||
(rest + ten_kappa < wp_w || /// closer
|
||||
wp_w - rest > rest + ten_kappa - wp_w)) {
|
||||
buffer[len - 1]--;
|
||||
rest += ten_kappa;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Prettify(char* buffer, int length, int k) {
|
||||
const int kk = length + k; // 10^(kk-1) <= v < 10^kk
|
||||
|
||||
if (length <= kk && kk <= 21) {
|
||||
// 1234e7 -> 12340000000
|
||||
for (int i = length; i < kk; i++)
|
||||
buffer[i] = '0';
|
||||
buffer[kk] = '.';
|
||||
buffer[kk + 1] = '0';
|
||||
buffer[kk + 2] = '\0';
|
||||
}
|
||||
else if (0 < kk && kk <= 21) {
|
||||
// 1234e-2 -> 12.34
|
||||
memmove(&buffer[kk + 1], &buffer[kk], size_t(length - kk));
|
||||
buffer[kk] = '.';
|
||||
buffer[length + 1] = '\0';
|
||||
}
|
||||
else if (-6 < kk && kk <= 0) {
|
||||
// 1234e-6 -> 0.001234
|
||||
const int offset = 2 - kk;
|
||||
memmove(&buffer[offset], &buffer[0], size_t(length));
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
for (int i = 2; i < offset; i++)
|
||||
buffer[i] = '0';
|
||||
buffer[length + offset] = '\0';
|
||||
}
|
||||
else if (length == 1) {
|
||||
// 1e30
|
||||
buffer[1] = 'e';
|
||||
WriteExponent(kk - 1, &buffer[2]);
|
||||
}
|
||||
else {
|
||||
// 1234e30 -> 1.234e33
|
||||
memmove(&buffer[2], &buffer[1], size_t(length - 1));
|
||||
buffer[1] = '.';
|
||||
buffer[length + 1] = 'e';
|
||||
WriteExponent(kk - 1, &buffer[0 + length + 2]);
|
||||
}
|
||||
inline int CountDecimalDigit32(uint32_t n) {
|
||||
// Simple pure C++ implementation was faster than __builtin_clz version in this situation.
|
||||
if (n < 10) return 1;
|
||||
if (n < 100) return 2;
|
||||
if (n < 1000) return 3;
|
||||
if (n < 10000) return 4;
|
||||
if (n < 100000) return 5;
|
||||
if (n < 1000000) return 6;
|
||||
if (n < 10000000) return 7;
|
||||
if (n < 100000000) return 8;
|
||||
// Will not reach 10 digits in DigitGen()
|
||||
//if (n < 1000000000) return 9;
|
||||
//return 10;
|
||||
return 9;
|
||||
}
|
||||
|
||||
inline void dtoa_milo(double value, char* buffer) {
|
||||
// Not handling NaN and inf
|
||||
assert(!std::isnan(value));
|
||||
assert(!std::isinf(value));
|
||||
inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) {
|
||||
static const uint64_t kPow10[] = { 1ULL, 10ULL, 100ULL, 1000ULL, 10000ULL, 100000ULL, 1000000ULL, 10000000ULL, 100000000ULL,
|
||||
1000000000ULL, 10000000000ULL, 100000000000ULL, 1000000000000ULL,
|
||||
10000000000000ULL, 100000000000000ULL, 1000000000000000ULL,
|
||||
10000000000000000ULL, 100000000000000000ULL, 1000000000000000000ULL,
|
||||
10000000000000000000ULL };
|
||||
const DiyFp one(uint64_t(1) << -Mp.e, Mp.e);
|
||||
const DiyFp wp_w = Mp - W;
|
||||
uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e);
|
||||
uint64_t p2 = Mp.f & (one.f - 1);
|
||||
int kappa = CountDecimalDigit32(p1); // kappa in [0, 9]
|
||||
*len = 0;
|
||||
|
||||
while (kappa > 0) {
|
||||
uint32_t d = 0;
|
||||
switch (kappa) {
|
||||
case 9: d = p1 / 100000000; p1 %= 100000000; break;
|
||||
case 8: d = p1 / 10000000; p1 %= 10000000; break;
|
||||
case 7: d = p1 / 1000000; p1 %= 1000000; break;
|
||||
case 6: d = p1 / 100000; p1 %= 100000; break;
|
||||
case 5: d = p1 / 10000; p1 %= 10000; break;
|
||||
case 4: d = p1 / 1000; p1 %= 1000; break;
|
||||
case 3: d = p1 / 100; p1 %= 100; break;
|
||||
case 2: d = p1 / 10; p1 %= 10; break;
|
||||
case 1: d = p1; p1 = 0; break;
|
||||
default:;
|
||||
}
|
||||
if (d || *len)
|
||||
buffer[(*len)++] = static_cast<char>('0' + static_cast<char>(d));
|
||||
kappa--;
|
||||
uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2;
|
||||
if (tmp <= delta) {
|
||||
*K += kappa;
|
||||
GrisuRound(buffer, *len, delta, tmp, kPow10[kappa] << -one.e, wp_w.f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// kappa = 0
|
||||
for (;;) {
|
||||
p2 *= 10;
|
||||
delta *= 10;
|
||||
char d = static_cast<char>(p2 >> -one.e);
|
||||
if (d || *len)
|
||||
buffer[(*len)++] = static_cast<char>('0' + d);
|
||||
p2 &= one.f - 1;
|
||||
kappa--;
|
||||
if (p2 < delta) {
|
||||
*K += kappa;
|
||||
int index = -kappa;
|
||||
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 20 ? kPow10[index] : 0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void Grisu2(double value, char* buffer, int* length, int* K) {
|
||||
const DiyFp v(value);
|
||||
DiyFp w_m, w_p;
|
||||
v.NormalizedBoundaries(&w_m, &w_p);
|
||||
|
||||
const DiyFp c_mk = GetCachedPower(w_p.e, K);
|
||||
const DiyFp W = v.Normalize() * c_mk;
|
||||
DiyFp Wp = w_p * c_mk;
|
||||
DiyFp Wm = w_m * c_mk;
|
||||
Wm.f++;
|
||||
Wp.f--;
|
||||
DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K);
|
||||
}
|
||||
|
||||
inline char* WriteExponent(int K, char* buffer) {
|
||||
if (K < 0) {
|
||||
*buffer++ = '-';
|
||||
K = -K;
|
||||
}
|
||||
|
||||
if (K >= 100) {
|
||||
*buffer++ = static_cast<char>('0' + static_cast<char>(K / 100));
|
||||
K %= 100;
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
else if (K >= 10) {
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
else
|
||||
*buffer++ = static_cast<char>('0' + static_cast<char>(K));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) {
|
||||
const int kk = length + k; // 10^(kk-1) <= v < 10^kk
|
||||
|
||||
if (0 <= k && kk <= 21) {
|
||||
// 1234e7 -> 12340000000
|
||||
for (int i = length; i < kk; i++)
|
||||
buffer[i] = '0';
|
||||
buffer[kk] = '.';
|
||||
buffer[kk + 1] = '0';
|
||||
return &buffer[kk + 2];
|
||||
}
|
||||
else if (0 < kk && kk <= 21) {
|
||||
// 1234e-2 -> 12.34
|
||||
std::memmove(&buffer[kk + 1], &buffer[kk], static_cast<size_t>(length - kk));
|
||||
buffer[kk] = '.';
|
||||
if (0 > k + maxDecimalPlaces) {
|
||||
// When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1
|
||||
// Remove extra trailing zeros (at least one) after truncation.
|
||||
for (int i = kk + maxDecimalPlaces; i > kk + 1; i--)
|
||||
if (buffer[i] != '0')
|
||||
return &buffer[i + 1];
|
||||
return &buffer[kk + 2]; // Reserve one zero
|
||||
}
|
||||
else
|
||||
return &buffer[length + 1];
|
||||
}
|
||||
else if (-6 < kk && kk <= 0) {
|
||||
// 1234e-6 -> 0.001234
|
||||
const int offset = 2 - kk;
|
||||
std::memmove(&buffer[offset], &buffer[0], static_cast<size_t>(length));
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
for (int i = 2; i < offset; i++)
|
||||
buffer[i] = '0';
|
||||
if (length - kk > maxDecimalPlaces) {
|
||||
// When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1
|
||||
// Remove extra trailing zeros (at least one) after truncation.
|
||||
for (int i = maxDecimalPlaces + 1; i > 2; i--)
|
||||
if (buffer[i] != '0')
|
||||
return &buffer[i + 1];
|
||||
return &buffer[3]; // Reserve one zero
|
||||
}
|
||||
else
|
||||
return &buffer[length + offset];
|
||||
}
|
||||
else if (kk < -maxDecimalPlaces) {
|
||||
// Truncate to zero
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
buffer[2] = '0';
|
||||
return &buffer[3];
|
||||
}
|
||||
else if (length == 1) {
|
||||
// 1e30
|
||||
buffer[1] = 'e';
|
||||
return WriteExponent(kk - 1, &buffer[2]);
|
||||
}
|
||||
else {
|
||||
// 1234e30 -> 1.234e33
|
||||
std::memmove(&buffer[2], &buffer[1], static_cast<size_t>(length - 1));
|
||||
buffer[1] = '.';
|
||||
buffer[length + 1] = 'e';
|
||||
return WriteExponent(kk - 1, &buffer[0 + length + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
inline char *dtoa_milo(double value, char* buffer) {
|
||||
|
||||
Double d(value);
|
||||
if (d.IsZero()) {
|
||||
if (d.Sign())
|
||||
*buffer++ = '-'; // -0.0, Issue #289
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
buffer[2] = '0';
|
||||
return &buffer[3];
|
||||
#if 0
|
||||
} else if (d.IsInf()) {
|
||||
if (d.Sign())
|
||||
*buffer++ = '-';
|
||||
buffer[0] = 'i';
|
||||
buffer[1] = 'n';
|
||||
buffer[2] = 'f';
|
||||
return &buffer[3];
|
||||
} else if (d.IsNan()) {
|
||||
if (d.Sign())
|
||||
*buffer++ = '-';
|
||||
buffer[0] = 'n';
|
||||
buffer[1] = 'a';
|
||||
buffer[2] = 'n';
|
||||
return &buffer[3];
|
||||
#endif
|
||||
} else {
|
||||
if (value < 0) {
|
||||
*buffer++ = '-';
|
||||
value = -value;
|
||||
}
|
||||
int length, K;
|
||||
Grisu2(value, buffer, &length, &K);
|
||||
|
||||
int maxDecimalPlaces = 324;
|
||||
return Prettify(buffer, length, K, maxDecimalPlaces);
|
||||
}
|
||||
|
||||
if (std::fabs(value) < std::numeric_limits<double>::epsilon()) {
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
buffer[2] = '0';
|
||||
buffer[3] = '\0';
|
||||
}
|
||||
else {
|
||||
if (value < 0) {
|
||||
*buffer++ = '-';
|
||||
value = -value;
|
||||
}
|
||||
int length, K;
|
||||
Grisu2(value, buffer, &length, &K);
|
||||
Prettify(buffer, length, K);
|
||||
}
|
||||
}
|
||||
|
||||
4443
src/external/fast_float/fast_float.h
vendored
Normal file
4443
src/external/fast_float/fast_float.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1709
src/nonstd/string_view.hpp
Normal file
1709
src/nonstd/string_view.hpp
Normal file
File diff suppressed because it is too large
Load Diff
653
src/str-util.cc
653
src/str-util.cc
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
// Copyright 2023 - Present, Light Transport Entertainment, Inc.
|
||||
|
||||
#include <cmath>
|
||||
#include "str-util.hh"
|
||||
|
||||
#include "unicode-xid.hh"
|
||||
@@ -9,6 +11,18 @@
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
// external
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Weverything"
|
||||
#endif
|
||||
|
||||
#include "external/dragonbox/dragonbox_to_chars.h"
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
std::string buildEscapedAndQuotedStringForUSDA(const std::string &str) {
|
||||
@@ -711,392 +725,285 @@ std::string makeIdentifierValid(const std::string &str, bool is_utf8) {
|
||||
return s;
|
||||
}
|
||||
|
||||
double atof(const char *p) {
|
||||
// TODO: Use from_chars
|
||||
return std::atof(p);
|
||||
// ----------------------------------------------------------------------
|
||||
// based on fmtlib
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||
// MIT license.
|
||||
//
|
||||
|
||||
namespace internal {
|
||||
|
||||
// TOOD: Use builtin_clz insturction?
|
||||
// T = uint32 or uint64
|
||||
template <typename T>
|
||||
inline int count_digits(T n) {
|
||||
int count = 1;
|
||||
for (;;) {
|
||||
// Integer division is slow so do it for a group of four digits instead
|
||||
// of for every digit. The idea comes from the talk by Alexandrescu
|
||||
// "Three Optimization Tips for C++". See speed-test for a comparison.
|
||||
if (n < 10) return count;
|
||||
if (n < 100) return count + 1;
|
||||
if (n < 1000) return count + 2;
|
||||
if (n < 10000) return count + 3;
|
||||
n /= 10000u;
|
||||
count += 4;
|
||||
}
|
||||
}
|
||||
|
||||
double atof(const std::string &s) {
|
||||
return atof(s.c_str());
|
||||
// Converts value in the range [0, 100) to a string.
|
||||
// GCC generates slightly better code when value is pointer-size.
|
||||
inline auto digits2(size_t value) -> const char* {
|
||||
// Align data since unaligned access may be slower when crossing a
|
||||
// hardware-specific boundary.
|
||||
alignas(2) static const char data[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
return &data[value * 2];
|
||||
}
|
||||
|
||||
/*
|
||||
base64.cpp and base64.h
|
||||
|
||||
Copyright (C) 2004-2008 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
|
||||
*/
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
#pragma clang diagnostic ignored "-Wconversion"
|
||||
#endif
|
||||
|
||||
#ifdef __SSE2__
|
||||
#else
|
||||
static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __SSE2__
|
||||
#else
|
||||
// Fallback implementation (original)
|
||||
static std::string base64_encode_scalar(unsigned char const *bytes_to_encode,
|
||||
unsigned int in_len) {
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
const char *base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
while (in_len--) {
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] =
|
||||
((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] =
|
||||
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j < 3; j++) char_array_3[j] = '\0';
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] =
|
||||
((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] =
|
||||
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
|
||||
for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]];
|
||||
|
||||
while ((i++ < 3)) ret += '=';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
// SSE2-optimized base64 encode implementation
|
||||
#ifdef __SSE2__
|
||||
static std::string base64_encode_sse(unsigned char const *bytes_to_encode, unsigned int in_len) {
|
||||
if (in_len == 0) return std::string();
|
||||
|
||||
const char base64_chars[64] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
||||
};
|
||||
|
||||
// Calculate output size
|
||||
const size_t output_len = ((in_len + 2) / 3) * 4;
|
||||
std::string result;
|
||||
result.reserve(output_len);
|
||||
|
||||
size_t input_pos = 0;
|
||||
|
||||
// Process 12 bytes at a time using SSE2 (produces 16 base64 characters)
|
||||
while (input_pos + 12 <= in_len) {
|
||||
// Load 12 input bytes (will process as 4 groups of 3 bytes each)
|
||||
alignas(16) uint8_t input_block[16] = {0};
|
||||
|
||||
// Copy 12 bytes, leaving last 4 bytes as zero padding
|
||||
for (int i = 0; i < 12; i++) {
|
||||
input_block[i] = bytes_to_encode[input_pos + i];
|
||||
}
|
||||
|
||||
// Load input data into SSE register (currently unused but reserved for future vectorization)
|
||||
(void)_mm_load_si128(reinterpret_cast<const __m128i*>(input_block));
|
||||
|
||||
// Process 4 groups of 3 bytes each
|
||||
alignas(16) uint8_t output_indices[16];
|
||||
|
||||
for (int group = 0; group < 4; group++) {
|
||||
int base_idx = group * 3;
|
||||
|
||||
// Extract 3 bytes for this group
|
||||
uint8_t b0 = input_block[base_idx];
|
||||
uint8_t b1 = input_block[base_idx + 1];
|
||||
uint8_t b2 = input_block[base_idx + 2];
|
||||
|
||||
// Convert 3 bytes to 4 base64 indices
|
||||
output_indices[group * 4] = (b0 >> 2) & 0x3F;
|
||||
output_indices[group * 4 + 1] = ((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F);
|
||||
output_indices[group * 4 + 2] = ((b1 & 0x0F) << 2) | ((b2 >> 6) & 0x03);
|
||||
output_indices[group * 4 + 3] = b2 & 0x3F;
|
||||
}
|
||||
|
||||
// Convert indices to base64 characters using table lookup
|
||||
for (int i = 0; i < 16; i++) {
|
||||
result.push_back(base64_chars[output_indices[i]]);
|
||||
}
|
||||
|
||||
input_pos += 12;
|
||||
}
|
||||
|
||||
// Handle remaining bytes with scalar code
|
||||
while (input_pos + 3 <= in_len) {
|
||||
uint8_t b0 = bytes_to_encode[input_pos];
|
||||
uint8_t b1 = bytes_to_encode[input_pos + 1];
|
||||
uint8_t b2 = bytes_to_encode[input_pos + 2];
|
||||
|
||||
result.push_back(base64_chars[(b0 >> 2) & 0x3F]);
|
||||
result.push_back(base64_chars[((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F)]);
|
||||
result.push_back(base64_chars[((b1 & 0x0F) << 2) | ((b2 >> 6) & 0x03)]);
|
||||
result.push_back(base64_chars[b2 & 0x3F]);
|
||||
|
||||
input_pos += 3;
|
||||
}
|
||||
|
||||
// Handle final 1-2 bytes if present
|
||||
if (input_pos < in_len) {
|
||||
uint8_t b0 = bytes_to_encode[input_pos];
|
||||
uint8_t b1 = (input_pos + 1 < in_len) ? bytes_to_encode[input_pos + 1] : 0;
|
||||
|
||||
result.push_back(base64_chars[(b0 >> 2) & 0x3F]);
|
||||
result.push_back(base64_chars[((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F)]);
|
||||
|
||||
if (input_pos + 1 < in_len) {
|
||||
result.push_back(base64_chars[((b1 & 0x0F) << 2)]);
|
||||
} else {
|
||||
result.push_back('=');
|
||||
}
|
||||
result.push_back('=');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif // __SSE2__
|
||||
|
||||
std::string base64_encode(unsigned char const *bytes_to_encode,
|
||||
unsigned int in_len) {
|
||||
#ifdef __SSE2__
|
||||
// Use SSE2 optimized version if available
|
||||
return base64_encode_sse(bytes_to_encode, in_len);
|
||||
#else
|
||||
// Use scalar fallback implementation
|
||||
return base64_encode_scalar(bytes_to_encode, in_len);
|
||||
#endif
|
||||
// Writes a two-digit value to out.
|
||||
inline void write2digits(char* out, size_t value) {
|
||||
// if (!is_constant_evaluated() && std::is_same<Char, char>::value &&
|
||||
// !FMT_OPTIMIZE_SIZE) {
|
||||
// memcpy(out, digits2(value), 2);
|
||||
// return;
|
||||
// }
|
||||
*out++ = static_cast<char>('0' + value / 10);
|
||||
*out = static_cast<char>('0' + value % 10);
|
||||
}
|
||||
|
||||
// SSE2-optimized base64 decode implementation
|
||||
#ifdef __SSE2__
|
||||
static std::string base64_decode_sse(std::string const &encoded_string) {
|
||||
const size_t input_len = encoded_string.size();
|
||||
if (input_len == 0) return std::string();
|
||||
|
||||
// Lookup table for base64 decoding (256 entries, -1 for invalid chars)
|
||||
static const int8_t decode_table[256] = {
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-2,-1,-1,
|
||||
-1, 0, 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,-1, -1,-1,-1,-1,
|
||||
-1,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,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
|
||||
};
|
||||
|
||||
// Calculate output size (remove padding)
|
||||
size_t padding = 0;
|
||||
if (input_len >= 1 && encoded_string[input_len - 1] == '=') padding++;
|
||||
if (input_len >= 2 && encoded_string[input_len - 2] == '=') padding++;
|
||||
|
||||
const size_t output_len = (input_len * 3) / 4 - padding;
|
||||
std::string result;
|
||||
result.reserve(output_len);
|
||||
|
||||
const uint8_t* input = reinterpret_cast<const uint8_t*>(encoded_string.data());
|
||||
size_t input_pos = 0;
|
||||
|
||||
// Process 16 bytes at a time using SSE2
|
||||
while (input_pos + 16 <= input_len) {
|
||||
// Load 16 input bytes
|
||||
__m128i input_chunk = _mm_loadu_si128(reinterpret_cast<const __m128i*>(input + input_pos));
|
||||
|
||||
// Decode using lookup table (split into two 8-byte chunks for table lookup)
|
||||
alignas(16) uint8_t input_bytes[16];
|
||||
_mm_store_si128(reinterpret_cast<__m128i*>(input_bytes), input_chunk);
|
||||
|
||||
alignas(16) int8_t decoded[16];
|
||||
bool valid = true;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
decoded[i] = decode_table[input_bytes[i]];
|
||||
if (decoded[i] < 0 && input_bytes[i] != '=') {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) break; // Fall back to scalar processing for invalid chars
|
||||
|
||||
// Pack groups of 4 decoded bytes into 3 output bytes
|
||||
for (int group = 0; group < 4; group++) {
|
||||
if (input_pos + group * 4 + 3 >= input_len) break;
|
||||
|
||||
int base_idx = group * 4;
|
||||
if (decoded[base_idx] >= 0 && decoded[base_idx + 1] >= 0 &&
|
||||
decoded[base_idx + 2] >= 0 && decoded[base_idx + 3] >= 0) {
|
||||
|
||||
uint32_t combined = (static_cast<uint32_t>(decoded[base_idx]) << 18) |
|
||||
(static_cast<uint32_t>(decoded[base_idx + 1]) << 12) |
|
||||
(static_cast<uint32_t>(decoded[base_idx + 2]) << 6) |
|
||||
static_cast<uint32_t>(decoded[base_idx + 3]);
|
||||
|
||||
result.push_back(static_cast<char>((combined >> 16) & 0xFF));
|
||||
result.push_back(static_cast<char>((combined >> 8) & 0xFF));
|
||||
result.push_back(static_cast<char>(combined & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
input_pos += 16;
|
||||
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
|
||||
static char* write_exponent(int exp, char* out) {
|
||||
// FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
|
||||
if (exp < 0) {
|
||||
*out++ = '-';
|
||||
exp = -exp;
|
||||
} else {
|
||||
*out++ = '+';
|
||||
}
|
||||
|
||||
// Process remaining bytes with scalar code
|
||||
while (input_pos + 4 <= input_len) {
|
||||
uint8_t a = input[input_pos];
|
||||
uint8_t b = input[input_pos + 1];
|
||||
uint8_t c = input[input_pos + 2];
|
||||
uint8_t d = input[input_pos + 3];
|
||||
|
||||
if (a == '=' || b == '=') break;
|
||||
|
||||
int8_t da = decode_table[a];
|
||||
int8_t db = decode_table[b];
|
||||
int8_t dc = decode_table[c];
|
||||
int8_t dd = decode_table[d];
|
||||
|
||||
if (da < 0 || db < 0) break;
|
||||
|
||||
uint32_t combined = (static_cast<uint32_t>(da) << 18) |
|
||||
(static_cast<uint32_t>(db) << 12);
|
||||
|
||||
result.push_back(static_cast<char>((combined >> 16) & 0xFF));
|
||||
|
||||
if (c != '=' && dc >= 0) {
|
||||
combined |= static_cast<uint32_t>(dc) << 6;
|
||||
result.push_back(static_cast<char>((combined >> 8) & 0xFF));
|
||||
|
||||
if (d != '=' && dd >= 0) {
|
||||
combined |= static_cast<uint32_t>(dd);
|
||||
result.push_back(static_cast<char>(combined & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
input_pos += 4;
|
||||
auto uexp = static_cast<uint32_t>(exp);
|
||||
// if (is_constant_evaluated()) {
|
||||
// if (uexp < 10) *out++ = '0';
|
||||
// return format_decimal<Char>(out, uexp, count_digits(uexp));
|
||||
// }
|
||||
if (uexp >= 100u) {
|
||||
const char* top = digits2(uexp / 100);
|
||||
if (uexp >= 1000u) *out++ = top[0];
|
||||
*out++ = static_cast<char>(top[1]);
|
||||
uexp %= 100;
|
||||
}
|
||||
|
||||
return result;
|
||||
const char* d = digits2(uexp);
|
||||
*out++ = static_cast<char>(d[0]);
|
||||
*out++ = static_cast<char>(d[1]);
|
||||
return out;
|
||||
}
|
||||
#endif // __SSE2__
|
||||
|
||||
// Fallback implementation (original)
|
||||
std::string base64_decode(std::string const &encoded_string) {
|
||||
#ifdef __SSE2__
|
||||
// Use SSE2 optimized version if available
|
||||
return base64_decode_sse(encoded_string);
|
||||
#else
|
||||
// Original scalar implementation
|
||||
int in_len = static_cast<int>(encoded_string.size());
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
|
||||
const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
while (in_len-- && (encoded_string[in_] != '=') &&
|
||||
is_base64(encoded_string[in_])) {
|
||||
char_array_4[i++] = encoded_string[in_];
|
||||
in_++;
|
||||
if (i == 4) {
|
||||
for (i = 0; i < 4; i++)
|
||||
char_array_4[i] =
|
||||
static_cast<unsigned char>(base64_chars.find(char_array_4[i]));
|
||||
|
||||
char_array_3[0] =
|
||||
(char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] =
|
||||
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++) ret += char_array_3[i];
|
||||
i = 0;
|
||||
}
|
||||
inline char* fill_n(char* p, int n, char c) {
|
||||
for (int i = 0; i < n; i++, p++) {
|
||||
*p = c;
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j < 4; j++) char_array_4[j] = 0;
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
char_array_4[j] =
|
||||
static_cast<unsigned char>(base64_chars.find(char_array_4[j]));
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] =
|
||||
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||
}
|
||||
|
||||
return ret;
|
||||
#endif // __SSE2__
|
||||
return p;
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
/*
|
||||
-- end base64.cpp and base64.h
|
||||
*/
|
||||
inline void format_decimal_impl(char* out, uint64_t value, uint32_t size) {
|
||||
// FMT_ASSERT(size >= count_digits(value), "invalid digit count");
|
||||
unsigned n = size;
|
||||
while (value >= 100) {
|
||||
// Integer division is slow so do it for a group of two digits instead
|
||||
// of for every digit. The idea comes from the talk by Alexandrescu
|
||||
// "Three Optimization Tips for C++". See speed-test for a comparison.
|
||||
n -= 2;
|
||||
write2digits(out + n, static_cast<unsigned>(value % 100));
|
||||
value /= 100;
|
||||
}
|
||||
if (value >= 10) {
|
||||
n -= 2;
|
||||
write2digits(out + n, static_cast<unsigned>(value));
|
||||
} else {
|
||||
out[--n] = static_cast<char>('0' + value);
|
||||
}
|
||||
//return out + n;
|
||||
}
|
||||
|
||||
inline char* format_decimal(char* out, uint64_t value, uint32_t num_digits) {
|
||||
format_decimal_impl(out, value, num_digits);
|
||||
return out + num_digits;
|
||||
}
|
||||
|
||||
inline char* write_significand_e(char* out, uint64_t significand,
|
||||
int significand_size, int exponent) {
|
||||
out = format_decimal(out, significand, uint32_t(significand_size));
|
||||
return fill_n(out, exponent, '0');
|
||||
}
|
||||
|
||||
inline char* write_significand(char* out, uint64_t significand,
|
||||
int significand_size, int integral_size,
|
||||
char decimal_point) {
|
||||
if (!decimal_point) return format_decimal(out, significand, uint32_t(significand_size));
|
||||
out += significand_size + 1;
|
||||
char* end = out;
|
||||
int floating_size = significand_size - integral_size;
|
||||
for (int i = floating_size / 2; i > 0; --i) {
|
||||
out -= 2;
|
||||
write2digits(out, static_cast<std::size_t>(significand % 100));
|
||||
significand /= 100;
|
||||
}
|
||||
if (floating_size % 2 != 0) {
|
||||
*--out = static_cast<char>('0' + significand % 10);
|
||||
significand /= 10;
|
||||
}
|
||||
*--out = decimal_point;
|
||||
format_decimal(out - integral_size, significand, uint32_t(integral_size));
|
||||
return end;
|
||||
}
|
||||
|
||||
// Use dragonbox algorithm to print floating point value.
|
||||
// Use to_deciamal and do human-readable pretty printing for some value range(e.g. print 1e-3 as 0.001)
|
||||
//
|
||||
// exp_upper: (15 + 1) for double, (6+1) for float
|
||||
static char* dtoa_dragonbox(const double f, char* buf, int exp_upper = 16) {
|
||||
//const int spec_precision = -1; // unlimited
|
||||
|
||||
bool is_negative = std::signbit(f);
|
||||
|
||||
auto ret = jkj::dragonbox::to_decimal(f);
|
||||
|
||||
// print human-readable float for the value in range [1e-exp_lower, 1e+exp_upper]
|
||||
const int exp_lower = -4;
|
||||
char exp_char = 'e';
|
||||
char zero_char = '0';
|
||||
|
||||
auto significand = ret.significand;
|
||||
int significand_size = count_digits(significand);
|
||||
|
||||
//size_t size = size_t(significand_size) + (is_negative ? 1u : 0u);
|
||||
|
||||
int output_exp = ret.exponent + significand_size - 1;
|
||||
bool use_exp_format = (output_exp < exp_lower) || (output_exp >= exp_upper);
|
||||
|
||||
char decimal_point = '.';
|
||||
if (use_exp_format) {
|
||||
int num_zeros = 0;
|
||||
if (significand_size == 1) {
|
||||
decimal_point = '\0';
|
||||
}
|
||||
//auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp;
|
||||
//int exp_digits = 2;
|
||||
//if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3;
|
||||
|
||||
//size += (decimal_point ? 1u : 0u) + 2u + size_t(exp_digits);
|
||||
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
buf =
|
||||
write_significand(buf, significand, significand_size, 1, decimal_point);
|
||||
|
||||
if (num_zeros > 0) buf = fill_n(buf, num_zeros, zero_char);
|
||||
*buf++ = exp_char;
|
||||
return write_exponent(output_exp, buf);
|
||||
}
|
||||
|
||||
int exp = ret.exponent + significand_size;
|
||||
if (ret.exponent >= 0) {
|
||||
// 1234e5 -> 123400000[.0+]
|
||||
//size += static_cast<size_t>(ret.exponent);
|
||||
//int num_zeros = spec_precision - exp;
|
||||
// abort_fuzzing_if(num_zeros > 5000);
|
||||
// if (specs.alt()) {
|
||||
// ++size;
|
||||
// if (num_zeros <= 0 && specs.type() != presentation_type::fixed)
|
||||
// num_zeros = 0;
|
||||
// if (num_zeros > 0) size += size_t(num_zeros);
|
||||
// }
|
||||
// auto grouping = Grouping(loc, specs.localized());
|
||||
// size += size_t(grouping.count_separators(exp));
|
||||
// return write_padded<Char, align::right>(out, specs, size, [&](iterator
|
||||
// it) {
|
||||
// if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// it = write_significand<Char>(it, significand, significand_size,
|
||||
// f.exponent, grouping);
|
||||
// if (!specs.alt()) return it;
|
||||
// *it++ = decimal_point;
|
||||
// return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
|
||||
// });
|
||||
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
return write_significand_e(buf, significand, significand_size,
|
||||
ret.exponent);
|
||||
|
||||
} else if (exp > 0) {
|
||||
// 1234e-2 -> 12.34[0+]
|
||||
// int num_zeros = specs.alt() ? spec_precision - significand_size : 0;
|
||||
// size += 1 + static_cast<unsigned>(max_of(num_zeros, 0));
|
||||
//size += 1;
|
||||
// auto grouping = Grouping(loc, specs.localized());
|
||||
// size += size_t(grouping.count_separators(exp));
|
||||
// return write_padded<Char, align::right>(out, specs, size, [&](iterator
|
||||
// it) {
|
||||
// if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// it = write_significand(it, significand, significand_size, exp,
|
||||
// decimal_point, grouping);
|
||||
// return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
|
||||
// });
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
return write_significand(buf, significand, significand_size, exp,
|
||||
decimal_point);
|
||||
}
|
||||
// 1234e-6 -> 0.001234
|
||||
int num_zeros = -exp;
|
||||
// if (significand_size == 0 && specs.precision >= 0 &&
|
||||
// specs.precision < num_zeros) {
|
||||
// num_zeros = spec_precision;
|
||||
// }
|
||||
bool pointy = num_zeros != 0 || significand_size != 0; // || specs.alt();
|
||||
//size += 1u + (pointy ? 1u : 0u) + size_t(num_zeros);
|
||||
// return write_padded<Char, align::right>(out, specs, size, [&](iterator it)
|
||||
// {
|
||||
// if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// *it++ = zero;
|
||||
// if (!pointy) return it;
|
||||
// *it++ = decimal_point;
|
||||
// it = detail::fill_n(it, num_zeros, zero);
|
||||
// return write_significand<Char>(it, significand, significand_size);
|
||||
// });
|
||||
|
||||
if (is_negative) {
|
||||
*buf++ = '-';
|
||||
}
|
||||
|
||||
*buf++ = zero_char;
|
||||
|
||||
if (!pointy) return buf;
|
||||
*buf++ = decimal_point;
|
||||
buf = fill_n(buf, num_zeros, zero_char);
|
||||
|
||||
return format_decimal(buf, significand, uint32_t(significand_size));
|
||||
}
|
||||
|
||||
static char* dtoa_dragonbox(const float f, char* buf) {
|
||||
return dtoa_dragonbox(double(f), buf, 7);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
char *dtoa(float f, char *buffer) {
|
||||
return internal::dtoa_dragonbox(f, buffer);
|
||||
}
|
||||
|
||||
char *dtoa(double f, char *buffer) {
|
||||
return internal::dtoa_dragonbox(f, buffer);
|
||||
}
|
||||
|
||||
} // namespace tinyusdz
|
||||
|
||||
@@ -357,6 +357,17 @@ inline std::string codepoint_to_utf8(uint32_t code) {
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// float/double to string
|
||||
// Currently tinyusdz uses dragonbox algorithm
|
||||
//
|
||||
// buffer must be at least 25 bytes.
|
||||
// filled string is not null-terminated.
|
||||
// (Use *(dtoa(f, buf)) = '\0' if you want null-terminated string)
|
||||
//
|
||||
char *dtoa(float f, char *buf);
|
||||
char *dtoa(double f, char *buf);
|
||||
|
||||
#if 0 // TODO
|
||||
///
|
||||
/// Convert UTF-8 code to UTF-8 char
|
||||
|
||||
114
src/tiny-container.hh
Normal file
114
src/tiny-container.hh
Normal file
@@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2024 - Present : Syoyo Fujita
|
||||
//
|
||||
|
||||
// Simple stack container class for custom vector/string.
|
||||
// Inspired from
|
||||
// - https://github.com/p-ranav/small_vector
|
||||
// - https://chromium.googlesource.com/chromium/chromium/+/master/base/stack_container.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
template<typename T, std::size_t Size>
|
||||
class StackAllocator : public std::allocator<T> {
|
||||
|
||||
typedef typename std::allocator<T>::pointer pointer;
|
||||
typedef typename std::allocator<T>::size_type size_type;
|
||||
|
||||
struct StackBuf
|
||||
{
|
||||
T *data() { return reinterpret_cast<T *>(_buf); }
|
||||
const T *data() const {
|
||||
return reinterpret_cast<const T *>(_buf);
|
||||
}
|
||||
|
||||
char _buf[sizeof(T[Size])];
|
||||
bool use_stack{false};
|
||||
};
|
||||
|
||||
pointer allocate(size_type n, void *hint = nullptr) {
|
||||
if (_buf && !_buf->use_stack && (n <= Size)) {
|
||||
_buf->use_stack = true;
|
||||
return _buf->data();
|
||||
} else {
|
||||
std::allocator<T>::allocate(n, hint);
|
||||
}
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type sz) {
|
||||
if (_buf && (p == _buf->data())) {
|
||||
_buf->use_stack = false;
|
||||
} else {
|
||||
std::allocator<T>::deallocate(p, sz);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
StackBuf *_buf{nullptr};
|
||||
};
|
||||
|
||||
// T : container type
|
||||
// N : capacity
|
||||
template<typename T, size_t Size>
|
||||
class StackContainer {
|
||||
public:
|
||||
|
||||
using Allocator = StackAllocator<T, Size>;
|
||||
|
||||
StackContainer() : _allocator(&_stack), _container(_allocator) {
|
||||
_container.reserve(Size);
|
||||
}
|
||||
|
||||
T &get() { return _container; }
|
||||
const T &get() const { return _container; }
|
||||
|
||||
T *operator->() { return &_container; }
|
||||
const T *operator->() const { return &_container; }
|
||||
|
||||
protected:
|
||||
typename Allocator::StackBuf _stack;
|
||||
|
||||
Allocator _allocator;
|
||||
T _container;
|
||||
|
||||
// disallow copy and assign.
|
||||
StackContainer(const StackContainer &) = delete;
|
||||
void operator=(const StackContainer &) = delete;
|
||||
|
||||
|
||||
};
|
||||
|
||||
template <typename T, size_t Size>
|
||||
class StackVector : public StackContainer<std::vector<T, StackAllocator<T, Size>>, Size> {
|
||||
public:
|
||||
StackVector() : StackContainer<std::vector<T, StackAllocator<T, Size>>, Size>() {}
|
||||
|
||||
|
||||
StackVector(const StackVector<T, Size> &rhs) : StackContainer<std::vector<T, StackAllocator<T, Size>>, Size>() {
|
||||
this->get().assign(rhs->begin(), rhs->end());
|
||||
}
|
||||
|
||||
StackVector<T, Size> &operator=(const StackVector<T, Size> &rhs) {
|
||||
this->get().assign(rhs->begin(), rhs->end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
T &operator[](size_t i) { return this->get().operator[](i); }
|
||||
const T &operator[](size_t i) const {
|
||||
return this->get().operator[](i);
|
||||
}
|
||||
|
||||
// TODO: lvalue ctor
|
||||
};
|
||||
|
||||
|
||||
} // namespace tinyusdz
|
||||
|
||||
|
||||
|
||||
765
src/tiny-string.cc
Normal file
765
src/tiny-string.cc
Normal file
@@ -0,0 +1,765 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2024-Present Light Transport Entertainment Inc.
|
||||
#include "tiny-string.hh"
|
||||
|
||||
#if defined(TINYUSDZ_USE_THREAD)
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Weverything"
|
||||
#endif
|
||||
|
||||
#include "external/fast_float/include/fast_float/fast_float.h"
|
||||
|
||||
#define nssv_CONFIG_USR_SV_OPERATOR 0
|
||||
|
||||
// TODO(syoyo): Use C++17 std::string_view when compiled with C++-17 compiler
|
||||
|
||||
// clang and gcc
|
||||
#if defined(__EXCEPTIONS) || defined(__cpp_exceptions)
|
||||
|
||||
#ifdef nsel_CONFIG_NO_EXCEPTIONS
|
||||
#undef nsel_CONFIG_NO_EXCEPTIONS
|
||||
#endif
|
||||
#ifdef nssv_CONFIG_NO_EXCEPTIONS
|
||||
#undef nssv_CONFIG_NO_EXCEPTIONS
|
||||
#endif
|
||||
|
||||
#define nsel_CONFIG_NO_EXCEPTIONS 0
|
||||
#define nssv_CONFIG_NO_EXCEPTIONS 0
|
||||
#else
|
||||
// -fno-exceptions
|
||||
#if !defined(nsel_CONFIG_NO_EXCEPTIONS)
|
||||
#define nsel_CONFIG_NO_EXCEPTIONS 1
|
||||
#endif
|
||||
|
||||
#define nssv_CONFIG_NO_EXCEPTIONS 1
|
||||
#endif
|
||||
#include "nonstd/string_view.hpp"
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
namespace str {
|
||||
|
||||
struct Lexer {
|
||||
|
||||
void skip_whitespaces() {
|
||||
|
||||
while (!eof()) {
|
||||
|
||||
char s = *curr;
|
||||
if ((s == ' ') || (s == '\t') || (s == '\f') || (s == '\n') || (s == '\r') || (s == '\v')) {
|
||||
curr++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool skip_until_delim_or_close_paren(const char delim, const char close_paren) {
|
||||
|
||||
while (!eof()) {
|
||||
|
||||
char s = *curr;
|
||||
if ((s == delim) || (s == close_paren)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
curr++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool char1(char *result) {
|
||||
if (eof()) {
|
||||
return false;
|
||||
}
|
||||
*result = *curr;
|
||||
curr++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool look_char1(char *result) {
|
||||
if (eof()) {
|
||||
return false;
|
||||
}
|
||||
*result = *curr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool consume_char1() {
|
||||
if (eof()) {
|
||||
return false;
|
||||
}
|
||||
curr++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool eof() const {
|
||||
return (curr >= p_end);
|
||||
}
|
||||
|
||||
inline bool unwind_char1() {
|
||||
if (curr <= p_begin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curr--;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lex_float(uint16_t &len, bool &truncated) {
|
||||
|
||||
// truncate too large fp string
|
||||
// (e.g. "0.100000010000000100000010000..."
|
||||
constexpr size_t n_trunc_chars = 256; // 65535 at max.
|
||||
|
||||
size_t n = 0;
|
||||
bool has_sign = false;
|
||||
bool has_exponential = false;
|
||||
bool has_dot = false;
|
||||
|
||||
// oneOf [0-9, eE, -+]
|
||||
while (!eof() || (n < n_trunc_chars)) {
|
||||
char c;
|
||||
look_char1(&c);
|
||||
if ((c == '-') || (c == '+')) {
|
||||
if (has_sign) {
|
||||
return false;
|
||||
}
|
||||
has_sign = true;
|
||||
} else if (c == '.') {
|
||||
if (has_dot) {
|
||||
return false;
|
||||
}
|
||||
has_dot = true;
|
||||
} else if ((c == 'e') || (c == 'E')) {
|
||||
if (has_exponential) {
|
||||
return false;
|
||||
}
|
||||
has_exponential = true;
|
||||
} else if ((c >= '0') && (c <= '9')) {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
consume_char1();
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
truncated = (n >= n_trunc_chars);
|
||||
|
||||
len = uint16_t(n);
|
||||
return true;
|
||||
}
|
||||
|
||||
void push_error(const std::string &msg) {
|
||||
err_ += msg + "\n";
|
||||
}
|
||||
|
||||
std::string get_error() const {
|
||||
return err_;
|
||||
}
|
||||
|
||||
const char *p_begin{nullptr};
|
||||
const char *p_end{nullptr};
|
||||
|
||||
const char *curr{nullptr};
|
||||
|
||||
private:
|
||||
std::string err_;
|
||||
};
|
||||
|
||||
|
||||
struct fp_lex_span
|
||||
{
|
||||
const char *p_begin{nullptr};
|
||||
uint16_t length{0};
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct vec_lex_span
|
||||
{
|
||||
fp_lex_span vspans[N];
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
#if 0
|
||||
// '[' + fp0 + "," + fp1 + ", " ... ']'
|
||||
// allow_delim_at_last is true: '[' + fp0 + "," + fp1 + ", " ... "," + ']'
|
||||
static bool lex_float_array(
|
||||
const char *p_begin,
|
||||
const char *p_end,
|
||||
std::vector<fp_lex_span> &result,
|
||||
std::string &err,
|
||||
const bool allow_delim_at_last = true,
|
||||
const char delim = ',',
|
||||
const char open_paren = '[',
|
||||
const char close_paren = ']') {
|
||||
|
||||
if (p_begin >= p_end) {
|
||||
err = "Invalid input\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Lexer lexer;
|
||||
lexer.p_begin = p_begin;
|
||||
lexer.p_end = p_end;
|
||||
lexer.curr = p_begin;
|
||||
|
||||
|
||||
// '['
|
||||
{
|
||||
char c;
|
||||
if (!lexer.char1(&c)) {
|
||||
err = "Input too short.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c != open_paren) {
|
||||
err = "Input does not begin with open parenthesis character.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
lexer.skip_whitespaces();
|
||||
|
||||
while (!lexer.eof()) {
|
||||
|
||||
bool prev_is_delim = false;
|
||||
|
||||
// is ','?
|
||||
{
|
||||
char c;
|
||||
if (!lexer.look_char1(&c)) {
|
||||
lexer.push_error("Invalid character found.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == delim) {
|
||||
// Array element starts with delimiter, e.g. '[ ,'
|
||||
if (result.empty()) {
|
||||
lexer.push_error("Array element starts with the delimiter character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
prev_is_delim = true;
|
||||
lexer.consume_char1();
|
||||
}
|
||||
|
||||
lexer.skip_whitespaces();
|
||||
}
|
||||
|
||||
// is ']'?
|
||||
{
|
||||
char c;
|
||||
if (!lexer.look_char1(&c)) {
|
||||
lexer.push_error("Failed to read a character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == close_paren) {
|
||||
if (prev_is_delim) {
|
||||
if (allow_delim_at_last) {
|
||||
// ok
|
||||
return true;
|
||||
} else {
|
||||
lexer.push_error("Delimiter character is not allowed before the closing parenthesis character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// ok
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fp_lex_span sp;
|
||||
sp.p_begin = lexer.curr;
|
||||
|
||||
uint16_t length{0};
|
||||
bool truncated{false};
|
||||
|
||||
if (!lexer.lex_float(length, truncated)) {
|
||||
lexer.push_error("Input is not a floating point literal.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
sp.length = length;
|
||||
|
||||
if (truncated) {
|
||||
// skip until encountering delim or close_paren.
|
||||
if (!lexer.skip_until_delim_or_close_paren(delim, close_paren)) {
|
||||
lexer.push_error("Failed to seek to delimiter or closing parenthesis character.");
|
||||
err = lexer.get_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result.emplace_back(std::move(sp));
|
||||
|
||||
lexer.skip_whitespaces();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace internal
|
||||
|
||||
bool parse_int(const tstring_view &sv, int32_t *ret) {
|
||||
const char* str = sv.c_str();
|
||||
size_t len = sv.size();
|
||||
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool negative = false;
|
||||
size_t start = 0;
|
||||
|
||||
if (str[0] == '-') {
|
||||
negative = true;
|
||||
start = 1;
|
||||
} else if (str[0] == '+') {
|
||||
start = 1;
|
||||
}
|
||||
|
||||
if (start >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t result = 0;
|
||||
for (size_t i = start; i < len; i++) {
|
||||
if (str[i] < '0' || str[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
result = result * 10 + (str[i] - '0');
|
||||
|
||||
// Check for overflow
|
||||
if (negative && result > static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1) {
|
||||
return false;
|
||||
}
|
||||
if (!negative && result > std::numeric_limits<int32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = negative ? -static_cast<int32_t>(result) : static_cast<int32_t>(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_int64(const tstring_view &sv, int64_t *ret) {
|
||||
const char* str = sv.c_str();
|
||||
size_t len = sv.size();
|
||||
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool negative = false;
|
||||
size_t start = 0;
|
||||
|
||||
if (str[0] == '-') {
|
||||
negative = true;
|
||||
start = 1;
|
||||
} else if (str[0] == '+') {
|
||||
start = 1;
|
||||
}
|
||||
|
||||
if (start >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t result = 0;
|
||||
for (size_t i = start; i < len; i++) {
|
||||
if (str[i] < '0' || str[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
result = result * 10ull + uint64_t(str[i] - '0');
|
||||
|
||||
// Check for overflow
|
||||
if (negative && result > static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1) {
|
||||
return false;
|
||||
}
|
||||
if (!negative && result > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = negative ? -static_cast<int64_t>(result) : static_cast<int64_t>(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_uint(const tstring_view &sv, uint32_t *ret) {
|
||||
const char* str = sv.c_str();
|
||||
size_t len = sv.size();
|
||||
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
if (str[0] == '+') {
|
||||
start = 1;
|
||||
}
|
||||
|
||||
if (start >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t result = 0;
|
||||
for (size_t i = start; i < len; i++) {
|
||||
if (str[i] < '0' || str[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
result = result * 10 + uint64_t(str[i] - '0');
|
||||
|
||||
// Check for overflow
|
||||
if (result > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = static_cast<uint32_t>(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_uint64(const tstring_view &sv, uint64_t *ret) {
|
||||
const char* str = sv.c_str();
|
||||
size_t len = sv.size();
|
||||
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
if (str[0] == '+') {
|
||||
start = 1;
|
||||
}
|
||||
|
||||
if (start >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t result = 0;
|
||||
for (size_t i = start; i < len; i++) {
|
||||
if (str[i] < '0' || str[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for overflow before multiplication
|
||||
if (result > (std::numeric_limits<uint64_t>::max() - uint64_t(str[i] - '0')) / 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = result * 10 + uint64_t(str[i] - '0');
|
||||
}
|
||||
|
||||
*ret = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_float(const tstring_view &sv, float *ret) {
|
||||
auto result = fast_float::from_chars(sv.c_str(), sv.c_str() + sv.size(), *ret);
|
||||
return result.ec == std::errc{};
|
||||
}
|
||||
|
||||
bool parse_double(const tstring_view &sv, double *ret) {
|
||||
auto result = fast_float::from_chars(sv.c_str(), sv.c_str() + sv.size(), *ret);
|
||||
return result.ec == std::errc{};
|
||||
}
|
||||
|
||||
bool parse_float_arary(const tstring_view &sv, std::vector<float> *result, const char delimiter) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->clear();
|
||||
|
||||
if (sv.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *p = sv.c_str();
|
||||
const char *end = p + sv.size();
|
||||
|
||||
// Skip leading whitespace and '['
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p >= end || *p != '[') {
|
||||
return false;
|
||||
}
|
||||
p++; // skip '['
|
||||
|
||||
// Skip whitespace after '['
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Handle empty array
|
||||
if (p < end && *p == ']') {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (p < end) {
|
||||
// Skip whitespace
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p >= end) break;
|
||||
|
||||
// Check for closing bracket
|
||||
if (*p == ']') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the end of the number
|
||||
const char *num_start = p;
|
||||
while (p < end && *p != delimiter && *p != ']' &&
|
||||
*p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p == num_start) {
|
||||
return false; // No number found
|
||||
}
|
||||
|
||||
// Parse the number
|
||||
float value;
|
||||
auto parse_result = fast_float::from_chars(num_start, p, value);
|
||||
if (parse_result.ec != std::errc{}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->push_back(value);
|
||||
|
||||
// Skip whitespace after number
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Handle delimiter or end
|
||||
if (p < end && *p == delimiter) {
|
||||
p++; // skip delimiter
|
||||
} else if (p < end && *p == ']') {
|
||||
break; // end of array
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_double_arary(const tstring_view &sv, std::vector<double> *result, const char delimiter) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->clear();
|
||||
|
||||
if (sv.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *p = sv.c_str();
|
||||
const char *end = p + sv.size();
|
||||
|
||||
// Skip leading whitespace and '['
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p >= end || *p != '[') {
|
||||
return false;
|
||||
}
|
||||
p++; // skip '['
|
||||
|
||||
// Skip whitespace after '['
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Handle empty array
|
||||
if (p < end && *p == ']') {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (p < end) {
|
||||
// Skip whitespace
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p >= end) break;
|
||||
|
||||
// Check for closing bracket
|
||||
if (*p == ']') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the end of the number
|
||||
const char *num_start = p;
|
||||
while (p < end && *p != delimiter && *p != ']' &&
|
||||
*p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p == num_start) {
|
||||
return false; // No number found
|
||||
}
|
||||
|
||||
// Parse the number
|
||||
double value;
|
||||
auto parse_result = fast_float::from_chars(num_start, p, value);
|
||||
if (parse_result.ec != std::errc{}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->push_back(value);
|
||||
|
||||
// Skip whitespace after number
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Handle delimiter or end
|
||||
if (p < end && *p == delimiter) {
|
||||
p++; // skip delimiter
|
||||
} else if (p < end && *p == ']') {
|
||||
break; // end of array
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_int_arary(const tstring_view &sv, std::vector<int32_t> *result, const char delimiter) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->clear();
|
||||
|
||||
if (sv.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *p = sv.c_str();
|
||||
const char *end = p + sv.size();
|
||||
|
||||
// Skip leading whitespace and '['
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p >= end || *p != '[') {
|
||||
return false;
|
||||
}
|
||||
p++; // skip '['
|
||||
|
||||
// Skip whitespace after '['
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Handle empty array
|
||||
if (p < end && *p == ']') {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (p < end) {
|
||||
// Skip whitespace
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p >= end) break;
|
||||
|
||||
// Check for closing bracket
|
||||
if (*p == ']') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the end of the number
|
||||
const char *num_start = p;
|
||||
while (p < end && *p != delimiter && *p != ']' &&
|
||||
*p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p <= num_start) {
|
||||
return false; // No number found
|
||||
}
|
||||
|
||||
// Parse the number
|
||||
int32_t value;
|
||||
tstring_view num_view(num_start);
|
||||
// Create a temporary view with the correct length
|
||||
size_t num_len = size_t(p - num_start);
|
||||
std::string num_str(num_start, num_len);
|
||||
tstring_view temp_view(num_str.c_str());
|
||||
if (!parse_int(temp_view, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->push_back(value);
|
||||
|
||||
// Skip whitespace after number
|
||||
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Handle delimiter or end
|
||||
if (p < end && *p == delimiter) {
|
||||
p++; // skip delimiter
|
||||
} else if (p < end && *p == ']') {
|
||||
break; // end of array
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool print_float_array(std::vector<float> &v,
|
||||
std::string &dst, const char delimiter) {
|
||||
|
||||
// TODO
|
||||
(void)v;
|
||||
(void)dst;
|
||||
(void)delimiter;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace tinyusdz
|
||||
361
src/tiny-string.hh
Normal file
361
src/tiny-string.hh
Normal file
@@ -0,0 +1,361 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2024-Present Syoyo Fujita.
|
||||
|
||||
///
|
||||
/// Simple but fast string library.
|
||||
///
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "tiny-container.hh"
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
// default: Up to 1G char
|
||||
static size_t strlen(const char *s, size_t max_len = 1024u*1024u*1024u) {
|
||||
if (!s) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
while(i < max_len) {
|
||||
if (s[i] == '\0') {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
template<size_t N = 8>
|
||||
struct tstring_n {
|
||||
// 8 = enough size to store pointer address
|
||||
static_assert(N >= 8, "N must be 8 or larger.");
|
||||
|
||||
public:
|
||||
|
||||
tstring_n() {}
|
||||
~tstring_n() {
|
||||
_delete_string();
|
||||
}
|
||||
|
||||
tstring_n(const char *s) {
|
||||
_copy_string(s);
|
||||
}
|
||||
|
||||
tstring_n(const std::string &s) : tstring_n(s.c_str()) {
|
||||
}
|
||||
|
||||
tstring_n(const tstring_n &rhs) : tstring_n(rhs.c_str()) {
|
||||
}
|
||||
|
||||
tstring_n(tstring_n &&rhs) {
|
||||
|
||||
_delete_string();
|
||||
|
||||
_u = std::exchange(rhs._u, nullptr);
|
||||
_len = std::exchange(rhs._len, 0);
|
||||
}
|
||||
|
||||
tstring_n &operator=(const tstring_n &rhs) {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
_copy_string(rhs.c_str());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
tstring_n &operator=(tstring_n &&rhs) noexcept {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
_delete_string();
|
||||
|
||||
_u = std::exchange(rhs._u, nullptr);
|
||||
_len = std::exchange(rhs._len, 0);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const char *c_str() const {
|
||||
if (_len > N) {
|
||||
return reinterpret_cast<const char *>(_u._ptr);
|
||||
} else {
|
||||
return reinterpret_cast<const char *>(_u._buf);
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _len;
|
||||
}
|
||||
|
||||
std::string to_std_string() {
|
||||
const char *p;
|
||||
if (_len > N) {
|
||||
p = reinterpret_cast<const char *>(_u._ptr);
|
||||
} else {
|
||||
p = reinterpret_cast<const char *>(_u.buf);
|
||||
}
|
||||
std::string s(p, _len);
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
void _delete_string() {
|
||||
if ((_len > N) && (_u._ptr)) {
|
||||
delete[] _u._ptr;
|
||||
}
|
||||
memset(_u._buf, 0, 8);
|
||||
_len = 0;
|
||||
}
|
||||
|
||||
void _copy_string(const char *s) {
|
||||
|
||||
_delete_string();
|
||||
|
||||
//if (_len > 0) {
|
||||
// if (_len >= N) {
|
||||
// //char *p = reinterpret_cast<char *>(_buf);
|
||||
// //delete[] p;
|
||||
// //memset(_buf, 0, 8);
|
||||
// }
|
||||
//}
|
||||
|
||||
//char *dst = reinterpret_cast<char *>(_buf);
|
||||
|
||||
_len = strlen(s);
|
||||
if (_len > N) {
|
||||
char *dst = new char[_len+1];
|
||||
memcpy(dst, s, _len);
|
||||
dst[_len] = '\0';
|
||||
|
||||
_u._ptr = dst;
|
||||
} else {
|
||||
memcpy(_u._buf, s, _len);
|
||||
_u._buf[_len] = '\0';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: Ues custom vector class.
|
||||
union {
|
||||
char _buf[N+1]{};
|
||||
char *_ptr;
|
||||
} _u;
|
||||
|
||||
size_t _len{0};
|
||||
};
|
||||
|
||||
using tstring = tstring_n<>;
|
||||
|
||||
// just a retain the pointer address.
|
||||
struct tstring_view {
|
||||
public:
|
||||
|
||||
constexpr tstring_view() {}
|
||||
~tstring_view() {
|
||||
_s = nullptr;
|
||||
}
|
||||
|
||||
tstring_view(const char *s) {
|
||||
_len = strlen(s);
|
||||
_s = s;
|
||||
}
|
||||
|
||||
constexpr tstring_view(const char *s, size_t n) {
|
||||
_len = n;
|
||||
_s = s;
|
||||
}
|
||||
|
||||
tstring_view(const std::string &s) : tstring_view(s.c_str()) {
|
||||
}
|
||||
|
||||
tstring_view(const tstring &s) : tstring_view(s.c_str()) {
|
||||
}
|
||||
|
||||
bool operator==(const tstring_view &rhs) {
|
||||
if (_len != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < _len; i++) {
|
||||
if (_s[i] != rhs.c_str()[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator!=(const tstring_view &rhs) {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
const char *c_str() const {
|
||||
return _s;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _len;
|
||||
}
|
||||
|
||||
// C++20-like API
|
||||
bool starts_with( const tstring_view &sv ) const noexcept
|
||||
{
|
||||
size_t sv_size = sv.size();
|
||||
|
||||
if (_len < sv_size) {
|
||||
return false;
|
||||
}
|
||||
const char *sv_str = sv.c_str();
|
||||
|
||||
for (size_t i = 0; i < sv_size; i++) {
|
||||
if (_s[i] != sv_str[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool starts_with( const char *s) const {
|
||||
size_t s_size = strlen(s);
|
||||
|
||||
if (_len < s_size) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < s_size; i++) {
|
||||
if (_s[i] != s[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ends_with( const tstring_view &sv ) const noexcept
|
||||
{
|
||||
size_t sv_size = sv.size();
|
||||
|
||||
if (_len < sv_size) {
|
||||
return false;
|
||||
}
|
||||
const char *sv_str = sv.c_str();
|
||||
|
||||
for (size_t i = 0; i < sv_size; i++) {
|
||||
if (_s[_len - i - 1] != sv_str[sv_size - i - 1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ends_with( const char *s) const {
|
||||
size_t s_size = strlen(s);
|
||||
|
||||
if (_len < s_size) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < s_size; i++) {
|
||||
if (_s[_len - i - 1] != s[s_size - i - 1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool contains( const char *s) const {
|
||||
size_t s_size = strlen(s);
|
||||
if (s_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_len < s_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t i_s{0};
|
||||
for (size_t i = 0; i < _len; i++) {
|
||||
if (_s[i] == s[0]) {
|
||||
i_s = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_len < i_s + s_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = i_s; i < i_s + s_size; i++) {
|
||||
if (_s[i] != s[i - i_s]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool contains( const tstring_view &sv) const {
|
||||
return contains(sv.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: Ues custom vector class.
|
||||
const char *_s{nullptr}; // end with '\0'
|
||||
size_t _len{0};
|
||||
};
|
||||
|
||||
// Simple std::ostringstream like class
|
||||
class tostringstream
|
||||
{
|
||||
public:
|
||||
|
||||
tostringstream &operator<<( const tstring &str );
|
||||
tostringstream &operator<<( const tstring_view &str );
|
||||
|
||||
void write(const char *p, const size_t n);
|
||||
|
||||
uint64_t size() const {
|
||||
return binary_.size();
|
||||
}
|
||||
|
||||
std::string str() const;
|
||||
tstring tstr() const;
|
||||
|
||||
|
||||
const char *data() const { return binary_.data(); }
|
||||
|
||||
private:
|
||||
const std::vector<char> binary_;
|
||||
mutable uint64_t idx_{0};
|
||||
};
|
||||
|
||||
namespace str {
|
||||
|
||||
bool parse_int(const tstring_view &sv, int32_t *ret);
|
||||
bool parse_int64(const tstring_view &sv, int64_t *ret);
|
||||
|
||||
bool parse_uint(const tstring_view &sv, uint32_t *ret);
|
||||
bool parse_uint64(const tstring_view &sv, uint64_t *ret);
|
||||
|
||||
bool parse_float(const tstring_view &sv, float *ret);
|
||||
bool parse_double(const tstring_view &sv, double *ret);
|
||||
|
||||
bool parse_int_arary(const tstring_view &sv, std::vector<int32_t> *result, const char delimiter = ',');
|
||||
bool parse_float_arary(const tstring_view &sv, std::vector<float> *result, const char delimiter = ',');
|
||||
bool parse_double_arary(const tstring_view &sv, std::vector<double> *result, const char delimiter = ',');
|
||||
|
||||
bool print_float_array(std::vector<float> &v,
|
||||
std::string &dst, const char delimiter = ',');
|
||||
|
||||
}
|
||||
|
||||
} // namespace tinyusdz
|
||||
@@ -78,6 +78,31 @@ namespace tinyusdz {
|
||||
}
|
||||
//#define PushWarn(s) if (warn) { (*warn) += s; }
|
||||
|
||||
// Helper function to format magic header bytes for error messages
|
||||
static std::string FormatMagicHeader(const uint8_t *addr, const size_t length, size_t max_bytes = 16) {
|
||||
if (!addr || length == 0) {
|
||||
return "(empty)";
|
||||
}
|
||||
|
||||
std::string result = "0x";
|
||||
size_t bytes_to_show = std::min(length, max_bytes);
|
||||
|
||||
for (size_t i = 0; i < bytes_to_show; i++) {
|
||||
char hex[3];
|
||||
snprintf(hex, sizeof(hex), "%02x", addr[i]);
|
||||
result += hex;
|
||||
if (i < bytes_to_show - 1) {
|
||||
result += " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (length > max_bytes) {
|
||||
result += "...";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LoadUSDCFromMemory(const uint8_t *addr, const size_t length,
|
||||
const std::string &filename, Stage *stage,
|
||||
std::string *warn, std::string *err,
|
||||
@@ -125,6 +150,7 @@ bool LoadUSDCFromMemory(const uint8_t *addr, const size_t length,
|
||||
usdc::USDCReaderConfig config;
|
||||
config.numThreads = options.num_threads;
|
||||
config.strict_allowedToken_check = options.strict_allowedToken_check;
|
||||
config.kMaxAllowedMemoryInMB = size_t(options.max_memory_limit_in_mb);
|
||||
usdc::USDCReader reader(&sr, config);
|
||||
|
||||
if (!reader.ReadUSDC()) {
|
||||
@@ -728,6 +754,7 @@ bool LoadUSDAFromMemory(const uint8_t *addr, const size_t length,
|
||||
tinyusdz::usda::USDAReaderConfig config;
|
||||
config.strict_allowedToken_check = options.strict_allowedToken_check;
|
||||
config.allow_unknown_apiSchema = !options.strict_apiSchema_check;
|
||||
config.max_memory_limit_in_mb = size_t(options.max_memory_limit_in_mb);
|
||||
reader.set_reader_config(config);
|
||||
|
||||
reader.SetBaseDir(base_dir);
|
||||
@@ -893,7 +920,11 @@ bool LoadUSDFromMemory(const uint8_t *addr, const size_t length,
|
||||
options);
|
||||
} else {
|
||||
if (err) {
|
||||
(*err) += "Couldn't determine USD format(USDA/USDC/USDZ).\n";
|
||||
(*err) += "Couldn't determine USD format(USDA/USDC/USDZ). ";
|
||||
(*err) += "Found magic header: " + FormatMagicHeader(addr, length, 8) + ", ";
|
||||
(*err) += "expected: \"#usda 1.0\" (0x23 75 73 64 61 20 31 2e 30) for USDA, ";
|
||||
(*err) += "\"PXR-USDC\" (0x50 58 52 2d 55 53 44 43) for USDC, ";
|
||||
(*err) += "or ZIP signature (0x50 4b 03 04) for USDZ.\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1470,7 +1501,11 @@ bool LoadLayerFromMemory(const uint8_t *addr, const size_t length,
|
||||
#endif
|
||||
} else {
|
||||
if (err) {
|
||||
(*err) += "Couldn't determine USD format(USDA/USDC/USDZ).\n";
|
||||
(*err) += "Couldn't determine USD format(USDA/USDC/USDZ). ";
|
||||
(*err) += "Found magic header: " + FormatMagicHeader(addr, length, 8) + ", ";
|
||||
(*err) += "expected: \"#usda 1.0\" (0x23 75 73 64 61 20 31 2e 30) for USDA, ";
|
||||
(*err) += "\"PXR-USDC\" (0x50 58 52 2d 55 53 44 43) for USDC, ";
|
||||
(*err) += "or ZIP signature (0x50 4b 03 04) for USDZ.\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -320,6 +320,7 @@ class USDAReader::Impl {
|
||||
|
||||
void set_reader_config(const USDAReaderConfig &config) {
|
||||
_config = config;
|
||||
_parser.SetMaxMemoryLimit(config.max_memory_limit_in_mb);
|
||||
}
|
||||
|
||||
const USDAReaderConfig get_reader_config() const {
|
||||
|
||||
@@ -18,6 +18,7 @@ struct USDAReaderConfig {
|
||||
bool allow_unknown_shader{true};
|
||||
bool allow_unknown_apiSchema{true};
|
||||
bool strict_allowedToken_check{false};
|
||||
size_t max_memory_limit_in_mb{1024ull*128ull}; // Default 128GB
|
||||
};
|
||||
|
||||
///
|
||||
|
||||
@@ -21,7 +21,7 @@ struct USDCReaderConfig {
|
||||
uint32_t kMaxTokenLength = 4096; // Max length of `token`
|
||||
uint32_t kMaxStringLength = 1024*1024*64; // Max length of `string` data
|
||||
uint32_t kMaxElementSize = 8192; // Max allowed value for `elementSize`
|
||||
size_t kMaxAllowedMemoryInMB = 1024*16; //Max allowed memory usage in [mb]
|
||||
size_t kMaxAllowedMemoryInMB = 1024*128; //Max allowed memory usage in [mb]
|
||||
|
||||
bool allow_unknown_prims = true;
|
||||
bool allow_unknown_apiSchemas = true;
|
||||
|
||||
@@ -37,6 +37,8 @@ TEST_LIST = {
|
||||
{ "pathutil_test", pathutil_test },
|
||||
{ "ioutil_test", ioutil_test },
|
||||
{ "strutil_test", strutil_test },
|
||||
{ "tinystring_test", tinystring_test },
|
||||
{ "parse_int_test", parse_int_test },
|
||||
{ "timesamples_test", timesamples_test },
|
||||
#if defined(TINYUSDZ_WITH_PXR_COMPAT_API)
|
||||
{ "pxr_compat_api_test", pxr_compat_api_test },
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "unit-strutil.h"
|
||||
#include "str-util.hh"
|
||||
#include "tiny-string.hh"
|
||||
|
||||
using namespace tinyusdz;
|
||||
|
||||
@@ -38,3 +39,161 @@ void strutil_test(void) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void tinystring_test(void) {
|
||||
|
||||
tstring s("hello");
|
||||
tstring s2("bora");
|
||||
tstring s3("ll");
|
||||
tstring s4("hellobora");
|
||||
tstring_view v0(s);
|
||||
tstring_view v1(s);
|
||||
tstring_view v2(s2);
|
||||
tstring_view v3(s3);
|
||||
tstring_view v4(s4);
|
||||
|
||||
TEST_CHECK(v0 == v1);
|
||||
TEST_CHECK(v0 != v2);
|
||||
|
||||
TEST_CHECK(v0.contains(v0));
|
||||
TEST_CHECK(v0.contains(v3));
|
||||
TEST_CHECK(!v0.contains(v2));
|
||||
|
||||
TEST_CHECK(!v4.starts_with(v2));
|
||||
TEST_CHECK(v4.starts_with(v0));
|
||||
|
||||
TEST_CHECK(!v4.ends_with(v0));
|
||||
TEST_CHECK(v4.ends_with(v2));
|
||||
|
||||
}
|
||||
|
||||
void parse_int_test(void) {
|
||||
using namespace tinyusdz::str;
|
||||
|
||||
int32_t result;
|
||||
|
||||
// Basic positive numbers
|
||||
{
|
||||
tstring_view sv("123");
|
||||
TEST_CHECK(parse_int(sv, &result));
|
||||
TEST_CHECK(result == 123);
|
||||
}
|
||||
|
||||
// Basic negative numbers
|
||||
{
|
||||
tstring_view sv("-456");
|
||||
TEST_CHECK(parse_int(sv, &result));
|
||||
TEST_CHECK(result == -456);
|
||||
}
|
||||
|
||||
// Zero
|
||||
{
|
||||
tstring_view sv("0");
|
||||
TEST_CHECK(parse_int(sv, &result));
|
||||
TEST_CHECK(result == 0);
|
||||
}
|
||||
|
||||
// Positive sign
|
||||
{
|
||||
tstring_view sv("+789");
|
||||
TEST_CHECK(parse_int(sv, &result));
|
||||
TEST_CHECK(result == 789);
|
||||
}
|
||||
|
||||
// Maximum int32_t value
|
||||
{
|
||||
tstring_view sv("2147483647");
|
||||
TEST_CHECK(parse_int(sv, &result));
|
||||
TEST_CHECK(result == 2147483647);
|
||||
}
|
||||
|
||||
// Minimum int32_t value
|
||||
{
|
||||
tstring_view sv("-2147483648");
|
||||
TEST_CHECK(parse_int(sv, &result));
|
||||
TEST_CHECK(result == -2147483648);
|
||||
}
|
||||
|
||||
// Empty string
|
||||
{
|
||||
tstring_view sv("");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
// Just a sign
|
||||
{
|
||||
tstring_view sv("-");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("+");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
// Non-numeric characters
|
||||
{
|
||||
tstring_view sv("123a");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("a123");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("12.3");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
// Overflow cases
|
||||
{
|
||||
tstring_view sv("2147483648"); // INT32_MAX + 1
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("-2147483649"); // INT32_MIN - 1
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
// Very large numbers
|
||||
{
|
||||
tstring_view sv("999999999999999999");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("-999999999999999999");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
// Leading/trailing spaces (should fail since parse_int doesn't handle whitespace)
|
||||
{
|
||||
tstring_view sv(" 123");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("123 ");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
// Multiple signs
|
||||
{
|
||||
tstring_view sv("++123");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("--123");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
{
|
||||
tstring_view sv("+-123");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
void strutil_test(void);
|
||||
void tinystring_test(void);
|
||||
void parse_int_test(void);
|
||||
|
||||
Reference in New Issue
Block a user