aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2019-10-06 20:59:12 +0200
committerDavid Robillard <d@drobilla.net>2020-06-21 18:12:04 +0200
commit5c00dae1a38a1741705fe759054928b7fb3d30cb (patch)
treec243b13d2deafc355246853bbcdea2b9532b3856
parenta204962e35263d0f0aca8e9b34e5091669c7e057 (diff)
downloadserd-5c00dae1a38a1741705fe759054928b7fb3d30cb.tar.gz
serd-5c00dae1a38a1741705fe759054928b7fb3d30cb.tar.bz2
serd-5c00dae1a38a1741705fe759054928b7fb3d30cb.zip
Add support for xsd:double and xsd:float
These can be used to serialise a float or double in the shortest normalised form that can be read back in to the exact same floating point value.
-rw-r--r--NEWS1
-rw-r--r--serd/serd.h30
-rw-r--r--src/node.c81
-rw-r--r--src/static_nodes.h2
4 files changed, 114 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index a230535f..9ee5e586 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ serd (1.0.1) unstable;
* Add option for writing terse output without newlines
* Add support for validation
* Add support for writing terse collections
+ * Add support for xsd:float and xsd:double literals
* Bring read/write interface closer to C standard
* Make nodes opaque
* Make serd_strtod API const-correct
diff --git a/serd/serd.h b/serd/serd.h
index cde9a3c4..aeed687e 100644
--- a/serd/serd.h
+++ b/serd/serd.h
@@ -722,6 +722,36 @@ serd_new_decimal(double d,
const SerdNode* datatype);
/**
+ Create a new node by serialising `d` into a normalised xsd:double string.
+
+ The returned node will always be in normalised scientific notation, like
+ "1.23E4", except for NaN and negative/positive infinity, which are "NaN",
+ "-INF", and "INF", respectively.
+
+ Uses the shortest possible representation that precisely describes `d`,
+ which has at most 17 significant digits (under 24 characters total).
+
+ @param d Double value to serialise.
+ @return A literal node with datatype xsd:double.
+*/
+SERD_API
+SerdNode*
+serd_new_double(double d);
+
+/**
+ Create a new node by serialising `f` into a normalised xsd:float string.
+
+ Uses identical formatting to serd_new_double(), except with at most 9
+ significant digits (under 14 characters total).
+
+ @param f Float value to serialise.
+ @return A literal node with datatype xsd:float.
+*/
+SERD_API
+SerdNode*
+serd_new_float(float f);
+
+/**
Create a new node by serialising `i` into an xsd:integer string
@param i Integer value to serialise.
diff --git a/src/node.c b/src/node.c
index 9a0ad3b9..408c9561 100644
--- a/src/node.c
+++ b/src/node.c
@@ -32,6 +32,9 @@
#include <string.h>
// Define C11 numeric constants if the compiler hasn't already
+#ifndef FLT_DECIMAL_DIG
+# define FLT_DECIMAL_DIG 9
+#endif
#ifndef DBL_DECIMAL_DIG
# define DBL_DECIMAL_DIG 17
#endif
@@ -697,6 +700,84 @@ serd_new_decimal(const double d,
return node;
}
+static SerdNode*
+serd_new_scientific(const double d,
+ const unsigned precision,
+ const SerdNode* datatype)
+{
+ const size_t type_len = serd_node_total_size(datatype);
+ const int fpclass = fpclassify(d);
+
+ if (fpclass == FP_INFINITE && d < 0) {
+ return serd_new_typed_literal("-INF", datatype);
+ } else if (fpclass == FP_INFINITE && d > 0) {
+ return serd_new_typed_literal("INF", datatype);
+ } else if (fpclass == FP_NAN) {
+ return serd_new_typed_literal("NaN", datatype);
+ } else if (fpclass == FP_ZERO) {
+ return signbit(d) ? serd_new_typed_literal("-0.0E0", datatype)
+ : serd_new_typed_literal("0.0E0", datatype);
+ }
+
+ // Get decimal digits
+ const double abs_d = fabs(d);
+ char digits[32] = {0};
+ const SerdDecimalCount count = serd_decimals(abs_d, digits, precision);
+ assert(count.count == 1 || digits[count.count - 1] != '0');
+
+ // Calculate string length and allocate node
+ const size_t len = count.count;
+ const int expt = count.expt;
+ unsigned abs_expt = (unsigned)abs(expt);
+ const unsigned abs_exp_digits = (unsigned)serd_count_digits(abs_expt);
+
+ SerdNode* node = serd_node_malloc(type_len + len + abs_exp_digits + 4,
+ SERD_HAS_DATATYPE,
+ SERD_LITERAL);
+
+ char* const buf = serd_node_buffer(node);
+ if (d < 0.0) {
+ buf[node->n_bytes++] = '-';
+ }
+
+ // Write mantissa, with decimal point after the first (normal form)
+ buf[node->n_bytes++] = digits[0];
+ buf[node->n_bytes++] = '.';
+ if (len > 1) {
+ node->n_bytes += copy_digits(buf + node->n_bytes, digits + 1, len - 1);
+ } else {
+ buf[node->n_bytes++] = '0';
+ }
+
+ // Write exponent
+ buf[node->n_bytes++] = 'E';
+ if (expt < 0) {
+ buf[node->n_bytes++] = '-';
+ }
+ char* s = buf + node->n_bytes + abs_exp_digits - 1;
+ do {
+ *s-- = (char)('0' + (abs_expt % 10));
+ } while ((abs_expt /= 10) > 0);
+ node->n_bytes += abs_exp_digits;
+
+ memcpy(serd_node_get_meta(node), datatype, type_len);
+ serd_node_check_padding(node);
+ return node;
+}
+
+SerdNode*
+serd_new_double(const double d)
+{
+ return serd_new_scientific(d, DBL_DECIMAL_DIG, &serd_xsd_double.node);
+}
+
+SerdNode*
+serd_new_float(const float f)
+{
+ return serd_new_scientific(
+ (double)f, FLT_DECIMAL_DIG, &serd_xsd_float.node);
+}
+
SerdNode*
serd_new_integer(int64_t i, const SerdNode* datatype)
{
diff --git a/src/static_nodes.h b/src/static_nodes.h
index f4794d72..b7770432 100644
--- a/src/static_nodes.h
+++ b/src/static_nodes.h
@@ -32,6 +32,8 @@ typedef struct StaticNode {
DEFINE_XSD_NODE(base64Binary)
DEFINE_XSD_NODE(boolean)
DEFINE_XSD_NODE(decimal)
+DEFINE_XSD_NODE(double)
+DEFINE_XSD_NODE(float)
DEFINE_XSD_NODE(integer)
#endif // SERD_STATIC_NODES_H