<p>From <a href="https://rtfeldman.com/0.1-plus-0.2" rel="noreferrer">https://rtfeldman.com/0.1-plus-0.2</a>, on different floating point number semantics:</p>
<blockquote>
<p>Go takes a different approach. When you write 0.1 + 0.2 in Go, that expression gets evaluated to 0.3 at compile time with no precision loss. If you evaluate 0.1 + 0.2 == 0.3, you'll get true. However, if you use variables instead of constants—that is, set a := 0.1 and b := 0.2 and print a + b—you'll once again see 0.30000000000000004. This is because Go evaluates variables at runtime differently from constants at compile time; constants use precise (but slower) base-10 addition, whereas variables use the same base-2 floating-point math as Python, Java, JavaScript, and so on.</p>
</blockquote>
<p>To me, the same expression evaluating to different values in compile time and in runtime sounds like a bad idea, as it can make the same code behave differently and generate inconsistent or incompatible results depending on when it was evaluated.</p>
<p>Are there any advantages of doing this? Why does Go do it this way?</p>
<p>The only advantage that I can think of is: if I have a constant evaluator that works the same regardless of the target architecture, I can ship just one pre-compiled standard library (to some intermediate representation) for all targets, which can make cross compilation easier.</p>
<p>Note: the quote is about floating point semantics but I'm wondering about the general idea of having different semantics for expressions in compile time and runtime.</p>

<language-design><semantics><constant-expressions>
Are there any advantages of evaluating expressions differently in compile time and runtime?
CC BY-SA 4.0
2024-05-28T19:20:18.343Z
I agree with you, but there are "least surprise" arguments both ways, so you pick your poison.
Barmar
CC BY-SA 4.0
2024-05-28T19:26:11.497Z
The [Go Language Spec](https://go.dev/ref/spec#Constants) doesn't seem to support this claim; it requires that implementations "Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed binary exponent of at least 16 bits."
IMSoP
CC BY-SA 4.0
2024-05-29T20:58:09.657Z
The quote does not describe the same expression being evaluated differently at runtime and at compile time.  It describes *different*, related expressions being evaluated differently.  That doesn't invalidate the question, but we should be clear about the situation.
John Bollinger
CC BY-SA 4.0
2024-05-30T01:17:30.810Z
Is this question asking about any other types of expressions than floating-point expressions, inexactness, arbitrary precision? in particular where the root-cause is minimum and actual precision (and maybe order-of-evaluation)? We could also construct examples with integer overflow (MAXINT/2 + MAXINT/2  - MAXINT/2), for languages where left-to-right evaluation is not compulsory (think of a vexatious constant defined as (MAXINT/2  - MAXINT/2)). Or stressing FP overflow and underflow.
smci
CC BY-SA 4.0
2024-05-30T01:22:17.310Z
...and when you ask "Are there *any* advantages..." are you only asking about precision of the final result (or failing to overflow/underflow where that was potentially avoidable), or implying that allowing a compiler/runtime some free choice in order-of-evaluation might in some cases be desirable, sacricing speed for accuracy? I don't think "Are there *any* advantages..." is well-posed, obviously there are advantages, disadvantages and tradeoffs; please restate it.
smci
CC BY-SA 4.0
2024-05-30T01:23:46.090Z
...also, more precision in the final answer is not always necessarily better, if compatibility with existing implementations also matters, such as expressions which existing implementations evaluate to 0.3 instead of 0.30000000000000004. (Or conversely, do we want to stress-test the effects of what a language's spec allows for in optional precision? It depends what you mean by "advantages")
smci
CC BY-SA 4.0
2024-05-30T01:43:44.983Z
...obvious example is code using floating-point for currency (or other finite-precision) amounts, where "your change is \$0.3" is "more correct" than "\$0.30000000000000004". (How to handle if that "correctness" depended on implementation-specifics rather than language spec?) Or, floats being passed around/converted to/from strings e.g. JSON parsing
smci
CC BY-SA 4.0
2024-05-30T06:50:26.557Z
How do you judge the relative advantages of the two approaches, given that the allegedly better one is available only for decimal rationals? (That is, rationals whose denominator divides a power of 10.) The approach that handles 3/10 so nicely is not available for 1/3 or 1/7, for example.
Rosie F
CC BY-SA 4.0
2024-05-30T13:58:37.377Z
@RosieF the question is not really that much focused on floating point semantics, I'm interested in any kind of differences between compile time and runtime semantics of expression (or other constructors), and why would one want to implement these differences.
osa1
<p>Evaluating the same expression differently at compile time versus runtime would indeed be horrendous behavior.  But, that's not really what's going on here.</p>
<p>Go supports &quot;untyped&quot; floating point expressions.  These are floating point expressions that are not associated with a specific, fixed precision representation like <code>float32</code> or <code>float64</code>, and they are evaluated as if the underlying type had infinite precision.</p>
<p>(Or at least that's the idea.  Go actually leaves some of the details as an &quot;implementation restriction&quot;. Looking at the actual implementation for the &quot;usual&quot; compiler in <a href="https://github.com/golang/go/blob/master/src/go/constant/value.go" rel="noreferrer">src/go/constant/value.go</a>, it looks like Go tries to use rational numbers where reasonable or &quot;big floats&quot; from <code>math/big</code> otherwise.  So, <code>0.1</code>, <code>0.2</code>, and <code>0.3</code> are likely represented exactly as rational numbers, while <code>sqrt(2)</code> probably gets a big float representation).</p>
<p>Anyway, it turns out that only constant expressions are allowed to be &quot;untyped&quot; like this, and so such expressions <em>can be</em> and <em>are</em> evaluated at compile time, but this is an implementation detail.  The bottom line is that the expression <code>0.3 - 0.1 - 0.2</code> in Go is an untyped expression that evaluates to an untyped floating point value that is precisely zero, while the expression <code>a - 0.1 - 0.2</code> (for <code>const a float64 = 0.3</code>) is a <code>float64</code> expression that evaluates to around <code>-2.78e-17</code>.  They are different expressions (or, if you prefer, the &quot;same&quot; expression evaluated using two different floating point types), and whether they are evaluated at compile time or runtime, their values will be different.</p>
<p>This is not entirely without precedent.  In Haskell, the numeric literal <code>0.3</code> is internally represented as a polymorphic function call that retains the full precision of the value independent of any specific representation:</p>
<pre><code>fromRational (3 % 10) :: Num a =&gt; a
</code></pre>
<p>Consequently, if the Haskell expression <code>0.3 - 0.1 - 0.2</code>, is evaluated in a context where the floating point type is <code>Double</code>, the exact representations of the literals will be converted to inexact <code>Double</code> representations, and the expression will evaluate to some non-zero fuzz (at compile time or runtime, depending on how smart the compiler is).  On the other hand, if the expression is evaluated in a context where the floating point type is <code>Rational</code> (the type of rational numbers), the expression will evaluate to exactly zero:</p>
<pre><code>{-# LANGUAGE NoMonomorphismRestriction #-}

main = do
  let expr = 0.3 - 0.1 - 0.2
  print (expr :: Double)             -- -2.78e-17
  print (expr :: Rational)           -- 0 % 1 (i.e., fraction 0/1)
</code></pre>
<p>If you're unfamiliar with Haskell, note that the <code>::</code> syntax is not a &quot;cast&quot;.  It's a type signature providing guidance to the type checker to resolve the types -- not only of the overall expression <code>expr</code> -- but of all of the numeric literals and operators within that expression.</p>
<p>So, here, <code>expr</code> represents a &quot;loosely typed&quot; expression containing conceptually infinite precision numeric literals.  When it is evaluated at a concrete type, the resulting computation may or may not be precise, depending on the nature of the concrete type.</p>
<p>The advantage in Haskell is that a literal like <code>0.3</code>, which isn't intended to be tied to any specific numeric representation, can always be used to select the &quot;best representation of <code>0.3</code>&quot; for whatever concrete type it is ultimately assigned, without the loss of precision that might take place if it was first converted to a <code>double</code>.</p>

CC BY-SA 4.0
2024-05-28T19:35:08.470Z
2024-05-30T15:39:56.337Z
2024-05-30T15:39:56.337Z
K. A. Buhr
CC BY-SA 4.0
2024-05-29T04:58:56.143Z
If only constants can be in this magic untyped state, isn’t that de-facto equivalent to "Evaluating the same expression differently at compile time versus runtime"?
MisterMiyagi
CC BY-SA 4.0
2024-05-29T09:52:42.867Z
@MisterMiyagi No, because the same expression can't be both constant and not constant.
kaya3
CC BY-SA 4.0
2024-05-29T18:49:51.850Z
Note that you can write `const a = float64(0.3)-0.1-0.2` and get `-2.78e-17`, presumably at compile time.
K. A. Buhr
CC BY-SA 4.0
2024-05-30T05:42:32.983Z
I want to note - Go uses **incredibly** confusing terminology here, well at odds with the conventional jargon. These "untyped" values do have what for all intents and purposes are types. Go calls these "kinds" (aargh!) and defines a one-way convertibility relation between them [in the specification](https://go.dev/ref/spec#Constant_expressions): from rune to integer to floating-point to complex. (The spec actually says that integers coerce to runes - this appears to be incorrect.)
apropos
CC BY-SA 4.0
2024-05-30T05:46:54.543Z
You will notice the lack of "untyped" strings or "untyped" bools in that list. This is because they do not coerce to any other "untyped" type. Attempting to compile the expression `const a = 4 * "hello"` results in the humorous error message `mismatched types untyped string and untyped int`
apropos
CC BY-SA 4.0
2024-05-30T14:42:36.593Z
'In practice, an implementation probably wouldn't use "base-10 addition" as suggested in that blog post you cite, but would instead just use a representation with much more precision than a float64 to give the illusion of infinite precision.' - there is no base-2^n floating point representation of 0.3 that has infinite precision.  In practice, the compiler needs to use rational-style or base-10 to here.
Yakk - Adam Nevraumont
CC BY-SA 4.0
2024-05-30T15:41:15.577Z
I've updated the answer, after taking a look at `constant/value.go` in the compiler source.
K. A. Buhr
<p>Numbers are the quintessential value objects. <span class="math-container">$1+1=2$</span> anywhere in the world, but numbers in computers work differently.</p>
<p><span class="math-container">$1+1=2$</span> in <code>u8</code> and <code>u64</code>, but <span class="math-container">$1+1=0$</span> in <code>u1</code>, for the same reason that <span class="math-container">$255+1=0$</span> in <code>u8</code>. That is, number <em>realizations</em> in computers do not follow the same rules and identities of pure abstract numbers of pure abstract mathematics.</p>
<p>In source code, constants are special. Because constant folding can generate huge savings by successive code reductions, to the point of rendering entire functions, even entire programs, into a constant result that executes no code at all, it's very profitable to pour a lot of effort into these optimizations.</p>
<p>This is where abstract numbers and constants meet. As constants can be folded in successive rounds, this also can be done for numbers and number operations. But it is necessary to always keep in mind the above notion, that operations on number realizations generate different results than may be expected from abstract numbers.</p>
<p>An <code>if ( 0.1 + 0.2 == 0.3 )</code> will probably <strong>fail</strong> in any computer that follows IEEE 734, because some of these numbers are not <em>representable</em> in <strong>binary</strong> floating point format. A <strong>decimal</strong> number that is not representable is <em>aproximated into another number</em>, and because of that, decimal identities do not hold anymore.</p>
<p>As you can imagine, this is bad for constant folding and branch eliminations, as exemplified on the <code>if()</code> above, and will run against human intuition.</p>
<p>So there is an appeal to treat <em>numeric constants</em> differently than turning them into realized numbers immediately for operations. By treating these <em>source numbers</em> as <code>ComptimeBigDecimal</code>, or even better, as <code>ComptimeFraction</code>, it is possible to have intuitive, previsible and stable results for additions, subtractions and multiplications, and is also possible to minimize the imprecision accumulation of operations and to postnote imprecision of divisions to one last <code>Fraction</code> into number realization format.</p>
<p>But as you noted, it is <strong>bad</strong> that number expressions generate different results in compile time and run time.</p>
<p>If a compiler treats number constants differently in compilation time, it also should expose this functionality as a library, and by doing so, be possible to replicate the results of compile time in run time.</p>
<p>But none of this will change the reality of existence, and even preference, of realized number types, with all the imperfections noted above. Because these realizated number formats are <em>fast</em>, sometimes <em>hardware fast</em>, and no simulated integer/decimal/fractional code can reach this performance.</p>
<p>Beyond these concerns, it's advantageous to treat source code numbers differently from numbers realized at runtime, <em>even in</em> the absence of number operations.</p>
<p>This is an example I recently encountered in Python:</p>
<pre><code>&gt;&gt;&gt; from fractions import Fraction
&gt;&gt;&gt; Fraction(1.65)
Fraction(3715469692580659, 2251799813685248)
&gt;&gt;&gt; Fraction(&quot;1.65&quot;)
Fraction(33, 20)
</code></pre>
<p>Why did <span class="math-container">$1.65$</span> get converted to such a weird fraction, even though it can be easily converted to <span class="math-container">$100/65$</span>? Because the decimal constant <code>1.65</code> in the source code was first converted to a <code>double</code>, and <em>then</em> this other number was precisely converted in a decimal fraction.</p>
<p>By simply delaying the source code number literal realization, it would be possible to code <code>Fraction()</code> to accept a <code>comptime_decimal_literal</code>, that in turn would result in both expressions above reaching an exact fraction of <span class="math-container">$1.65$</span>, instead of the approximate fraction of <span class="math-container">$3715469692580659/2251799813685248$</span>.</p>

CC BY-SA 4.0
2024-05-28T21:38:14.480Z
2024-05-31T22:01:06.920Z
2024-05-31T22:01:06.920Z
André L F S Bacci
<p>One of the design goals of the Go language is to be easy of use. It aims to be a prototyping language like Python, but still remain efficient by having a compiler and static typing.</p>
<p>This feature fits perfectly in the spirit of a prototyping language: worry less about the small implementation details. By having 0.1 + 0.2 evaluate <em>exactly</em> to 0.3, the developer does not have to think about floating point arithmetic in this specific circumstance and this allows for faster prototyping.</p>
<p>In programming languages there is often the trade-off of between following the programmers intent and of being precise. On one end of the spectrum, Python tries to follow the programmers intent. When evaluating expressions, it tries to predict what the programmer <em>wants</em>, not what would be the most logical result. This greatly improves developing speed and developer experience (up to debate). The downside is that Python is slow and can be unpredictable.</p>
<p>On the other end is Rust. Rust is very precise: it evaluates according to strict rules. The end result is always predictable and any piece of code leaves little doubt about what exactly is happening under the hood. Because of this, Rust code is often fast, robust and reliable. But the downside is that it can take quite some effort to express the things you want using the - relatively - small space of valid rust.</p>
<p>So on the continuum between following intent and precision, this implementation detail is closer to following intent. It makes it harder to predict what the code does, but maybe we don't want to predict what the code does. We just want it to <em>work</em>. So is this is a good thing? I would say it depends use case in the same way that Python is great for specific purposes.</p>

CC BY-SA 4.0
2024-05-31T08:08:31.140Z
2024-05-31T08:08:31.140Z
AccidentalTaylorExpansion
<p>If it has advantages, a better approximation would be to have a new type for decimals up to any finite precision, or just fractions. The new type doesn't need to be restricted to compile time. Once it gets assigned to a float, it gets truncated.</p>
<p>I don't think the difference between compile time and runtime is a big problem, as people should already learn the floating-point accuracy problem, and if they don't, they would get a surprise sooner or later. It's less of a problem if we use a different type, as it has a clear semantic and doesn't really add anything special.</p>
<p>But there are some other possible surprises:</p>
<ul>
<li>Decimals have the implication of being possibly inaccurate. It could be automatically cast to floating points in some cases, where you would want to keep the original value for fraction number types. But you could solve it by adding a tag to the type to distinguish them, if your language also has fractions.</li>
<li>Some functions might be much slower in arbitrary precision than floats. If you don't like distinguishing the functions by names, you could solve it by also matching the expected output type, or branching based on the value range. They may evolve into powerful features. Sometimes your language might already have them. But if it didn't, you may not think it is worth the extra work and complexity.</li>
<li>It's not sure what to do with <code>x + 0.1 + 0.2 == x + 0.3</code> where <code>x</code> is a float. You may define it to anything as long as it is consistent. But  by the slight more difficulty to understand it, it is likely to ruin what you have thought that could make the language better in this way.</li>
</ul>
<p>The advantage could be to make the computation more precise in compile time, where you would allow it to use more time, because the work is reused, and to keep the efficiency at runtime. But firstly, the little more precision usually just doesn't matter. Secondly, if you really want to do this, you could do more, by using faster computations in the most inner loops and more accurate computations elsewhere. It just isn't useful if you only do it in one unimportant case.</p>
<p>But I had another idea. I would advocate also supporting fixed-point numbers (fractions with constant denominator, and with easier representation) in newer practical languages, as it could reduce mistakes in many situations such as currency. If your language has fixed-point numbers, apparently it has to work like this. And if your language may add support for fixed-point numbers later, it would create a bigger surprise if it didn't.</p>
<p>In general, you should better distinguish them by the type, instead of compile time versus runtime. But it is understandable to do it in this way if the language don't have fixed-points yet, and you only leave the option for later.</p>

CC BY-SA 4.0
2024-05-29T00:01:40.903Z
2024-05-29T00:01:40.903Z
user23013
# Are there any advantages of evaluating expressions differently in compile time and runtime?
Posted by: username_0 on 2024-05-28
From <https://rtfeldman.com/0.1-plus-0.2>, on different floating point number semantics:

> Go takes a different approach. When you write 0.1 + 0.2 in Go, that expression gets evaluated to 0.3 at compile time with no precision loss. If you evaluate 0.1 + 0.2 == 0.3, you'll get true. However, if you use variables instead of constants—that is, set a := 0.1 and b := 0.2 and print a + b—you'll once again see 0.30000000000000004. This is because Go evaluates variables at runtime differently from constants at compile time; constants use precise (but slower) base-10 addition, whereas variables use the same base-2 floating-point math as Python, Java, JavaScript, and so on.

To me, the same expression evaluating to different values in compile time and in runtime sounds like a bad idea, as it can make the same code behave differently and generate inconsistent or incompatible results depending on when it was evaluated.

Are there any advantages of doing this? Why does Go do it this way?

The only advantage that I can think of is: if I have a constant evaluator that works the same regardless of the target architecture, I can ship just one pre-compiled standard library (to some intermediate representation) for all targets, which can make cross compilation easier.

Note: the quote is about floating point semantics but I'm wondering about the general idea of having different semantics for expressions in compile time and runtime.
**Tags:** language-design semantics constant-expressions
> **Comments:**
> **username_1**: I agree with you, but there are "least surprise" arguments both ways, so you pick your poison.  
> **username_2**: The [Go Language Spec](https://go.dev/ref/spec#Constants) doesn't seem to support this claim; it requires that implementations "Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed binary exponent of at least 16 bits."  
> **username_3**: The quote does not describe the same expression being evaluated differently at runtime and at compile time.  It describes *different*, related expressions being evaluated differently.  That doesn't invalidate the question, but we should be clear about the situation.  
> **username_4**: Is this question asking about any other types of expressions than floating-point expressions, inexactness, arbitrary precision? in particular where the root-cause is minimum and actual precision (and maybe order-of-evaluation)? We could also construct examples with integer overflow (MAXINT/2 + MAXINT/2  - MAXINT/2), for languages where left-to-right evaluation is not compulsory (think of a vexatious constant defined as (MAXINT/2  - MAXINT/2)). Or stressing FP overflow and underflow.  
> **username_4**: ...and when you ask "Are there *any* advantages..." are you only asking about precision of the final result (or failing to overflow/underflow where that was potentially avoidable), or implying that allowing a compiler/runtime some free choice in order-of-evaluation might in some cases be desirable, sacricing speed for accuracy? I don't think "Are there *any* advantages..." is well-posed, obviously there are advantages, disadvantages and tradeoffs; please restate it.  
> **username_4**: ...also, more precision in the final answer is not always necessarily better, if compatibility with existing implementations also matters, such as expressions which existing implementations evaluate to 0.3 instead of 0.30000000000000004. (Or conversely, do we want to stress-test the effects of what a language's spec allows for in optional precision? It depends what you mean by "advantages")  
> **username_4**: ...obvious example is code using floating-point for currency (or other finite-precision) amounts, where "your change is \$0.3" is "more correct" than "\$0.30000000000000004". (How to handle if that "correctness" depended on implementation-specifics rather than language spec?) Or, floats being passed around/converted to/from strings e.g. JSON parsing  
> **username_5**: How do you judge the relative advantages of the two approaches, given that the allegedly better one is available only for decimal rationals? (That is, rationals whose denominator divides a power of 10.) The approach that handles 3/10 so nicely is not available for 1/3 or 1/7, for example.  
> **username_0**: @RosieF the question is not really that much focused on floating point semantics, I'm interested in any kind of differences between compile time and runtime semantics of expression (or other constructors), and why would one want to implement these differences.  
---
## 4 answers
### Answer by username_6 on 2024-05-28. Score: 23
Evaluating the same expression differently at compile time versus runtime would indeed be horrendous behavior.  But, that's not really what's going on here.
Go supports "untyped" floating point expressions.  These are floating point expressions that are not associated with a specific, fixed precision representation like float32 or float64, and they are evaluated as if the underlying type had infinite precision.
(Or at least that's the idea.  Go actually leaves some of the details as an "implementation restriction". Looking at the actual implementation for the "usual" compiler in src/go/constant/value.go, it looks like Go tries to use rational numbers where reasonable or "big floats" from math/big otherwise.  So, 0.1, 0.2, and 0.3 are likely represented exactly as rational numbers, while sqrt(2) probably gets a big float representation).
Anyway, it turns out that only constant expressions are allowed to be "untyped" like this, and so such expressions can be and are evaluated at compile time, but this is an implementation detail.  The bottom line is that the expression 0.3 - 0.1 - 0.2 in Go is an untyped expression that evaluates to an untyped floating point value that is precisely zero, while the expression a - 0.1 - 0.2 (for const a float64 = 0.3) is a float64 expression that evaluates to around -2.78e-17.  They are different expressions (or, if you prefer, the "same" expression evaluated using two different floating point types), and whether they are evaluated at compile time or runtime, their values will be different.
This is not entirely without precedent.  In Haskell, the numeric literal 0.3 is internally represented as a polymorphic function call that retains the full precision of the value independent of any specific representation:
fromRational (3 % 10) :: Num a => a

Consequently, if the Haskell expression 0.3 - 0.1 - 0.2, is evaluated in a context where the floating point type is Double, the exact representations of the literals will be converted to inexact Double representations, and the expression will evaluate to some non-zero fuzz (at compile time or runtime, depending on how smart the compiler is).  On the other hand, if the expression is evaluated in a context where the floating point type is Rational (the type of rational numbers), the expression will evaluate to exactly zero:
{-# LANGUAGE NoMonomorphismRestriction #-}

main = do
  let expr = 0.3 - 0.1 - 0.2
  print (expr :: Double)             -- -2.78e-17
  print (expr :: Rational)           -- 0 % 1 (i.e., fraction 0/1)

If you're unfamiliar with Haskell, note that the :: syntax is not a "cast".  It's a type signature providing guidance to the type checker to resolve the types -- not only of the overall expression expr -- but of all of the numeric literals and operators within that expression.
So, here, expr represents a "loosely typed" expression containing conceptually infinite precision numeric literals.  When it is evaluated at a concrete type, the resulting computation may or may not be precise, depending on the nature of the concrete type.
The advantage in Haskell is that a literal like 0.3, which isn't intended to be tied to any specific numeric representation, can always be used to select the "best representation of 0.3" for whatever concrete type it is ultimately assigned, without the loss of precision that might take place if it was first converted to a double.
> **Comments:**
> **username_7** (2024-05-29): If only constants can be in this magic untyped state, isn’t that de-facto equivalent to "Evaluating the same expression differently at compile time versus runtime"?  
> **username_8** (2024-05-29): @MisterMiyagi No, because the same expression can't be both constant and not constant.  
> **username_6** (2024-05-29): Note that you can write `const a = float64(0.3)-0.1-0.2` and get `-2.78e-17`, presumably at compile time.  
> **username_9** (2024-05-30): I want to note - Go uses **incredibly** confusing terminology here, well at odds with the conventional jargon. These "untyped" values do have what for all intents and purposes are types. Go calls these "kinds" (aargh!) and defines a one-way convertibility relation between them [in the specification](https://go.dev/ref/spec#Constant_expressions): from rune to integer to floating-point to complex. (The spec actually says that integers coerce to runes - this appears to be incorrect.)  
> **username_9** (2024-05-30): You will notice the lack of "untyped" strings or "untyped" bools in that list. This is because they do not coerce to any other "untyped" type. Attempting to compile the expression `const a = 4 * "hello"` results in the humorous error message `mismatched types untyped string and untyped int`  
> **username_10** (2024-05-30): 'In practice, an implementation probably wouldn't use "base-10 addition" as suggested in that blog post you cite, but would instead just use a representation with much more precision than a float64 to give the illusion of infinite precision.' - there is no base-2^n floating point representation of 0.3 that has infinite precision.  In practice, the compiler needs to use rational-style or base-10 to here.  
> **username_6** (2024-05-30): I've updated the answer, after taking a look at `constant/value.go` in the compiler source.  
---
### Answer by username_11 on 2024-05-28. Score: 4
Numbers are the quintessential value objects. $1+1=2$ anywhere in the world, but numbers in computers work differently.
$1+1=2$ in u8 and u64, but $1+1=0$ in u1, for the same reason that $255+1=0$ in u8. That is, number realizations in computers do not follow the same rules and identities of pure abstract numbers of pure abstract mathematics.
In source code, constants are special. Because constant folding can generate huge savings by successive code reductions, to the point of rendering entire functions, even entire programs, into a constant result that executes no code at all, it's very profitable to pour a lot of effort into these optimizations.
This is where abstract numbers and constants meet. As constants can be folded in successive rounds, this also can be done for numbers and number operations. But it is necessary to always keep in mind the above notion, that operations on number realizations generate different results than may be expected from abstract numbers.
An if ( 0.1 + 0.2 == 0.3 ) will probably fail in any computer that follows IEEE 734, because some of these numbers are not representable in binary floating point format. A decimal number that is not representable is aproximated into another number, and because of that, decimal identities do not hold anymore.
As you can imagine, this is bad for constant folding and branch eliminations, as exemplified on the if() above, and will run against human intuition.
So there is an appeal to treat numeric constants differently than turning them into realized numbers immediately for operations. By treating these source numbers as ComptimeBigDecimal, or even better, as ComptimeFraction, it is possible to have intuitive, previsible and stable results for additions, subtractions and multiplications, and is also possible to minimize the imprecision accumulation of operations and to postnote imprecision of divisions to one last Fraction into number realization format.
But as you noted, it is bad that number expressions generate different results in compile time and run time.
If a compiler treats number constants differently in compilation time, it also should expose this functionality as a library, and by doing so, be possible to replicate the results of compile time in run time.
But none of this will change the reality of existence, and even preference, of realized number types, with all the imperfections noted above. Because these realizated number formats are fast, sometimes hardware fast, and no simulated integer/decimal/fractional code can reach this performance.
Beyond these concerns, it's advantageous to treat source code numbers differently from numbers realized at runtime, even in the absence of number operations.
This is an example I recently encountered in Python:
>>> from fractions import Fraction
>>> Fraction(1.65)
Fraction(3715469692580659, 2251799813685248)
>>> Fraction("1.65")
Fraction(33, 20)

Why did $1.65$ get converted to such a weird fraction, even though it can be easily converted to $100/65$? Because the decimal constant 1.65 in the source code was first converted to a double, and then this other number was precisely converted in a decimal fraction.
By simply delaying the source code number literal realization, it would be possible to code Fraction() to accept a comptime_decimal_literal, that in turn would result in both expressions above reaching an exact fraction of $1.65$, instead of the approximate fraction of $3715469692580659/2251799813685248$.
---
### Answer by username_13 on 2024-05-29. Score: 3
If it has advantages, a better approximation would be to have a new type for decimals up to any finite precision, or just fractions. The new type doesn't need to be restricted to compile time. Once it gets assigned to a float, it gets truncated.

I don't think the difference between compile time and runtime is a big problem, as people should already learn the floating-point accuracy problem, and if they don't, they would get a surprise sooner or later. It's less of a problem if we use a different type, as it has a clear semantic and doesn't really add anything special.

But there are some other possible surprises:

* Decimals have the implication of being possibly inaccurate. It could be automatically cast to floating points in some cases, where you would want to keep the original value for fraction number types. But you could solve it by adding a tag to the type to distinguish them, if your language also has fractions.
* Some functions might be much slower in arbitrary precision than floats. If you don't like distinguishing the functions by names, you could solve it by also matching the expected output type, or branching based on the value range. They may evolve into powerful features. Sometimes your language might already have them. But if it didn't, you may not think it is worth the extra work and complexity.
* It's not sure what to do with `x + 0.1 + 0.2 == x + 0.3` where `x` is a float. You may define it to anything as long as it is consistent. But by the slight more difficulty to understand it, it is likely to ruin what you have thought that could make the language better in this way.

The advantage could be to make the computation more precise in compile time, where you would allow it to use more time, because the work is reused, and to keep the efficiency at runtime. But firstly, the little more precision usually just doesn't matter. Secondly, if you really want to do this, you could do more, by using faster computations in the most inner loops and more accurate computations elsewhere. It just isn't useful if you only do it in one unimportant case.

But I had another idea. I would advocate also supporting fixed-point numbers (fractions with constant denominator, and with easier representation) in newer practical languages, as it could reduce mistakes in many situations such as currency. If your language has fixed-point numbers, apparently it has to work like this. And if your language may add support for fixed-point numbers later, it would create a bigger surprise if it didn't.

In general, you should better distinguish them by the type, instead of compile time versus runtime. But it is understandable to do it in this way if the language don't have fixed-points yet, and you only leave the option for later.
---
### Answer by username_12 on 2024-05-31. Score: 0
One of the design goals of the Go language is to be easy of use. It aims to be a prototyping language like Python, but still remain efficient by having a compiler and static typing.

This feature fits perfectly in the spirit of a prototyping language: worry less about the small implementation details. By having 0.1 + 0.2 evaluate *exactly* to 0.3, the developer does not have to think about floating point arithmetic in this specific circumstance and this allows for faster prototyping.

In programming languages there is often the trade-off of between following the programmers intent and of being precise. On one end of the spectrum, Python tries to follow the programmers intent. When evaluating expressions, it tries to predict what the programmer *wants*, not what would be the most logical result. This greatly improves developing speed and developer experience (up to debate). The downside is that Python is slow and can be unpredictable.

On the other end is Rust. Rust is very precise: it evaluates according to strict rules. The end result is always predictable and any piece of code leaves little doubt about what exactly is happening under the hood. Because of this, Rust code is often fast, robust and reliable. But the downside is that it can take quite some effort to express the things you want using the - relatively - small space of valid rust.

So on the continuum between following intent and precision, this implementation detail is closer to following intent. It makes it harder to predict what the code does, but maybe we don't want to predict what the code does. We just want it to *work*. So is this is a good thing? I would say it depends use case in the same way that Python is great for specific purposes.
---
