Skip to content
Closed
15 changes: 4 additions & 11 deletions numbat/src/bytecode_interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ use crate::interpreter::{
};
use crate::name_resolution::LAST_RESULT_IDENTIFIERS;
use crate::prefix::Prefix;
use crate::prefix_parser::AcceptsPrefix;
use crate::pretty_print::PrettyPrint;
use crate::typed_ast::{
BinaryOperator, DefineVariable, Expression, Statement, StringPart, UnaryOperator,
};
use crate::unit::{CanonicalName, Unit};
use crate::unit::Unit;
use crate::unit_registry::{UnitMetadata, UnitRegistry};
use crate::value::{FunctionReference, Value};
use crate::vm::{Constant, ExecutionContext, Op, Vm};
Expand Down Expand Up @@ -51,7 +50,7 @@ impl BytecodeInterpreter {
fn compile_expression(&mut self, expr: &Expression) -> Result<()> {
match expr {
Expression::Scalar(_span, n, _type) => {
let index = self.vm.add_constant(Constant::Scalar(n.to_f64()));
let index = self.vm.add_constant(Constant::scalar_from_f64(n.to_f64()));
self.vm.add_op1(Op::LoadConstant, index);
}
Expression::Identifier(_span, identifier, _type) => {
Expand Down Expand Up @@ -421,13 +420,7 @@ impl BytecodeInterpreter {
.map(|(name, ap)| (name.to_compact_string(), ap))
.collect();

let constant_idx = self.vm.add_constant(Constant::Unit(Unit::new_base(
CompactString::const_new("<dummy>"),
CanonicalName {
name: CompactString::const_new("<dummy>"),
accepts_prefix: AcceptsPrefix::both(),
},
))); // TODO: dummy is just a temp. value until the SetUnitConstant op runs
let constant_idx = self.vm.add_dummy_constant(); // TODO: dummy is just a temp. value until the SetUnitConstant op runs
let unit_information_idx = self.vm.add_unit_information(
unit_name,
Some(
Expand Down Expand Up @@ -530,7 +523,7 @@ impl BytecodeInterpreter {
pub fn get_defining_unit(&self, unit_name: &str) -> Option<&Unit> {
self.unit_name_to_constant_index
.get(unit_name)
.and_then(|idx| self.vm.constants.get(*idx as usize))
.and_then(|idx| self.vm.constants.get_index(*idx as usize))
.and_then(|constant| match constant {
Constant::Unit(u) => Some(u),
_ => None,
Expand Down
56 changes: 40 additions & 16 deletions numbat/src/number.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
use std::fmt::Display;
use std::fmt::{self, Debug, Display};

use compact_str::{format_compact, CompactString, ToCompactString};
use num_traits::{Pow, ToPrimitive};
use pretty_dtoa::FmtFloatConfig;

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] // TODO: we probably want to remove 'Copy' once we move to a more sophisticated numerical type
pub struct Number(pub f64);
/// A type that acts like an `f64`
///
/// To make this type `Hash` and `Eq`, we actually store a `u64`. To convert to and from `f64`
/// (which is the actual value we care about), we use the [`f64::from_bits`] and
/// [`f64::to_bits`] functions.
///
/// Note that we can't derive PartialEq because some f64 with different bits represent
/// the same value (e.g. `0.0` and `-0.0`).
#[derive(Clone, Copy, Eq)] // TODO: we probably want to remove 'Copy' once we move to a more sophisticated numerical type
pub struct Number(u64);

impl Debug for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Number").field(&self.to_f64()).finish()
}
}

impl Eq for Number {}
impl PartialEq for Number {
fn eq(&self, other: &Self) -> bool {
self.to_f64() == other.to_f64()
}
}

impl PartialOrd for Number {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_f64().partial_cmp(&other.to_f64())
}
}

impl Number {
pub fn from_f64(n: f64) -> Self {
Number(n)
Number(n.to_bits())
}

pub fn to_f64(self) -> f64 {
let Number(n) = self;
n
f64::from_bits(n)
}

pub fn pow(self, other: &Number) -> Self {
Expand All @@ -28,7 +52,7 @@ impl Number {
}

fn is_integer(self) -> bool {
self.0.trunc() == self.0
self.to_f64().trunc() == self.to_f64()
}

/// Pretty prints with default options
Expand All @@ -40,19 +64,19 @@ impl Number {
/// If options is None, default options will be used.
/// If options is not None, float-based format handling is used and integer-based format handling is skipped.
pub fn pretty_print_with_options(self, options: Option<FmtFloatConfig>) -> CompactString {
let number = self.0;
let number = self.to_f64();

// 64-bit floats can accurately represent integers up to 2^52 [1],
// which is approximately 4.5 × 10^15.
//
// [1] https://stackoverflow.com/a/43656339
//
// Skip special format handling for integers if options is not None.
if options.is_none() && self.is_integer() && self.0.abs() < 1e15 {
if options.is_none() && self.is_integer() && self.to_f64().abs() < 1e15 {
use num_format::{CustomFormat, Grouping, ToFormattedString};

let format = CustomFormat::builder()
.grouping(if self.0.abs() >= 100_000.0 {
.grouping(if self.to_f64().abs() >= 100_000.0 {
Grouping::Standard
} else {
Grouping::Posix
Expand Down Expand Up @@ -108,47 +132,47 @@ impl Number {

impl Display for Number {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_f64().fmt(f)
Display::fmt(&self.to_f64(), f)
}
}

impl std::ops::Add for Number {
type Output = Number;

fn add(self, rhs: Self) -> Self::Output {
Number(self.0 + rhs.0)
Number::from_f64(self.to_f64() + rhs.to_f64())
}
}

impl std::ops::Sub for Number {
type Output = Number;

fn sub(self, rhs: Self) -> Self::Output {
Number(self.0 - rhs.0)
Number::from_f64(self.to_f64() - rhs.to_f64())
}
}

impl std::ops::Mul for Number {
type Output = Number;

fn mul(self, rhs: Self) -> Self::Output {
Number(self.0 * rhs.0)
Number::from_f64(self.to_f64() * rhs.to_f64())
}
}

impl std::ops::Div for Number {
type Output = Number;

fn div(self, rhs: Self) -> Self::Output {
Number(self.0 / rhs.0)
Number::from_f64(self.to_f64() / rhs.to_f64())
}
}

impl std::ops::Neg for Number {
type Output = Number;

fn neg(self) -> Self::Output {
Number(-self.0)
Number::from_f64(-self.to_f64())
}
}

Expand Down
2 changes: 1 addition & 1 deletion numbat/src/prefix_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub enum PrefixParserResult<'a> {

type Result<T> = std::result::Result<T, NameResolutionError>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AcceptsPrefix {
pub short: bool,
pub long: bool,
Expand Down
9 changes: 9 additions & 0 deletions numbat/src/product.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::hash::Hash;
use std::ops::{Div, Mul};

use crate::arithmetic::{Exponent, Power};
Expand Down Expand Up @@ -235,6 +236,14 @@ impl<Factor: Clone + Ord + PartialEq + Canonicalize, const CANONICALIZE: bool> P
}
}

impl<Factor: Clone + Ord + Hash + Canonicalize, const CANONICALIZE: bool> Hash
for Product<Factor, CANONICALIZE>
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.canonicalized().factors.hash(state);
}
}

impl<Factor: Clone + Ord + Canonicalize + Eq, const CANONICALIZE: bool> Eq
for Product<Factor, CANONICALIZE>
{
Expand Down
17 changes: 9 additions & 8 deletions numbat/src/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ use crate::{
prefix::Prefix,
prefix_parser::AcceptsPrefix,
product::{Canonicalize, Product},
vm::HashableNumber,
};

pub type ConversionFactor = Number;

/// A unit can either be a base/fundamental unit or it is derived from another unit.
/// In the latter case, a conversion factor to the defining unit has to be specified.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum UnitKind {
Base,
Derived(ConversionFactor, Unit),
Derived(HashableNumber, Unit),
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CanonicalName {
pub name: CompactString,
pub accepts_prefix: AcceptsPrefix,
Expand All @@ -37,7 +38,7 @@ impl CanonicalName {
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnitIdentifier {
pub name: CompactString,
pub canonical_name: CanonicalName,
Expand Down Expand Up @@ -66,7 +67,7 @@ impl UnitIdentifier {
Number::from_f64(1.0),
),
UnitKind::Derived(factor, defining_unit) => {
BaseUnitAndFactor(defining_unit.clone(), *factor)
BaseUnitAndFactor(defining_unit.clone(), *factor.number())
}
}
}
Expand Down Expand Up @@ -98,7 +99,7 @@ impl UnitIdentifier {
)
.product();

BaseUnitAndFactor(base_unit, *factor * defining_unit_factor)
BaseUnitAndFactor(base_unit, *factor.number() * defining_unit_factor)
}
}
}
Expand Down Expand Up @@ -166,7 +167,7 @@ impl Ord for UnitIdentifier {
}
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnitFactor {
pub unit_id: UnitIdentifier,
pub prefix: Prefix,
Expand Down Expand Up @@ -255,7 +256,7 @@ impl Unit {
unit_id: UnitIdentifier {
name,
canonical_name,
kind: UnitKind::Derived(factor, base_unit),
kind: UnitKind::Derived(factor.into(), base_unit),
},
exponent: Rational::from_integer(1),
})
Expand Down
2 changes: 1 addition & 1 deletion numbat/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
list::NumbatList, pretty_print::PrettyPrint, quantity::Quantity, typed_ast::StructInfo,
};

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FunctionReference {
Foreign(CompactString),
Normal(CompactString),
Expand Down
Loading