rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
log_numbers.c File Reference

Turning a text cell into a number, when "1,024" might be either. More...

#include "log_numbers.h"
#include <ctype.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

Data Structures

struct  [struct].k_time_units
 Seconds per unit for the time units an exporter is likely to write. More...

Functions

static int grouping_len (const char *p)
 Two character classes that overlap, and the overlap is the whole reason this is written by hand rather than with strtod():
static int space_len (const char *p)
 Length of a white-space character at a position.
static const char * skip_space (const char *p)
 Skip white-space characters.
static bool copy_digits (const char *begin, const char *end, char *out, size_t out_size)
 Copy [begin, end) into out, dropping grouping-only characters.
static const char * unit_begin (const char *p)
 Truncate without splitting a UTF-8 sequence: half a ° is worse than none.
static bool unit_is_plausible (const char *p)
 Whether a unit text can be a unit.
static void copy_trimmed (const char *begin, const char *end, char *out, size_t out_size)
 Copy a range without surrounding white space.
bool log_split_value (const char *raw, log_value_t *out)
 Split a raw cell into numeric text, exponent and unit.
static bool leading_group_is_legal (const char *digits, char sep)
 The integer part, read as a thousands group: 1-3 digits and no leading zero (a bare "0" cannot be followed by a group either).
static bool grouping_is_legal (const char *digits, char sep)
 Every group of a repeated separator: leading 1-3 digits, the rest exactly three, digits throughout.
log_evidence_t log_evidence_of (const char *digits)
 Inspect one already-split numeric string and report what it proves.
void log_votes_reset (log_votes_t *v)
 Clear the tally.
void log_votes_add (log_votes_t *v, const char *raw)
 Feed one raw cell.
log_conv_t log_votes_result (const log_votes_t *v, log_ambig_t fallback, bool *confident, bool *conflict)
 Decide the convention from the tally.
static bool all_digits (const char *begin, const char *end)
 Whether a range holds only digits and is not empty.
bool log_is_well_formed (const char *digits, log_conv_t convention)
 Is digits well formed under convention?
bool log_parse_with (const char *raw, log_conv_t convention, double *value, char *unit, size_t unit_size)
 Parse one cell with a known convention.
bool log_parse_loose (const char *raw, log_ambig_t fallback, double *value)
 Parse a standalone value, inferring the convention from it alone.
void log_unit_tally_reset (log_unit_tally_t *t)
 Clear a unit tally.
void log_unit_tally_add (log_unit_tally_t *t, const char *unit)
 Add a unit suffix to a tally.
void log_unit_dominant (const log_unit_tally_t *t, char *out, size_t out_size, bool *mixed)
 The most common non-empty suffix. mixed reports more than one.
static bool ieq (const char *a, const char *b)
 Compare two strings without regard to case.
static bool is_bare_unit (const char *s)
 Whether a text is a unit in the bare unit list.
static void copy_range (const char *begin, const char *end, char *out, size_t out_size)
 Copy a range, cut without splitting a UTF-8 sequence.
void log_unit_from_header (const char *header, char *name, size_t name_size, char *unit, size_t unit_size)
 Pull a unit out of a column header: "voltage (V)", "current [A]", "speed km/h", "power (W)". "gyroADC[0]" keeps its index – that is an axis number, not a unit.
double log_time_unit_scale (const char *unit)
 Seconds per unit for a time column, or 0 when the name is not a time unit.

Variables

static const char *const k_bare_units []
 Units accepted after a space, e.g. "altitude m".
static const struct  k_time_units []

Detailed Description

Turning a text cell into a number, when "1,024" might be either.

The hard problem in a locale-blind log reader is not splitting rows, it is the decimal point: "1,024" is a thousand-and-twenty-four to an English writer and one-point-oh-two-four to a German one, and the cell itself cannot say which. So this does not decide per cell. log_evidence_of() classifies a cell as evidence for a convention, evidence against, or genuinely ambiguous, and the caller votes across the whole column – one unambiguous "1.5" settles a file that is otherwise all thousands-shaped values.

The care is in what counts as no evidence. A repeated separator is usually grouping, but "192.168.0.1", "15.01.2024" and "1.2.3" are an address, a date and a version, not numbers, and voting on them hands the file to the wrong convention. A three-digit tail is the ambiguous shape, except when the integer part cannot be a thousands group (a leading zero, or more than three digits), which is the millisecond-timestamp case that would otherwise read an English log as German.

SPDX-License-Identifier: MIT

Function Documentation

◆ all_digits()

bool all_digits ( const char * begin,
const char * end )
static

Whether a range holds only digits and is not empty.

Parameters
beginFirst byte of the range.
endOne past the last byte of the range.
Returns
true when the range is non-empty and every byte is a digit.

◆ copy_digits()

bool copy_digits ( const char * begin,
const char * end,
char * out,
size_t out_size )
static

Copy [begin, end) into out, dropping grouping-only characters.

Parameters
beginFirst byte of the body.
endOne past the last byte of the body.
outReceives the digits, NUL-terminated.
out_sizeSize of out, bytes.
Returns
true when at least one byte was copied and it fits.

◆ copy_range()

void copy_range ( const char * begin,
const char * end,
char * out,
size_t out_size )
static

Copy a range, cut without splitting a UTF-8 sequence.

Parameters
beginFirst byte of the range.
endOne past the last byte of the range.
outReceives the text, NUL-terminated.
out_sizeSize of out, bytes.

◆ copy_trimmed()

void copy_trimmed ( const char * begin,
const char * end,
char * out,
size_t out_size )
static

Copy a range without surrounding white space.

Truncate without splitting a UTF-8 sequence: half a degree sign is worse than none.

Parameters
beginFirst byte of the range.
endOne past the last byte of the range.
outReceives the text, NUL-terminated.
out_sizeSize of out, bytes.

◆ grouping_is_legal()

bool grouping_is_legal ( const char * digits,
char sep )
static

Every group of a repeated separator: leading 1-3 digits, the rest exactly three, digits throughout.

Parameters
digitsNumeric text as log_split_value() gives it.
sepThe separator that groups.
Returns
true when the groups are legal.

◆ grouping_len()

int grouping_len ( const char * p)
static

Two character classes that overlap, and the overlap is the whole reason this is written by hand rather than with strtod():

grouping-only ' ' '\'' U+00A0 U+202F – never a decimal point whitespace ASCII space and friends, plus U+00A0 and U+202F

ASCII space is in both, so "1 234,56" is one number and "12%" is a number with a unit. What separates them is that the numeric body must end on a digit: the scanner remembers where the last digit was and hands everything after it to the unit.

Parameters
pThe text at the position.
Returns
1 for ASCII space or apostrophe, 2 for U+00A0, 3 for U+202F, else 0.

◆ ieq()

bool ieq ( const char * a,
const char * b )
static

Compare two strings without regard to case.

Parameters
aThe first string.
bThe second string.
Returns
true when the strings are equal without regard to case.

◆ is_bare_unit()

bool is_bare_unit ( const char * s)
static

Whether a text is a unit in the bare unit list.

Parameters
sThe text.
Returns
true when s is in k_bare_units, in any case.

◆ leading_group_is_legal()

bool leading_group_is_legal ( const char * digits,
char sep )
static

The integer part, read as a thousands group: 1-3 digits and no leading zero (a bare "0" cannot be followed by a group either).

Parameters
digitsNumeric text as log_split_value() gives it.
sepThe separator that groups.
Returns
true when the integer part is 1 to 3 digits without a leading zero.

◆ log_evidence_of()

log_evidence_t log_evidence_of ( const char * digits)

Inspect one already-split numeric string and report what it proves.

Parameters
digitsNumeric text as log_split_value() gives it.
Returns
The evidence.

◆ log_is_well_formed()

bool log_is_well_formed ( const char * digits,
log_conv_t convention )

Is digits well formed under convention?

Strict on purpose. Stripping the grouping separator and parsing what is left turns the German "1.234,56" into 1.23456 when read as English: a silently wrong number. Rejecting lets the caller surface the mismatch.

Parameters
digitsNumeric text as log_split_value() gives it.
conventionThe convention to check against.
Returns
true when the text is well formed.

◆ log_parse_loose()

bool log_parse_loose ( const char * raw,
log_ambig_t fallback,
double * value )

Parse a standalone value, inferring the convention from it alone.

Parameters
rawThe raw cell text.
fallbackThe default for an ambiguous value.
valueReceives the number.
Returns
true when the value parsed.

◆ log_parse_with()

bool log_parse_with ( const char * raw,
log_conv_t convention,
double * value,
char * unit,
size_t unit_size )

Parse one cell with a known convention.

Parameters
rawThe raw cell text.
conventionThe convention of the file.
valueReceives the number.
unitReceives the unit suffix.
unit_sizeSize of unit, bytes.
Returns
false when the cell does not conform, rather than guessing.

◆ log_split_value()

bool log_split_value ( const char * raw,
log_value_t * out )

Split a raw cell into numeric text, exponent and unit.

Grouping characters that are never decimal points – ASCII space, the apostrophe Swiss exporters use, U+00A0 and U+202F – are removed here, so digits carries only digits, '.' and ','.

Parameters
rawThe raw cell text.
outReceives the parts.
Returns
false when the cell holds no number at all.

◆ log_time_unit_scale()

double log_time_unit_scale ( const char * unit)

Seconds per unit for a time column, or 0 when the name is not a time unit.

Parameters
unitThe unit name.
Returns
Seconds per unit, or 0.

◆ log_unit_dominant()

void log_unit_dominant ( const log_unit_tally_t * t,
char * out,
size_t out_size,
bool * mixed )

The most common non-empty suffix. mixed reports more than one.

Parameters
tThe tally.
outReceives the suffix.
out_sizeSize of out, bytes.
mixedReceives true when more than one suffix was seen.

◆ log_unit_from_header()

void log_unit_from_header ( const char * header,
char * name,
size_t name_size,
char * unit,
size_t unit_size )

Pull a unit out of a column header: "voltage (V)", "current [A]", "speed km/h", "power (W)". "gyroADC[0]" keeps its index – that is an axis number, not a unit.

Parameters
headerThe column header.
nameReceives the name without the unit.
name_sizeSize of name, bytes.
unitReceives the unit.
unit_sizeSize of unit, bytes.

◆ log_unit_tally_add()

void log_unit_tally_add ( log_unit_tally_t * t,
const char * unit )

Add a unit suffix to a tally.

Parameters
tThe tally.
unitThe unit suffix.

◆ log_unit_tally_reset()

void log_unit_tally_reset ( log_unit_tally_t * t)

Clear a unit tally.

Parameters
tThe tally.

◆ log_votes_add()

void log_votes_add ( log_votes_t * v,
const char * raw )

Feed one raw cell.

Cells that hold no number are ignored.

Parameters
vThe tally.
rawThe raw cell text.

◆ log_votes_reset()

void log_votes_reset ( log_votes_t * v)

Clear the tally.

Parameters
vThe tally.

◆ log_votes_result()

log_conv_t log_votes_result ( const log_votes_t * v,
log_ambig_t fallback,
bool * confident,
bool * conflict )

Decide the convention from the tally.

Parameters
vThe tally.
fallbackThe default for a wholly ambiguous file.
confidentset false when nothing proved anything and the fallback had to decide.
conflictset true when both conventions were proven, which means the file itself is inconsistent; the caller reports it rather than silently picking the winner.
Returns
The convention.

◆ skip_space()

const char * skip_space ( const char * p)
static

Skip white-space characters.

Parameters
pThe text.
Returns
The first position that is not white space.

◆ space_len()

int space_len ( const char * p)
static

Length of a white-space character at a position.

Parameters
pThe text at the position.
Returns
The byte length of the character, or 0 when it is not white space.

◆ unit_begin()

const char * unit_begin ( const char * p)
static

Truncate without splitting a UTF-8 sequence: half a ° is worse than none.

What is left after the numeric body, read as a unit. A trailing decimal separator belongs to the number ("1500," is 1500), and a residual carrying a ':' or a signed number is the tail of something the scan stopped inside: a clock time, an ISO 8601 date, a hyphenated part number. Those are not numbers with units, and accepting them makes a whole column look numeric.

Parameters
pThe text after the numeric body.
Returns
The position of the unit text.

◆ unit_is_plausible()

bool unit_is_plausible ( const char * p)
static

Whether a unit text can be a unit.

What is left after the numeric body, read as a unit. A residual carrying a ':' or a signed number is the tail of something the scan stopped inside: a clock time, an ISO 8601 date, a hyphenated part number. Those are not numbers with units, and accepting them makes a whole column look numeric.

Parameters
pThe unit text.
Returns
false when the text holds a ':' or a sign before a digit.

Variable Documentation

◆ k_bare_units

const char* const k_bare_units[]
static
Initial value:
= {
"m", "km", "cm", "mm", "s", "ms", "us", "min", "h", "A", "mA", "V", "mV",
"W", "kW", "Wh", "mAh", "Hz", "kHz", "rpm", "g", "kg", "N", "Pa", "hPa",
"bar", "%", "\xC2\xB0" "C", "\xC2\xB0" "F", "deg", "rad", "m/s", "km/h",
"deg/s",
}

Units accepted after a space, e.g. "altitude m".

A list rather than a shape test, because "power output" also has the shape of a name followed by a short word.

◆ k_time_units

const struct [struct].k_time_units k_time_units[]
Initial value:
= {
{ "s", 1.0 }, { "sec", 1.0 }, { "secs", 1.0 },
{ "second", 1.0 }, { "seconds", 1.0 }, { "sekunde", 1.0 },
{ "sekunden", 1.0 }, { "ms", 1e-3 }, { "msec", 1e-3 },
{ "millis", 1e-3 }, { "millisecond", 1e-3 }, { "milliseconds", 1e-3 },
{ "millisekunden", 1e-3 }, { "us", 1e-6 }, { "\xC2\xB5" "s", 1e-6 },
{ "microsecond", 1e-6 }, { "microseconds", 1e-6 },
{ "mikrosekunden", 1e-6 }, { "ns", 1e-9 }, { "min", 60.0 },
{ "minute", 60.0 }, { "minutes", 60.0 }, { "minuten", 60.0 },
{ "h", 3600.0 }, { "hr", 3600.0 }, { "hour", 3600.0 },
{ "hours", 3600.0 }, { "stunden", 3600.0 },
}