Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve fixed decimals #574

Merged
merged 20 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions compiler/vm/src/heap/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,8 @@ macro_rules! operator_fn {
($name:ident) => {
pub fn $name(&self, heap: &mut Heap, rhs: &Int) -> Self {
match (self, rhs) {
(Int::Inline(lhs), Int::Inline(rhs)) => lhs.$name(heap, *rhs),
(Int::Heap(on_heap), Int::Inline(inline))
| (Int::Inline(inline), Int::Heap(on_heap)) => on_heap.$name(heap, inline.get()),
(Int::Inline(lhs), _) => lhs.$name(heap, *rhs),
(Int::Heap(lhs), Int::Inline(rhs)) => lhs.$name(heap, rhs.get()),
(Int::Heap(lhs), Int::Heap(rhs)) => lhs.$name(heap, rhs.get()),
}
}
Expand Down
30 changes: 26 additions & 4 deletions compiler/vm/src/heap/object_inline/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ impl InlineInt {
Tag::create_ordering(heap, ordering)
}

operator_fn!(shift_left, i64::checked_shl, Shl::shl);
operator_fn!(shift_right, i64::checked_shr, Shr::shr);
shift_fn!(shift_left, i64::checked_shl, Shl::shl);
shift_fn!(shift_right, i64::checked_shr, Shr::shr);

pub fn bit_length(self) -> Self {
// SAFETY: The `bit_length` can be at most 62 since that's how large an [InlineInt] can get.
Expand All @@ -90,7 +90,29 @@ impl InlineInt {

macro_rules! operator_fn {
($name:ident, $inline_operation:expr, $bigint_operation:expr) => {
pub fn $name(self, heap: &mut Heap, rhs: Self) -> Int {
pub fn $name(self, heap: &mut Heap, rhs: Int) -> Int {
let lhs = self.get();
match rhs {
Int::Inline(rhs) => rhs
.try_get()
.and_then(|rhs| $inline_operation(lhs, rhs))
.map(|it| Int::create(heap, it))
.unwrap_or_else(|| {
Int::create_from_bigint(
heap,
$bigint_operation(BigInt::from(lhs), rhs.get()),
)
}),
Int::Heap(rhs) => {
Int::create_from_bigint(heap, $bigint_operation(BigInt::from(lhs), rhs.get()))
}
}
}
};
}
macro_rules! shift_fn {
($name:ident, $inline_operation:expr, $bigint_operation:expr) => {
pub fn $name(self, heap: &mut Heap, rhs: InlineInt) -> Int {
let lhs = self.get();
rhs.try_get()
.and_then(|rhs| $inline_operation(lhs, rhs))
Expand All @@ -109,7 +131,7 @@ macro_rules! operator_fn_closed {
}
};
}
use {operator_fn, operator_fn_closed};
use {operator_fn, operator_fn_closed, shift_fn};

impl DebugDisplay for InlineInt {
fn fmt(&self, f: &mut Formatter, _is_debug: bool) -> fmt::Result {
Expand Down
2 changes: 1 addition & 1 deletion packages/Builtins/_.candy
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ textGetRange text startInclusive endExclusive :=
needs (text | typeIs Text)
needs (startInclusive | typeIs Int)
needs (startInclusive | isNonNegative)
needs (startInclusive | intCompareTo (text | ✨.textLength) | equals Less)
needs (startInclusive | isLessThanOrEqualTo (text | ✨.textLength))
needs (endExclusive | typeIs Int)
needs (endExclusive | isNonNegative)
needs (endExclusive | isLessThanOrEqualTo (text | ✨.textLength))
Expand Down
95 changes: 72 additions & 23 deletions packages/Core/fixedDecimal.candy
Original file line number Diff line number Diff line change
@@ -1,51 +1,99 @@
# TODO: As soon as tags or value hiding is supported, change fixed point numbers
# to that. Currently, they're just normal ints.

bool = use "..bool"
[check] = use "..check"
[ifElse, recursive] = use "..controlFlow"
[equals] = use "..equality"
[run] = use "..function"
int = use "..int"
struct = use "..struct"
tag = use "..tag"
text = use "..text"
[toDebugText] = use "..toDebugText"

decimalsPow = 10000000
# TODO: Perhaps allow different levels of precision.
isScale scale = int.is scale | bool.lazyAnd { int.isNonNegative scale }
scaleFactor scale =
needs (isScale scale)
10 | int.pow scale

is a := a %
FixedDecimal [minorUnits, scale] -> int.is minorUnits | bool.lazyAnd { isScale scale}
_ -> False

is := int.is
minorUnits a :=
needs (is a)
(a | tag.getValue).minorUnits
scale a :=
needs (is a)
(a | tag.getValue).scale

fromIntScaled minorUnits scale :=
needs (int.is minorUnits)
needs (isScale scale)
FixedDecimal [minorUnits, scale]
fromInt a :=
needs (int.is a)
a | int.multiply decimalsPow
fromIntScaled a 0
floorToInt a :=
needs (is a)
a | int.divideTruncating decimalsPow
a | minorUnits | int.divideTruncating (a | scale | scaleFactor)

rescaledMinorUnits a targetScale =
needs (is a)
needs (isScale targetScale)
ifElse
a | scale | int.isGreaterThan targetScale
{ a | minorUnits | int.divideTruncating (scaleFactor (a | scale | int.subtract targetScale)) }
{ a | minorUnits | int.multiply (scaleFactor (targetScale | int.subtract (a | scale))) }
rescale a targetScale :=
needs (is a)
needs (isScale targetScale)
fromIntScaled (a | rescaledMinorUnits targetScale) targetScale
comparableMinorUnits valueA valueB =
needs (is valueA)
needs (is valueB)
targetScale = int.max (valueA | scale) (valueB | scale)
[
ValueA: rescaledMinorUnits valueA targetScale,
ValueB: rescaledMinorUnits valueB targetScale,
targetScale,
]

add summandA summandB :=
needs (is summandA)
needs (is summandB)
summandA | int.add summandB
[valueA, valueB, targetScale] = comparableMinorUnits summandA summandB
fromIntScaled (valueA | int.add valueB) targetScale
subtract minuend subtrahend :=
needs (is minuend)
needs (is subtrahend)
minuend | int.subtract subtrahend
[valueA, valueB, targetScale] = comparableMinorUnits minuend subtrahend
fromIntScaled (valueA | int.subtract valueB) targetScale
negate value :=
needs (is value)
value | int.negate
fromIntScaled (value | minorUnits | int.negate) (value | scale)
multiply factorA factorB :=
needs (is factorA)
needs (is factorB)
factorA | int.multiply factorB | int.divideTruncating decimalsPow
divide dividend divisor :=
fromIntScaled
factorA | minorUnits | int.multiply (factorB | minorUnits)
factorA | scale | int.add (factorB | scale)
divideTruncating dividend divisor :=
needs (is dividend)
needs (is divisor)
needs (divisor | minorUnits | equals 0 | bool.not) "You can't divide by zero."
[valueA, valueB, targetScale] = comparableMinorUnits dividend divisor
fromIntScaled (valueA | int.multiply (targetScale | scaleFactor) | int.divideTruncating valueB) targetScale
divideTruncatingAtScale dividend divisor targetScale :=
needs (is dividend)
needs (is divisor)
needs (divisor | equals 0 | bool.not) "You can't divide by zero."
dividend | int.multiply decimalsPow | int.divideTruncating divisor
needs (isScale targetScale)
needs (divisor | minorUnits | equals 0 | bool.not) "You can't divide by zero."
[valueA, valueB] = comparableMinorUnits dividend divisor
fromIntScaled (valueA | int.multiply (targetScale | scaleFactor) | int.divideTruncating valueB) targetScale

compareTo valueA valueB :=
needs (is valueA)
needs (is valueB)
[valueA, valueB] = comparableMinorUnits valueA valueB
result = valueA | int.compareTo valueB
check (equals result Equal | bool.implies (equals valueA valueB))
result
Expand All @@ -68,13 +116,13 @@ isGreaterThanOrEqualTo valueA valueB :=

isPositive value :=
needs (is value)
value | isGreaterThan 0
value | minorUnits | int.isGreaterThan 0
isNonPositive value :=
needs (is value)
value | isPositive | bool.not
isNegative value :=
needs (is value)
value | isLessThan 0
value | minorUnits | int.isLessThan 0
isNonNegative value :=
needs (is value)
value | isNegative | bool.not
Expand All @@ -87,7 +135,7 @@ approxEquals a b delta :=
needs (is b)
needs (is delta)
needs (isNonNegative delta)
a | int.subtract b | int.absolute | int.isLessThanOrEqualTo delta
a | subtract b | absolute | isLessThanOrEqualTo delta

min valueA valueB :=
needs (is valueA)
Expand All @@ -114,11 +162,12 @@ coerceIn value minimum maximum :=

toText a :=
needs (is a)
beforeDot = a | floorToInt | toDebugText
beforeDot = a | floorToInt
afterDot = run {
tmp = a | int.remainder decimalsPow
ifElse (isNonNegative tmp) { tmp | int.add decimalsPow | toDebugText | text.removePrefix "1" } {
tmp | int.subtract decimalsPow | toDebugText | text.removePrefix "-1"
scaleFactor = a | scale | scaleFactor
tmp = a | minorUnits | int.remainder scaleFactor
ifElse (int.isNonNegative tmp) { tmp | int.add scaleFactor | toDebugText | text.removePrefix "1" } {
tmp | int.subtract scaleFactor | toDebugText | text.removePrefix "-1"
}
}
"{beforeDot}.{afterDot}"
"{beforeDot}.{afterDot}"
21 changes: 20 additions & 1 deletion packages/Core/int.candy
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
builtins = use "Builtins"
bool = use "..bool"
[check] = use "..check"
[ifElse] = use "..controlFlow"
[ifElse, recursive] = use "..controlFlow"
[equals] = use "..equality"
type = use "..type"

Expand Down Expand Up @@ -115,3 +115,22 @@ coerceIn value minimum maximum :=
value | coerceAtLeast minimum | coerceAtMost maximum

parse := builtins.intParse

pow base exponent :=
needs (is base)
needs (is exponent)
needs (isNonNegative exponent)
recursive [base, exponent] { recurse params ->
[base, exponent] = params
exponent %
0 -> 1
_ ->
ifElse
exponent | isEven
{ recurse [Base: base | multiply base, Exponent: exponent | shiftRight 1] }
{
base
| multiply
recurse [Base: base | multiply base, Exponent: exponent | subtract 1 | shiftRight 1]
}
}
20 changes: 13 additions & 7 deletions packages/examples/sqrt.candy
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
builtins = use "Builtins"
[equals, fixedDecimal, ifElse, recursive] = use "Core"
[equals, fixedDecimal, ifElse, int, recursive] = use "Core"

sqrt x :=
sqrt x precision :=
needs (fixedDecimal.is x)
needs (fixedDecimal.isNonNegative x)
needs (int.is precision)
needs (int.isNonNegative precision)

recursive (x | fixedDecimal.divide (2 | fixedDecimal.fromInt)) { recurse guess ->
delta = 10 | fixedDecimal.fromIntScaled precision

recursive (x | fixedDecimal.divideTruncatingAtScale (2 | fixedDecimal.fromInt) precision) {
recurse guess ->
refinedGuess =
fixedDecimal.divide
guess | fixedDecimal.add (x | fixedDecimal.divide guess)
fixedDecimal.divideTruncatingAtScale
guess | fixedDecimal.add (x | fixedDecimal.divideTruncatingAtScale guess precision)
2 | fixedDecimal.fromInt
ifElse (fixedDecimal.approxEquals guess refinedGuess 10) { guess } { recurse refinedGuess }
precision
ifElse (fixedDecimal.approxEquals guess refinedGuess delta) { guess } { recurse refinedGuess }
}

main _ :=
input = 2
result = input | fixedDecimal.fromInt | sqrt
result = input | fixedDecimal.fromInt | sqrt 65
builtins.print "The root of {input} is {result | fixedDecimal.toText}"