Skip to main content

zincite_check/
floats.rs

1#[cfg(feature = "prepare")]
2use crate::prepare::ArgumentType;
3use crate::{IntegerRelation, Value, ValueType, relations::RelationMode};
4use serde::{Deserialize, Deserializer, Serialize};
5
6/// A finite binary64 value. Equality is exact; either signed zero becomes positive zero.
7#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize)]
8#[serde(transparent)]
9pub struct FiniteFloat(f64);
10impl Eq for FiniteFloat {}
11impl FiniteFloat {
12    pub fn new(value: f64) -> Result<Self, &'static str> {
13        if !value.is_finite() {
14            return Err("float values must be finite");
15        }
16        Ok(Self(if value == 0.0 { 0.0 } else { value }))
17    }
18    pub fn get(self) -> f64 {
19        self.0
20    }
21}
22impl<'de> Deserialize<'de> for FiniteFloat {
23    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
24        Self::new(f64::deserialize(deserializer)?).map_err(serde::de::Error::custom)
25    }
26}
27impl std::fmt::Display for FiniteFloat {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{:?}", self.0)
30    }
31}
32
33/// Absolute and relative comparison tolerances owned by a prepared model.
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
35pub struct FloatTolerance {
36    absolute: FiniteFloat,
37    relative: FiniteFloat,
38}
39impl FloatTolerance {
40    pub fn new(absolute: f64, relative: f64) -> Result<Self, &'static str> {
41        if absolute < 0.0 || relative < 0.0 {
42            return Err("float tolerances must be nonnegative");
43        }
44        Ok(Self {
45            absolute: FiniteFloat::new(absolute)?,
46            relative: FiniteFloat::new(relative)?,
47        })
48    }
49    pub fn absolute(self) -> f64 {
50        self.absolute.get()
51    }
52    pub fn relative(self) -> f64 {
53        self.relative.get()
54    }
55    pub fn equal(self, left: FiniteFloat, right: FiniteFloat) -> bool {
56        let (a, b) = (left.get(), right.get());
57        if a == b {
58            return true;
59        }
60        let distance = (a - b).abs();
61        if distance.is_finite() && distance <= self.absolute() {
62            return true;
63        }
64        if self.relative() == 0.0 {
65            return false;
66        }
67        let scale = a.abs().max(b.abs());
68        let relative_distance = if distance.is_finite() {
69            distance / scale
70        } else {
71            (a * 0.5 - b * 0.5).abs() / (scale * 0.5)
72        };
73        relative_distance <= self.relative()
74    }
75    pub(crate) fn holds(
76        self,
77        relation: IntegerRelation,
78        left: FiniteFloat,
79        right: FiniteFloat,
80    ) -> bool {
81        use IntegerRelation::*;
82        let equal = self.equal(left, right);
83        match relation {
84            Equal => equal,
85            NotEqual => !equal,
86            Less => left < right && !equal,
87            LessEqual => left < right || equal,
88            Greater => left > right && !equal,
89            GreaterEqual => left > right || equal,
90        }
91    }
92    pub(crate) fn contains(
93        self,
94        value: FiniteFloat,
95        lower: FiniteFloat,
96        upper: FiniteFloat,
97    ) -> bool {
98        lower <= upper
99            && self.holds(IntegerRelation::LessEqual, lower, value)
100            && self.holds(IntegerRelation::LessEqual, value, upper)
101    }
102}
103impl Default for FloatTolerance {
104    fn default() -> Self {
105        Self::new(1e-9, 1e-9).expect("finite nonnegative defaults")
106    }
107}
108impl<'de> Deserialize<'de> for FloatTolerance {
109    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
110        #[derive(Deserialize)]
111        #[serde(deny_unknown_fields)]
112        struct Settings {
113            absolute: FiniteFloat,
114            relative: FiniteFloat,
115        }
116        let settings = Settings::deserialize(deserializer)?;
117        Self::new(settings.absolute.get(), settings.relative.get())
118            .map_err(serde::de::Error::custom)
119    }
120}
121
122/// A fixed union of closed float ranges, ordered and nonoverlapping by exact bounds.
123#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
124#[serde(transparent)]
125pub struct FloatSet(Vec<(FiniteFloat, FiniteFloat)>);
126impl FloatSet {
127    pub fn from_ranges(mut ranges: Vec<(FiniteFloat, FiniteFloat)>) -> Self {
128        ranges.retain(|(lower, upper)| lower <= upper);
129        ranges.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("finite endpoints"));
130        let mut union: Vec<(FiniteFloat, FiniteFloat)> = Vec::new();
131        for (lower, upper) in ranges {
132            if let Some(last) = union.last_mut()
133                && lower <= last.1
134            {
135                if upper > last.1 {
136                    last.1 = upper;
137                }
138            } else {
139                union.push((lower, upper));
140            }
141        }
142        Self(union)
143    }
144    pub fn ranges(&self) -> &[(FiniteFloat, FiniteFloat)] {
145        &self.0
146    }
147}
148impl<'de> Deserialize<'de> for FloatSet {
149    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
150        let ranges = Vec::<(FiniteFloat, FiniteFloat)>::deserialize(deserializer)?;
151        if ranges.iter().any(|(lower, upper)| lower > upper)
152            || ranges.windows(2).any(|pair| pair[0].1 >= pair[1].0)
153        {
154            return Err(serde::de::Error::custom(
155                "float set ranges must be ordered, nonoverlapping and not reversed",
156            ));
157        }
158        Ok(Self(ranges))
159    }
160}
161impl std::fmt::Display for FloatSet {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        f.write_str("{")?;
164        for (index, (lower, upper)) in self.0.iter().enumerate() {
165            if index != 0 {
166                f.write_str(", ")?;
167            }
168            if lower == upper {
169                write!(f, "{lower}")?;
170            } else {
171                write!(f, "{lower}..{upper}")?;
172            }
173        }
174        f.write_str("}")
175    }
176}
177
178#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum FloatOperation {
181    Absolute,
182    Add,
183    Multiply,
184    Divide,
185    Minimum,
186    Maximum,
187    SquareRoot,
188    Power,
189    FixedBasePower,
190    Exponential,
191    NaturalLog,
192    Log10,
193    Log2,
194    Sine,
195    Cosine,
196    Tangent,
197    ArcSine,
198    ArcCosine,
199    ArcTangent,
200    HyperbolicSine,
201    HyperbolicCosine,
202    HyperbolicTangent,
203    InverseHyperbolicSine,
204    InverseHyperbolicCosine,
205    InverseHyperbolicTangent,
206}
207impl FloatOperation {
208    pub(crate) fn input_count(self) -> usize {
209        match self {
210            Self::Add
211            | Self::Multiply
212            | Self::Divide
213            | Self::Minimum
214            | Self::Maximum
215            | Self::Power
216            | Self::FixedBasePower => 2,
217            _ => 1,
218        }
219    }
220    pub(crate) fn calculate(self, inputs: &[FiniteFloat]) -> Result<FiniteFloat, &'static str> {
221        let x = inputs[0].get();
222        let result = match self {
223            Self::Absolute => x.abs(),
224            Self::Add => x + inputs[1].get(),
225            Self::Multiply => x * inputs[1].get(),
226            Self::Divide => {
227                let divisor = inputs[1].get();
228                if divisor == 0.0 {
229                    return Err("division by zero in float arithmetic");
230                }
231                x / divisor
232            }
233            Self::Minimum => x.min(inputs[1].get()),
234            Self::Maximum => x.max(inputs[1].get()),
235            Self::SquareRoot => {
236                if x < 0.0 {
237                    return Err("sqrt requires a nonnegative argument");
238                }
239                libm::sqrt(x)
240            }
241            Self::Power | Self::FixedBasePower => {
242                let exponent = inputs[1].get();
243                if self == Self::FixedBasePower && x <= 0.0 {
244                    return Err("fixed-base power requires a positive base");
245                }
246                if x < 0.0 && exponent.trunc() != exponent {
247                    return Err("negative float power base requires an integral exponent");
248                }
249                if x == 0.0 && exponent < 0.0 {
250                    return Err("zero float power base requires a nonnegative exponent");
251                }
252                libm::pow(x, exponent)
253            }
254            Self::Exponential => libm::exp(x),
255            Self::NaturalLog | Self::Log10 | Self::Log2 => {
256                if x <= 0.0 {
257                    return Err("logarithm requires a positive argument");
258                }
259                match self {
260                    Self::NaturalLog => libm::log(x),
261                    Self::Log10 => libm::log10(x),
262                    _ => libm::log2(x),
263                }
264            }
265            Self::Sine => libm::sin(x),
266            Self::Cosine => libm::cos(x),
267            Self::Tangent => libm::tan(x),
268            Self::ArcSine | Self::ArcCosine => {
269                if !(-1.0..=1.0).contains(&x) {
270                    return Err("inverse sine/cosine requires an argument in -1..1");
271                }
272                if self == Self::ArcSine {
273                    libm::asin(x)
274                } else {
275                    libm::acos(x)
276                }
277            }
278            Self::ArcTangent => libm::atan(x),
279            Self::HyperbolicSine => libm::sinh(x),
280            Self::HyperbolicCosine => libm::cosh(x),
281            Self::HyperbolicTangent => libm::tanh(x),
282            Self::InverseHyperbolicSine => libm::asinh(x),
283            Self::InverseHyperbolicCosine => {
284                if x < 1.0 {
285                    return Err("acosh requires an argument at least one");
286                }
287                libm::acosh(x)
288            }
289            Self::InverseHyperbolicTangent => {
290                if x <= -1.0 || x >= 1.0 {
291                    return Err("atanh requires an argument strictly between -1 and 1");
292                }
293                libm::atanh(x)
294            }
295        };
296        finite_result(result)
297    }
298}
299/// Exact-order selection; tolerance only checks the supplied result.
300#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(rename_all = "snake_case")]
302pub enum FloatExtremum {
303    Minimum,
304    Maximum,
305}
306impl FloatExtremum {
307    pub(crate) fn calculate(self, inputs: &[FiniteFloat]) -> FiniteFloat {
308        inputs
309            .iter()
310            .copied()
311            .reduce(|left, right| match self {
312                Self::Minimum if right < left => right,
313                Self::Maximum if right > left => right,
314                _ => left,
315            })
316            .expect("validated nonempty float extremum")
317    }
318}
319
320pub(crate) fn validate_integer_exponent(exponent: i64) -> Result<(), &'static str> {
321    if !(0..=2147483646).contains(&exponent) {
322        return Err("Gecode float power exponent must be in 0..2147483646");
323    }
324    Ok(())
325}
326
327pub(crate) fn integer_power(
328    base: FiniteFloat,
329    mut exponent: i64,
330) -> Result<FiniteFloat, &'static str> {
331    if base.get() == 0.0 && exponent == 0 {
332        return Err("Gecode integer power excludes zero to the zero power");
333    }
334    let mut factor = base;
335    let mut result = FiniteFloat::new(1.0)?;
336    while exponent > 0 {
337        if exponent % 2 == 1 {
338            result = finite_result(result.get() * factor.get())?;
339        }
340        exponent /= 2;
341        if exponent > 0 {
342            factor = finite_result(factor.get() * factor.get())?;
343        }
344    }
345    Ok(result)
346}
347
348#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
349#[serde(rename_all = "snake_case")]
350pub enum FloatConversion {
351    IntegerToFloat,
352    Ceiling,
353    Floor,
354    Round,
355}
356impl FloatConversion {
357    pub(crate) fn input_type(self) -> ValueType {
358        if self == Self::IntegerToFloat {
359            ValueType::Integer
360        } else {
361            ValueType::Float
362        }
363    }
364    pub(crate) fn output_type(self) -> ValueType {
365        if self == Self::IntegerToFloat {
366            ValueType::Float
367        } else {
368            ValueType::Integer
369        }
370    }
371    pub(crate) fn calculate(self, input: &Value) -> Result<Value, &'static str> {
372        if self == Self::IntegerToFloat {
373            return Ok(Value::Float(FiniteFloat::new(input.integer() as f64)?));
374        }
375        let x = input.float().get();
376        let integer = match self {
377            Self::Ceiling => x.ceil(),
378            Self::Floor => x.floor(),
379            Self::Round => x.round(),
380            Self::IntegerToFloat => unreachable!(),
381        };
382        if !(-9223372036854775808.0..9223372036854775808.0).contains(&integer) {
383            return Err("float conversion result is outside i64 range");
384        }
385        Ok(Value::Integer(integer as i64))
386    }
387}
388#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
389#[serde(rename_all = "snake_case")]
390pub enum FloatMembership {
391    Interval,
392    Domain,
393    Set,
394}
395#[derive(Clone, Copy, Debug, PartialEq, Eq)]
396pub(crate) enum FloatName {
397    Relation {
398        relation: IntegerRelation,
399        linear: bool,
400        mode: RelationMode,
401    },
402    CodedImplication,
403    Arithmetic(FloatOperation),
404    IntegerPower,
405    ArrayExtremum {
406        extremum: FloatExtremum,
407        result_first: bool,
408    },
409    Conversion(FloatConversion),
410    Membership(FloatMembership),
411}
412impl FloatName {
413    pub fn from_name(name: &str) -> Option<Self> {
414        use IntegerRelation::*;
415        use RelationMode::*;
416        Some(match name {
417            "float_eq" | "gecode_float_eq" | "fzn_float_eq" => Self::Relation {
418                relation: Equal,
419                linear: false,
420                mode: Plain,
421            },
422            "float_eq_reif" | "gecode_float_eq_reif" | "fzn_float_eq_reif" => Self::Relation {
423                relation: Equal,
424                linear: false,
425                mode: Reified,
426            },
427            "float_eq_imp" => Self::Relation {
428                relation: Equal,
429                linear: false,
430                mode: Implied,
431            },
432            "float_ne" | "gecode_float_ne" | "fzn_float_ne" => Self::Relation {
433                relation: NotEqual,
434                linear: false,
435                mode: Plain,
436            },
437            "float_ne_reif" => Self::Relation {
438                relation: NotEqual,
439                linear: false,
440                mode: Reified,
441            },
442            "float_ne_imp" => Self::Relation {
443                relation: NotEqual,
444                linear: false,
445                mode: Implied,
446            },
447            "float_le" | "gecode_float_le" | "fzn_float_le" => Self::Relation {
448                relation: LessEqual,
449                linear: false,
450                mode: Plain,
451            },
452            "float_le_reif" | "gecode_float_le_reif" | "fzn_float_le_reif" => Self::Relation {
453                relation: LessEqual,
454                linear: false,
455                mode: Reified,
456            },
457            "float_le_imp" => Self::Relation {
458                relation: LessEqual,
459                linear: false,
460                mode: Implied,
461            },
462            "float_lt" | "gecode_float_lt" | "fzn_float_lt" => Self::Relation {
463                relation: Less,
464                linear: false,
465                mode: Plain,
466            },
467            "float_lt_reif" | "gecode_float_lt_reif" | "fzn_float_lt_reif" => Self::Relation {
468                relation: Less,
469                linear: false,
470                mode: Reified,
471            },
472            "float_lt_imp" => Self::Relation {
473                relation: Less,
474                linear: false,
475                mode: Implied,
476            },
477            "float_lin_eq" | "gecode_float_lin_eq" | "fzn_float_lin_eq" => Self::Relation {
478                relation: Equal,
479                linear: true,
480                mode: Plain,
481            },
482            "float_lin_eq_reif" | "gecode_float_lin_eq_reif" | "fzn_float_lin_eq_reif" => {
483                Self::Relation {
484                    relation: Equal,
485                    linear: true,
486                    mode: Reified,
487                }
488            }
489            "float_lin_eq_imp" => Self::Relation {
490                relation: Equal,
491                linear: true,
492                mode: Implied,
493            },
494            "float_lin_ne" => Self::Relation {
495                relation: NotEqual,
496                linear: true,
497                mode: Plain,
498            },
499            "float_lin_ne_reif" => Self::Relation {
500                relation: NotEqual,
501                linear: true,
502                mode: Reified,
503            },
504            "float_lin_ne_imp" => Self::Relation {
505                relation: NotEqual,
506                linear: true,
507                mode: Implied,
508            },
509            "float_lin_le" | "gecode_float_lin_le" | "fzn_float_lin_le" => Self::Relation {
510                relation: LessEqual,
511                linear: true,
512                mode: Plain,
513            },
514            "float_lin_le_reif" | "gecode_float_lin_le_reif" | "fzn_float_lin_le_reif" => {
515                Self::Relation {
516                    relation: LessEqual,
517                    linear: true,
518                    mode: Reified,
519                }
520            }
521            "float_lin_le_imp" => Self::Relation {
522                relation: LessEqual,
523                linear: true,
524                mode: Implied,
525            },
526            "float_lin_lt" | "gecode_float_lin_lt" | "fzn_float_lin_lt" => Self::Relation {
527                relation: Less,
528                linear: true,
529                mode: Plain,
530            },
531            "float_lin_lt_reif" | "gecode_float_lin_lt_reif" | "fzn_float_lin_lt_reif" => {
532                Self::Relation {
533                    relation: Less,
534                    linear: true,
535                    mode: Reified,
536                }
537            }
538            "float_lin_lt_imp" => Self::Relation {
539                relation: Less,
540                linear: true,
541                mode: Implied,
542            },
543            "float_abs" | "gecode_float_abs" | "fzn_float_abs" => {
544                Self::Arithmetic(FloatOperation::Absolute)
545            }
546            "float_plus" | "gecode_float_plus" | "fzn_float_plus" => {
547                Self::Arithmetic(FloatOperation::Add)
548            }
549            "float_times" | "gecode_float_times" | "fzn_float_times" => {
550                Self::Arithmetic(FloatOperation::Multiply)
551            }
552            "float_div" | "gecode_float_div" | "fzn_float_div" => {
553                Self::Arithmetic(FloatOperation::Divide)
554            }
555            "float_min" | "gecode_float_min" | "fzn_float_min" => {
556                Self::Arithmetic(FloatOperation::Minimum)
557            }
558            "float_max" | "gecode_float_max" | "fzn_float_max" => {
559                Self::Arithmetic(FloatOperation::Maximum)
560            }
561            "float_sqrt" | "gecode_float_sqrt" | "fzn_float_sqrt" => {
562                Self::Arithmetic(FloatOperation::SquareRoot)
563            }
564            "float_acos" | "gecode_float_acos" | "fzn_float_acos" => {
565                Self::Arithmetic(FloatOperation::ArcCosine)
566            }
567            "float_asin" | "gecode_float_asin" | "fzn_float_asin" => {
568                Self::Arithmetic(FloatOperation::ArcSine)
569            }
570            "float_atan" | "gecode_float_atan" | "fzn_float_atan" => {
571                Self::Arithmetic(FloatOperation::ArcTangent)
572            }
573            "float_cos" | "gecode_float_cos" | "fzn_float_cos" => {
574                Self::Arithmetic(FloatOperation::Cosine)
575            }
576            "float_exp" | "gecode_float_exp" | "fzn_float_exp" => {
577                Self::Arithmetic(FloatOperation::Exponential)
578            }
579            "float_ln" | "gecode_float_ln" | "fzn_float_ln" => {
580                Self::Arithmetic(FloatOperation::NaturalLog)
581            }
582            "float_log10" | "gecode_float_log10" | "fzn_float_log10" => {
583                Self::Arithmetic(FloatOperation::Log10)
584            }
585            "float_log2" | "gecode_float_log2" | "fzn_float_log2" => {
586                Self::Arithmetic(FloatOperation::Log2)
587            }
588            "float_sin" | "gecode_float_sin" | "fzn_float_sin" => {
589                Self::Arithmetic(FloatOperation::Sine)
590            }
591            "float_tan" | "gecode_float_tan" | "fzn_float_tan" => {
592                Self::Arithmetic(FloatOperation::Tangent)
593            }
594            "float_acosh" => Self::Arithmetic(FloatOperation::InverseHyperbolicCosine),
595            "float_asinh" => Self::Arithmetic(FloatOperation::InverseHyperbolicSine),
596            "float_atanh" => Self::Arithmetic(FloatOperation::InverseHyperbolicTangent),
597            "float_cosh" => Self::Arithmetic(FloatOperation::HyperbolicCosine),
598            "float_sinh" => Self::Arithmetic(FloatOperation::HyperbolicSine),
599            "float_tanh" => Self::Arithmetic(FloatOperation::HyperbolicTangent),
600            "float_pow" => Self::Arithmetic(FloatOperation::Power),
601            "gecode_float_pow" | "gecode_gecode_float_pow" | "fzn_gecode_float_pow" => {
602                Self::IntegerPower
603            }
604            "gecode_float_pow_base"
605            | "gecode_gecode_float_pow_base"
606            | "fzn_gecode_float_pow_base" => Self::Arithmetic(FloatOperation::FixedBasePower),
607            "array_float_minimum" => Self::ArrayExtremum {
608                extremum: FloatExtremum::Minimum,
609                result_first: true,
610            },
611            "gecode_float_array_min"
612            | "gecode_gecode_float_array_min"
613            | "fzn_gecode_float_array_min" => Self::ArrayExtremum {
614                extremum: FloatExtremum::Minimum,
615                result_first: false,
616            },
617            "array_float_maximum" => Self::ArrayExtremum {
618                extremum: FloatExtremum::Maximum,
619                result_first: true,
620            },
621            "gecode_float_array_max"
622            | "gecode_gecode_float_array_max"
623            | "fzn_gecode_float_array_max" => Self::ArrayExtremum {
624                extremum: FloatExtremum::Maximum,
625                result_first: false,
626            },
627            "int2float" | "gecode_int2float" | "fzn_int2float" => {
628                Self::Conversion(FloatConversion::IntegerToFloat)
629            }
630            "float_ceil" => Self::Conversion(FloatConversion::Ceiling),
631            "float_floor" => Self::Conversion(FloatConversion::Floor),
632            "float_round" => Self::Conversion(FloatConversion::Round),
633            "float_in" => Self::Membership(FloatMembership::Interval),
634            "float_dom" => Self::Membership(FloatMembership::Domain),
635            "float_set_in" => Self::Membership(FloatMembership::Set),
636            "gecode_float_rel_imp_fzn"
637            | "gecode_gecode_float_rel_imp_fzn"
638            | "fzn_gecode_float_rel_imp_fzn" => Self::CodedImplication,
639            _ => return None,
640        })
641    }
642    #[cfg(feature = "prepare")]
643    pub fn signature(self) -> Vec<ArgumentType> {
644        let scalar = |kind, is_var| ArgumentType {
645            kind,
646            is_var,
647            is_array: false,
648        };
649        let array = |kind, is_var| ArgumentType {
650            kind,
651            is_var,
652            is_array: true,
653        };
654        match self {
655            Self::Relation { linear, mode, .. } => {
656                let mut args = if linear {
657                    vec![
658                        array(ValueType::Float, false),
659                        array(ValueType::Float, true),
660                        scalar(ValueType::Float, false),
661                    ]
662                } else {
663                    vec![scalar(ValueType::Float, true); 2]
664                };
665                if mode != RelationMode::Plain {
666                    args.push(scalar(ValueType::Boolean, true));
667                }
668                args
669            }
670            Self::CodedImplication => vec![
671                scalar(ValueType::Float, true),
672                scalar(ValueType::Integer, false),
673                scalar(ValueType::Float, true),
674                scalar(ValueType::Boolean, true),
675            ],
676            Self::Arithmetic(operation) => {
677                let mut args = vec![scalar(ValueType::Float, true); operation.input_count() + 1];
678                if operation == FloatOperation::FixedBasePower {
679                    args[0].is_var = false;
680                }
681                args
682            }
683            Self::IntegerPower => vec![
684                scalar(ValueType::Float, true),
685                scalar(ValueType::Integer, false),
686                scalar(ValueType::Float, true),
687            ],
688            Self::ArrayExtremum { result_first, .. } => {
689                let result = scalar(ValueType::Float, true);
690                let inputs = array(ValueType::Float, true);
691                if result_first {
692                    vec![result, inputs]
693                } else {
694                    vec![inputs, result]
695                }
696            }
697            Self::Conversion(operation) => vec![
698                scalar(operation.input_type(), true),
699                scalar(operation.output_type(), true),
700            ],
701            Self::Membership(FloatMembership::Interval) => vec![
702                scalar(ValueType::Float, true),
703                scalar(ValueType::Float, false),
704                scalar(ValueType::Float, false),
705            ],
706            Self::Membership(FloatMembership::Domain) => vec![
707                scalar(ValueType::Float, true),
708                array(ValueType::Float, false),
709            ],
710            Self::Membership(FloatMembership::Set) => vec![
711                scalar(ValueType::Float, true),
712                scalar(ValueType::FloatSet, false),
713            ],
714        }
715    }
716}
717pub(crate) fn finite_result(value: f64) -> Result<FiniteFloat, &'static str> {
718    FiniteFloat::new(value).map_err(|_| "nonfinite result in float arithmetic")
719}
720pub(crate) fn sum(
721    terms: impl IntoIterator<Item = (FiniteFloat, FiniteFloat)>,
722) -> Result<FiniteFloat, &'static str> {
723    let mut sum = 0.0;
724    for (coefficient, value) in terms {
725        let product = finite_result(coefficient.get() * value.get())?;
726        sum = finite_result(sum + product.get())?.get();
727    }
728    finite_result(sum)
729}